From c1da7f1db7ee12802ac524cfc2602e2c978be878 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 5 Jun 2018 14:27:35 -0400 Subject: [PATCH 0001/4053] :art: spelling --- lib/models/repository-states/present.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index bd8c2fb7c9..44ec3099e9 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -787,7 +787,7 @@ function buildHunksFromDiff(diff) { } else if (status === 'nonewline') { hunkLine = new HunkLine(text.substr(1), status, -1, -1, diffLineNumber++); } else { - throw new Error(`unknow status type: ${status}`); + throw new Error(`unknown status type: ${status}`); } return hunkLine; }); From d83ebfed9a85b9724ed58990a63003368b098d8e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Jun 2018 08:36:42 -0400 Subject: [PATCH 0002/4053] Trigger refModel after the has been initialized --- lib/atom/atom-text-editor.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index 73139403e3..ab7eedf561 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -61,10 +61,6 @@ export default class AtomTextEditor extends React.PureComponent { this.refElement = new RefHolder(); this.refModel = new RefHolder(); - - this.subs.add( - this.refElement.observe(element => this.refModel.setter(element.getModel())), - ); } render() { @@ -81,11 +77,19 @@ export default class AtomTextEditor extends React.PureComponent { componentDidMount() { this.setAttributesOnElement(this.props); - const editor = this.getModel(); - this.subs.add( - editor.onDidChange(this.didChange), - editor.onDidChangeCursorPosition(this.didChangeCursorPosition), - ); + this.refElement.map(element => { + const editor = element.getModel(); + + this.refModel.setter(editor); + + this.subs.add( + editor.onDidChange(this.didChange), + editor.onDidChangeCursorPosition(this.didChangeCursorPosition), + ); + + // shhh, eslint. shhhh + return null; + }); } componentDidUpdate(prevProps) { From f04bb7d18b5c18f64719a7f22f7e4a5c06627166 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Jun 2018 09:14:30 -0400 Subject: [PATCH 0003/4053] It begins --- lib/containers/file-patch-container.js | 53 ++ lib/controllers/file-patch-controller.js | 543 +------------- lib/controllers/file-patch-controller.old.js | 550 ++++++++++++++ lib/controllers/root-controller.js | 24 +- lib/items/file-patch-item.js | 91 +++ lib/views/file-patch-view.js | 717 ++----------------- lib/views/file-patch-view.old.js | 716 ++++++++++++++++++ 7 files changed, 1462 insertions(+), 1232 deletions(-) create mode 100644 lib/containers/file-patch-container.js create mode 100644 lib/controllers/file-patch-controller.old.js create mode 100644 lib/items/file-patch-item.js create mode 100644 lib/views/file-patch-view.old.js diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js new file mode 100644 index 0000000000..9e5d5a4eee --- /dev/null +++ b/lib/containers/file-patch-container.js @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import yubikiri from 'yubikiri'; + +import {autobind} from '../helpers'; +import ObserveModel from '../views/observe-model'; +import FilePatchController from '../controllers/file-patch-controller'; + +export default class FilePatchContainer extends React.Component { + static propTypes = { + repository: PropTypes.object.isRequired, + stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), + relPath: PropTypes.string.isRequired, + + tooltips: PropTypes.object.isRequired, + } + + constructor(props) { + super(props); + + autobind(this, 'fetchData', 'renderWithData'); + } + + fetchData(repository) { + return yubikiri({ + filePatch: repository.getFilePatchForPath(this.props.relPath, {staged: this.props.stagingStatus === 'staged'}), + isPartiallyStaged: repository.isPartiallyStaged(this.props.relPath), + }); + } + + render() { + return ( + + {this.renderWithData} + + ); + } + + renderWithData(data) { + if (data === null) { + return null; + } + + return ( + + ); + } +} diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 90d6a67df8..3df858ab0a 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -1,550 +1,9 @@ -import path from 'path'; import React from 'react'; -import PropTypes from 'prop-types'; -import {Point} from 'atom'; -import {Emitter, CompositeDisposable} from 'event-kit'; -import Switchboard from '../switchboard'; import FilePatchView from '../views/file-patch-view'; -import ModelObserver from '../models/model-observer'; -import {autobind} from '../helpers'; export default class FilePatchController extends React.Component { - static propTypes = { - largeDiffByteThreshold: PropTypes.number, - getRepositoryForWorkdir: PropTypes.func.isRequired, - workingDirectoryPath: PropTypes.string.isRequired, - commandRegistry: PropTypes.object.isRequired, - deserializers: PropTypes.object.isRequired, - tooltips: PropTypes.object.isRequired, - filePath: PropTypes.string.isRequired, - lineNumber: PropTypes.number, - initialStagingStatus: PropTypes.oneOf(['unstaged', 'staged']).isRequired, - discardLines: PropTypes.func.isRequired, - didSurfaceFile: PropTypes.func.isRequired, - quietlySelectItem: PropTypes.func.isRequired, - undoLastDiscard: PropTypes.func.isRequired, - openFiles: PropTypes.func.isRequired, - switchboard: PropTypes.instanceOf(Switchboard), - } - - static defaultProps = { - largeDiffByteThreshold: 32768, - switchboard: new Switchboard(), - } - - static uriPattern = 'atom-github://file-patch/{relpath...}?workdir={workdir}&stagingStatus={stagingStatus}' - - static buildURI(relPath, workdir, stagingStatus) { - return 'atom-github://file-patch/' + - relPath + - `?workdir=${encodeURIComponent(workdir)}` + - `&stagingStatus=${encodeURIComponent(stagingStatus)}`; - } - - static confirmedLargeFilePatches = new Set() - - static resetConfirmedLargeFilePatches() { - this.confirmedLargeFilePatches = new Set(); - } - - constructor(props, context) { - super(props, context); - autobind( - this, - 'onRepoRefresh', 'handleShowDiffClick', 'attemptHunkStageOperation', 'attemptFileStageOperation', - 'attemptModeStageOperation', 'attemptSymlinkStageOperation', 'attemptLineStageOperation', 'didSurfaceFile', - 'diveIntoCorrespondingFilePatch', 'focus', 'openCurrentFile', 'discardLines', 'undoLastDiscard', 'hasUndoHistory', - ); - - this.stagingOperationInProgress = false; - this.emitter = new Emitter(); - - this.state = { - filePatch: null, - stagingStatus: props.initialStagingStatus, - isPartiallyStaged: false, - }; - - this.repositoryObserver = new ModelObserver({ - didUpdate: repo => this.onRepoRefresh(repo), - }); - this.repositoryObserver.setActiveModel(props.getRepositoryForWorkdir(this.props.workingDirectoryPath)); - - this.filePatchLoadedPromise = new Promise(res => { - this.resolveFilePatchLoadedPromise = res; - }); - - this.subscriptions = new CompositeDisposable(); - this.subscriptions.add( - this.props.switchboard.onDidFinishActiveContextUpdate(() => { - this.repositoryObserver.setActiveModel(this.props.getRepositoryForWorkdir(this.props.workingDirectoryPath)); - }), - ); - } - - getFilePatchLoadedPromise() { - return this.filePatchLoadedPromise; - } - - getStagingStatus() { - return this.state.stagingStatus; - } - - getFilePath() { - return this.props.filePath; - } - - getWorkingDirectory() { - return this.props.workingDirectoryPath; - } - - getTitle() { - let title = this.isStaged() ? 'Staged' : 'Unstaged'; - title += ' Changes: '; - title += this.props.filePath; - return title; - } - - serialize() { - return { - deserializer: 'FilePatchControllerStub', - uri: this.getURI(), - }; - } - - onDidDestroy(callback) { - return this.emitter.on('did-destroy', callback); - } - - terminatePendingState() { - if (!this.hasTerminatedPendingState) { - this.emitter.emit('did-terminate-pending-state'); - this.hasTerminatedPendingState = true; - } - } - - onDidTerminatePendingState(callback) { - return this.emitter.on('did-terminate-pending-state', callback); - } - - async onRepoRefresh(repository) { - const staged = this.isStaged(); - let filePatch = await this.getFilePatchForPath(this.props.filePath, staged); - const isPartiallyStaged = await repository.isPartiallyStaged(this.props.filePath); - - const onFinish = () => { - this.props.switchboard.didFinishRepositoryRefresh(); - }; - - if (filePatch) { - this.resolveFilePatchLoadedPromise(); - if (!this.destroyed) { - this.setState({filePatch, isPartiallyStaged}, onFinish); - } else { - onFinish(); - } - } else { - const oldFilePatch = this.state.filePatch; - if (oldFilePatch) { - filePatch = oldFilePatch.clone({ - oldFile: oldFilePatch.oldFile.clone({mode: null, symlink: null}), - newFile: oldFilePatch.newFile.clone({mode: null, symlink: null}), - patch: oldFilePatch.getPatch().clone({hunks: []}), - }); - if (!this.destroyed) { - this.setState({filePatch, isPartiallyStaged}, onFinish); - } else { - onFinish(); - } - } - } - } - - getFilePatchForPath(filePath, staged) { - const repository = this.repositoryObserver.getActiveModel(); - return repository.getFilePatchForPath(filePath, {staged}); - } - - componentDidUpdate(_prevProps, prevState) { - if (prevState.stagingStatus !== this.state.stagingStatus) { - this.emitter.emit('did-change-title'); - } - } - - goToDiffLine(lineNumber) { - this.filePatchView.goToDiffLine(lineNumber); - } - - componentWillUnmount() { - this.destroy(); - } - render() { - const fp = this.state.filePatch; - const hunks = fp ? fp.getHunks() : []; - const executableModeChange = fp && fp.didChangeExecutableMode() ? - {oldMode: fp.getOldMode(), newMode: fp.getNewMode()} : - null; - const symlinkChange = fp && fp.hasSymlink() ? - { - oldSymlink: fp.getOldSymlink(), - newSymlink: fp.getNewSymlink(), - typechange: fp.hasTypechange(), - filePatchStatus: fp.getStatus(), - } : null; - const repository = this.repositoryObserver.getActiveModel(); - if (repository.isUndetermined() || repository.isLoading()) { - return ( -
- -
- ); - } else if (repository.isAbsent()) { - return ( -
- - The repository for {this.props.workingDirectoryPath} is not open in Atom. - -
- ); - } else { - const hasUndoHistory = repository ? this.hasUndoHistory() : false; - return ( -
- { this.filePatchView = c; }} - commandRegistry={this.props.commandRegistry} - tooltips={this.props.tooltips} - displayLargeDiffMessage={!this.shouldDisplayLargeDiff(this.state.filePatch)} - byteCount={this.byteCount} - handleShowDiffClick={this.handleShowDiffClick} - hunks={hunks} - executableModeChange={executableModeChange} - symlinkChange={symlinkChange} - filePath={this.props.filePath} - workingDirectoryPath={this.getWorkingDirectory()} - stagingStatus={this.state.stagingStatus} - isPartiallyStaged={this.state.isPartiallyStaged} - attemptLineStageOperation={this.attemptLineStageOperation} - attemptHunkStageOperation={this.attemptHunkStageOperation} - attemptFileStageOperation={this.attemptFileStageOperation} - attemptModeStageOperation={this.attemptModeStageOperation} - attemptSymlinkStageOperation={this.attemptSymlinkStageOperation} - didSurfaceFile={this.didSurfaceFile} - didDiveIntoCorrespondingFilePatch={this.diveIntoCorrespondingFilePatch} - switchboard={this.props.switchboard} - openCurrentFile={this.openCurrentFile} - discardLines={this.discardLines} - undoLastDiscard={this.undoLastDiscard} - hasUndoHistory={hasUndoHistory} - lineNumber={this.props.lineNumber} - /> -
- ); - } - } - - shouldDisplayLargeDiff(filePatch) { - if (!filePatch) { return true; } - - const fullPath = path.join(this.getWorkingDirectory(), this.props.filePath); - if (FilePatchController.confirmedLargeFilePatches.has(fullPath)) { - return true; - } - - this.byteCount = filePatch.getByteSize(); - return this.byteCount < this.props.largeDiffByteThreshold; - } - - onDidChangeTitle(callback) { - return this.emitter.on('did-change-title', callback); - } - - handleShowDiffClick() { - if (this.repositoryObserver.getActiveModel()) { - const fullPath = path.join(this.getWorkingDirectory(), this.props.filePath); - FilePatchController.confirmedLargeFilePatches.add(fullPath); - this.forceUpdate(); - } - } - - async stageHunk(hunk) { - this.props.switchboard.didBeginStageOperation({stage: true, hunk: true}); - - await this.repositoryObserver.getActiveModel().applyPatchToIndex( - this.state.filePatch.getStagePatchForHunk(hunk), - ); - - this.props.switchboard.didFinishStageOperation({stage: true, hunk: true}); - } - - async unstageHunk(hunk) { - this.props.switchboard.didBeginStageOperation({unstage: true, hunk: true}); - - await this.repositoryObserver.getActiveModel().applyPatchToIndex( - this.state.filePatch.getUnstagePatchForHunk(hunk), - ); - - this.props.switchboard.didFinishStageOperation({unstage: true, hunk: true}); - } - - stageOrUnstageHunk(hunk) { - const stagingStatus = this.state.stagingStatus; - if (stagingStatus === 'unstaged') { - return this.stageHunk(hunk); - } else if (stagingStatus === 'staged') { - return this.unstageHunk(hunk); - } else { - throw new Error(`Unknown stagingStatus: ${stagingStatus}`); - } - } - - async stageFile() { - this.props.switchboard.didBeginStageOperation({stage: true, file: true}); - - await this.repositoryObserver.getActiveModel().stageFiles([this.props.filePath]); - this.props.switchboard.didFinishStageOperation({stage: true, file: true}); - } - - async unstageFile() { - this.props.switchboard.didBeginStageOperation({unstage: true, file: true}); - - await this.repositoryObserver.getActiveModel().unstageFiles([this.props.filePath]); - - this.props.switchboard.didFinishStageOperation({unstage: true, file: true}); - } - - stageOrUnstageFile() { - const stagingStatus = this.state.stagingStatus; - if (stagingStatus === 'unstaged') { - return this.stageFile(); - } else if (stagingStatus === 'staged') { - return this.unstageFile(); - } else { - throw new Error(`Unknown stagingStatus: ${stagingStatus}`); - } - } - - async stageModeChange(mode) { - this.props.switchboard.didBeginStageOperation({stage: true, mode: true}); - - await this.repositoryObserver.getActiveModel().stageFileModeChange( - this.props.filePath, mode, - ); - this.props.switchboard.didFinishStageOperation({stage: true, mode: true}); - } - - async unstageModeChange(mode) { - this.props.switchboard.didBeginStageOperation({unstage: true, mode: true}); - - await this.repositoryObserver.getActiveModel().stageFileModeChange( - this.props.filePath, mode, - ); - this.props.switchboard.didFinishStageOperation({unstage: true, mode: true}); - } - - stageOrUnstageModeChange() { - const stagingStatus = this.state.stagingStatus; - const oldMode = this.state.filePatch.getOldMode(); - const newMode = this.state.filePatch.getNewMode(); - if (stagingStatus === 'unstaged') { - return this.stageModeChange(newMode); - } else if (stagingStatus === 'staged') { - return this.unstageModeChange(oldMode); - } else { - throw new Error(`Unknown stagingStatus: ${stagingStatus}`); - } - } - - async stageSymlinkChange() { - this.props.switchboard.didBeginStageOperation({stage: true, symlink: true}); - - const filePatch = this.state.filePatch; - if (filePatch.hasTypechange() && filePatch.getStatus() === 'added') { - await this.repositoryObserver.getActiveModel().stageFileSymlinkChange(this.props.filePath); - } else { - await this.repositoryObserver.getActiveModel().stageFiles([this.props.filePath]); - } - this.props.switchboard.didFinishStageOperation({stage: true, symlink: true}); - } - - async unstageSymlinkChange() { - this.props.switchboard.didBeginStageOperation({unstage: true, symlink: true}); - - const filePatch = this.state.filePatch; - if (filePatch.hasTypechange() && filePatch.getStatus() === 'deleted') { - await this.repositoryObserver.getActiveModel().stageFileSymlinkChange(this.props.filePath); - } else { - await this.repositoryObserver.getActiveModel().unstageFiles([this.props.filePath]); - } - this.props.switchboard.didFinishStageOperation({unstage: true, symlink: true}); - } - - stageOrUnstageSymlinkChange() { - const stagingStatus = this.state.stagingStatus; - if (stagingStatus === 'unstaged') { - return this.stageSymlinkChange(); - } else if (stagingStatus === 'staged') { - return this.unstageSymlinkChange(); - } else { - throw new Error(`Unknown stagingStatus: ${stagingStatus}`); - } - } - - attemptHunkStageOperation(hunk) { - if (this.stagingOperationInProgress) { - return; - } - - this.stagingOperationInProgress = true; - this.props.switchboard.getChangePatchPromise().then(() => { - this.stagingOperationInProgress = false; - }); - - this.stageOrUnstageHunk(hunk); - } - - attemptFileStageOperation() { - if (this.stagingOperationInProgress) { - return; - } - - this.stagingOperationInProgress = true; - this.props.switchboard.getChangePatchPromise().then(() => { - this.stagingOperationInProgress = false; - }); - - this.stageOrUnstageFile(); - } - - attemptModeStageOperation() { - if (this.stagingOperationInProgress) { - return; - } - - this.stagingOperationInProgress = true; - this.props.switchboard.getChangePatchPromise().then(() => { - this.stagingOperationInProgress = false; - }); - - this.stageOrUnstageModeChange(); - } - - attemptSymlinkStageOperation() { - if (this.stagingOperationInProgress) { - return; - } - - this.stagingOperationInProgress = true; - this.props.switchboard.getChangePatchPromise().then(() => { - this.stagingOperationInProgress = false; - }); - - this.stageOrUnstageSymlinkChange(); - } - - async stageLines(lines) { - this.props.switchboard.didBeginStageOperation({stage: true, line: true}); - - await this.repositoryObserver.getActiveModel().applyPatchToIndex( - this.state.filePatch.getStagePatchForLines(lines), - ); - - this.props.switchboard.didFinishStageOperation({stage: true, line: true}); - } - - async unstageLines(lines) { - this.props.switchboard.didBeginStageOperation({unstage: true, line: true}); - - await this.repositoryObserver.getActiveModel().applyPatchToIndex( - this.state.filePatch.getUnstagePatchForLines(lines), - ); - - this.props.switchboard.didFinishStageOperation({unstage: true, line: true}); - } - - stageOrUnstageLines(lines) { - const stagingStatus = this.state.stagingStatus; - if (stagingStatus === 'unstaged') { - return this.stageLines(lines); - } else if (stagingStatus === 'staged') { - return this.unstageLines(lines); - } else { - throw new Error(`Unknown stagingStatus: ${stagingStatus}`); - } - } - - attemptLineStageOperation(lines) { - if (this.stagingOperationInProgress) { - return; - } - - this.stagingOperationInProgress = true; - this.props.switchboard.getChangePatchPromise().then(() => { - this.stagingOperationInProgress = false; - }); - - this.stageOrUnstageLines(lines); - } - - didSurfaceFile() { - if (this.props.didSurfaceFile) { - this.props.didSurfaceFile(this.props.filePath, this.state.stagingStatus); - } - } - - async diveIntoCorrespondingFilePatch() { - const stagingStatus = this.isStaged() ? 'unstaged' : 'staged'; - const filePatch = await this.getFilePatchForPath(this.props.filePath, stagingStatus === 'staged'); - this.props.quietlySelectItem(this.props.filePath, stagingStatus); - this.setState({filePatch, stagingStatus}); - } - - isStaged() { - return this.state.stagingStatus === 'staged'; - } - - isEmpty() { - return !this.state.filePatch || this.state.filePatch.getHunks().length === 0; - } - - focus() { - if (this.filePatchView) { - this.filePatchView.focus(); - } - } - - wasActivated(isStillActive) { - process.nextTick(() => { - isStillActive() && this.focus(); - }); - } - - async openCurrentFile({lineNumber} = {}) { - const [textEditor] = await this.props.openFiles([this.props.filePath]); - const position = new Point(lineNumber ? lineNumber - 1 : 0, 0); - textEditor.scrollToBufferPosition(position, {center: true}); - textEditor.setCursorBufferPosition(position); - return textEditor; - } - - discardLines(lines) { - return this.props.discardLines(this.state.filePatch, lines, this.repositoryObserver.getActiveModel()); - } - - undoLastDiscard() { - return this.props.undoLastDiscard(this.props.filePath, this.repositoryObserver.getActiveModel()); - } - - hasUndoHistory() { - return this.repositoryObserver.getActiveModel().hasDiscardHistory(this.props.filePath); - } - - destroy() { - this.destroyed = true; - this.subscriptions.dispose(); - this.repositoryObserver.destroy(); - this.emitter.emit('did-destroy'); + return ; } } diff --git a/lib/controllers/file-patch-controller.old.js b/lib/controllers/file-patch-controller.old.js new file mode 100644 index 0000000000..90d6a67df8 --- /dev/null +++ b/lib/controllers/file-patch-controller.old.js @@ -0,0 +1,550 @@ +import path from 'path'; +import React from 'react'; +import PropTypes from 'prop-types'; +import {Point} from 'atom'; +import {Emitter, CompositeDisposable} from 'event-kit'; + +import Switchboard from '../switchboard'; +import FilePatchView from '../views/file-patch-view'; +import ModelObserver from '../models/model-observer'; +import {autobind} from '../helpers'; + +export default class FilePatchController extends React.Component { + static propTypes = { + largeDiffByteThreshold: PropTypes.number, + getRepositoryForWorkdir: PropTypes.func.isRequired, + workingDirectoryPath: PropTypes.string.isRequired, + commandRegistry: PropTypes.object.isRequired, + deserializers: PropTypes.object.isRequired, + tooltips: PropTypes.object.isRequired, + filePath: PropTypes.string.isRequired, + lineNumber: PropTypes.number, + initialStagingStatus: PropTypes.oneOf(['unstaged', 'staged']).isRequired, + discardLines: PropTypes.func.isRequired, + didSurfaceFile: PropTypes.func.isRequired, + quietlySelectItem: PropTypes.func.isRequired, + undoLastDiscard: PropTypes.func.isRequired, + openFiles: PropTypes.func.isRequired, + switchboard: PropTypes.instanceOf(Switchboard), + } + + static defaultProps = { + largeDiffByteThreshold: 32768, + switchboard: new Switchboard(), + } + + static uriPattern = 'atom-github://file-patch/{relpath...}?workdir={workdir}&stagingStatus={stagingStatus}' + + static buildURI(relPath, workdir, stagingStatus) { + return 'atom-github://file-patch/' + + relPath + + `?workdir=${encodeURIComponent(workdir)}` + + `&stagingStatus=${encodeURIComponent(stagingStatus)}`; + } + + static confirmedLargeFilePatches = new Set() + + static resetConfirmedLargeFilePatches() { + this.confirmedLargeFilePatches = new Set(); + } + + constructor(props, context) { + super(props, context); + autobind( + this, + 'onRepoRefresh', 'handleShowDiffClick', 'attemptHunkStageOperation', 'attemptFileStageOperation', + 'attemptModeStageOperation', 'attemptSymlinkStageOperation', 'attemptLineStageOperation', 'didSurfaceFile', + 'diveIntoCorrespondingFilePatch', 'focus', 'openCurrentFile', 'discardLines', 'undoLastDiscard', 'hasUndoHistory', + ); + + this.stagingOperationInProgress = false; + this.emitter = new Emitter(); + + this.state = { + filePatch: null, + stagingStatus: props.initialStagingStatus, + isPartiallyStaged: false, + }; + + this.repositoryObserver = new ModelObserver({ + didUpdate: repo => this.onRepoRefresh(repo), + }); + this.repositoryObserver.setActiveModel(props.getRepositoryForWorkdir(this.props.workingDirectoryPath)); + + this.filePatchLoadedPromise = new Promise(res => { + this.resolveFilePatchLoadedPromise = res; + }); + + this.subscriptions = new CompositeDisposable(); + this.subscriptions.add( + this.props.switchboard.onDidFinishActiveContextUpdate(() => { + this.repositoryObserver.setActiveModel(this.props.getRepositoryForWorkdir(this.props.workingDirectoryPath)); + }), + ); + } + + getFilePatchLoadedPromise() { + return this.filePatchLoadedPromise; + } + + getStagingStatus() { + return this.state.stagingStatus; + } + + getFilePath() { + return this.props.filePath; + } + + getWorkingDirectory() { + return this.props.workingDirectoryPath; + } + + getTitle() { + let title = this.isStaged() ? 'Staged' : 'Unstaged'; + title += ' Changes: '; + title += this.props.filePath; + return title; + } + + serialize() { + return { + deserializer: 'FilePatchControllerStub', + uri: this.getURI(), + }; + } + + onDidDestroy(callback) { + return this.emitter.on('did-destroy', callback); + } + + terminatePendingState() { + if (!this.hasTerminatedPendingState) { + this.emitter.emit('did-terminate-pending-state'); + this.hasTerminatedPendingState = true; + } + } + + onDidTerminatePendingState(callback) { + return this.emitter.on('did-terminate-pending-state', callback); + } + + async onRepoRefresh(repository) { + const staged = this.isStaged(); + let filePatch = await this.getFilePatchForPath(this.props.filePath, staged); + const isPartiallyStaged = await repository.isPartiallyStaged(this.props.filePath); + + const onFinish = () => { + this.props.switchboard.didFinishRepositoryRefresh(); + }; + + if (filePatch) { + this.resolveFilePatchLoadedPromise(); + if (!this.destroyed) { + this.setState({filePatch, isPartiallyStaged}, onFinish); + } else { + onFinish(); + } + } else { + const oldFilePatch = this.state.filePatch; + if (oldFilePatch) { + filePatch = oldFilePatch.clone({ + oldFile: oldFilePatch.oldFile.clone({mode: null, symlink: null}), + newFile: oldFilePatch.newFile.clone({mode: null, symlink: null}), + patch: oldFilePatch.getPatch().clone({hunks: []}), + }); + if (!this.destroyed) { + this.setState({filePatch, isPartiallyStaged}, onFinish); + } else { + onFinish(); + } + } + } + } + + getFilePatchForPath(filePath, staged) { + const repository = this.repositoryObserver.getActiveModel(); + return repository.getFilePatchForPath(filePath, {staged}); + } + + componentDidUpdate(_prevProps, prevState) { + if (prevState.stagingStatus !== this.state.stagingStatus) { + this.emitter.emit('did-change-title'); + } + } + + goToDiffLine(lineNumber) { + this.filePatchView.goToDiffLine(lineNumber); + } + + componentWillUnmount() { + this.destroy(); + } + + render() { + const fp = this.state.filePatch; + const hunks = fp ? fp.getHunks() : []; + const executableModeChange = fp && fp.didChangeExecutableMode() ? + {oldMode: fp.getOldMode(), newMode: fp.getNewMode()} : + null; + const symlinkChange = fp && fp.hasSymlink() ? + { + oldSymlink: fp.getOldSymlink(), + newSymlink: fp.getNewSymlink(), + typechange: fp.hasTypechange(), + filePatchStatus: fp.getStatus(), + } : null; + const repository = this.repositoryObserver.getActiveModel(); + if (repository.isUndetermined() || repository.isLoading()) { + return ( +
+ +
+ ); + } else if (repository.isAbsent()) { + return ( +
+ + The repository for {this.props.workingDirectoryPath} is not open in Atom. + +
+ ); + } else { + const hasUndoHistory = repository ? this.hasUndoHistory() : false; + return ( +
+ { this.filePatchView = c; }} + commandRegistry={this.props.commandRegistry} + tooltips={this.props.tooltips} + displayLargeDiffMessage={!this.shouldDisplayLargeDiff(this.state.filePatch)} + byteCount={this.byteCount} + handleShowDiffClick={this.handleShowDiffClick} + hunks={hunks} + executableModeChange={executableModeChange} + symlinkChange={symlinkChange} + filePath={this.props.filePath} + workingDirectoryPath={this.getWorkingDirectory()} + stagingStatus={this.state.stagingStatus} + isPartiallyStaged={this.state.isPartiallyStaged} + attemptLineStageOperation={this.attemptLineStageOperation} + attemptHunkStageOperation={this.attemptHunkStageOperation} + attemptFileStageOperation={this.attemptFileStageOperation} + attemptModeStageOperation={this.attemptModeStageOperation} + attemptSymlinkStageOperation={this.attemptSymlinkStageOperation} + didSurfaceFile={this.didSurfaceFile} + didDiveIntoCorrespondingFilePatch={this.diveIntoCorrespondingFilePatch} + switchboard={this.props.switchboard} + openCurrentFile={this.openCurrentFile} + discardLines={this.discardLines} + undoLastDiscard={this.undoLastDiscard} + hasUndoHistory={hasUndoHistory} + lineNumber={this.props.lineNumber} + /> +
+ ); + } + } + + shouldDisplayLargeDiff(filePatch) { + if (!filePatch) { return true; } + + const fullPath = path.join(this.getWorkingDirectory(), this.props.filePath); + if (FilePatchController.confirmedLargeFilePatches.has(fullPath)) { + return true; + } + + this.byteCount = filePatch.getByteSize(); + return this.byteCount < this.props.largeDiffByteThreshold; + } + + onDidChangeTitle(callback) { + return this.emitter.on('did-change-title', callback); + } + + handleShowDiffClick() { + if (this.repositoryObserver.getActiveModel()) { + const fullPath = path.join(this.getWorkingDirectory(), this.props.filePath); + FilePatchController.confirmedLargeFilePatches.add(fullPath); + this.forceUpdate(); + } + } + + async stageHunk(hunk) { + this.props.switchboard.didBeginStageOperation({stage: true, hunk: true}); + + await this.repositoryObserver.getActiveModel().applyPatchToIndex( + this.state.filePatch.getStagePatchForHunk(hunk), + ); + + this.props.switchboard.didFinishStageOperation({stage: true, hunk: true}); + } + + async unstageHunk(hunk) { + this.props.switchboard.didBeginStageOperation({unstage: true, hunk: true}); + + await this.repositoryObserver.getActiveModel().applyPatchToIndex( + this.state.filePatch.getUnstagePatchForHunk(hunk), + ); + + this.props.switchboard.didFinishStageOperation({unstage: true, hunk: true}); + } + + stageOrUnstageHunk(hunk) { + const stagingStatus = this.state.stagingStatus; + if (stagingStatus === 'unstaged') { + return this.stageHunk(hunk); + } else if (stagingStatus === 'staged') { + return this.unstageHunk(hunk); + } else { + throw new Error(`Unknown stagingStatus: ${stagingStatus}`); + } + } + + async stageFile() { + this.props.switchboard.didBeginStageOperation({stage: true, file: true}); + + await this.repositoryObserver.getActiveModel().stageFiles([this.props.filePath]); + this.props.switchboard.didFinishStageOperation({stage: true, file: true}); + } + + async unstageFile() { + this.props.switchboard.didBeginStageOperation({unstage: true, file: true}); + + await this.repositoryObserver.getActiveModel().unstageFiles([this.props.filePath]); + + this.props.switchboard.didFinishStageOperation({unstage: true, file: true}); + } + + stageOrUnstageFile() { + const stagingStatus = this.state.stagingStatus; + if (stagingStatus === 'unstaged') { + return this.stageFile(); + } else if (stagingStatus === 'staged') { + return this.unstageFile(); + } else { + throw new Error(`Unknown stagingStatus: ${stagingStatus}`); + } + } + + async stageModeChange(mode) { + this.props.switchboard.didBeginStageOperation({stage: true, mode: true}); + + await this.repositoryObserver.getActiveModel().stageFileModeChange( + this.props.filePath, mode, + ); + this.props.switchboard.didFinishStageOperation({stage: true, mode: true}); + } + + async unstageModeChange(mode) { + this.props.switchboard.didBeginStageOperation({unstage: true, mode: true}); + + await this.repositoryObserver.getActiveModel().stageFileModeChange( + this.props.filePath, mode, + ); + this.props.switchboard.didFinishStageOperation({unstage: true, mode: true}); + } + + stageOrUnstageModeChange() { + const stagingStatus = this.state.stagingStatus; + const oldMode = this.state.filePatch.getOldMode(); + const newMode = this.state.filePatch.getNewMode(); + if (stagingStatus === 'unstaged') { + return this.stageModeChange(newMode); + } else if (stagingStatus === 'staged') { + return this.unstageModeChange(oldMode); + } else { + throw new Error(`Unknown stagingStatus: ${stagingStatus}`); + } + } + + async stageSymlinkChange() { + this.props.switchboard.didBeginStageOperation({stage: true, symlink: true}); + + const filePatch = this.state.filePatch; + if (filePatch.hasTypechange() && filePatch.getStatus() === 'added') { + await this.repositoryObserver.getActiveModel().stageFileSymlinkChange(this.props.filePath); + } else { + await this.repositoryObserver.getActiveModel().stageFiles([this.props.filePath]); + } + this.props.switchboard.didFinishStageOperation({stage: true, symlink: true}); + } + + async unstageSymlinkChange() { + this.props.switchboard.didBeginStageOperation({unstage: true, symlink: true}); + + const filePatch = this.state.filePatch; + if (filePatch.hasTypechange() && filePatch.getStatus() === 'deleted') { + await this.repositoryObserver.getActiveModel().stageFileSymlinkChange(this.props.filePath); + } else { + await this.repositoryObserver.getActiveModel().unstageFiles([this.props.filePath]); + } + this.props.switchboard.didFinishStageOperation({unstage: true, symlink: true}); + } + + stageOrUnstageSymlinkChange() { + const stagingStatus = this.state.stagingStatus; + if (stagingStatus === 'unstaged') { + return this.stageSymlinkChange(); + } else if (stagingStatus === 'staged') { + return this.unstageSymlinkChange(); + } else { + throw new Error(`Unknown stagingStatus: ${stagingStatus}`); + } + } + + attemptHunkStageOperation(hunk) { + if (this.stagingOperationInProgress) { + return; + } + + this.stagingOperationInProgress = true; + this.props.switchboard.getChangePatchPromise().then(() => { + this.stagingOperationInProgress = false; + }); + + this.stageOrUnstageHunk(hunk); + } + + attemptFileStageOperation() { + if (this.stagingOperationInProgress) { + return; + } + + this.stagingOperationInProgress = true; + this.props.switchboard.getChangePatchPromise().then(() => { + this.stagingOperationInProgress = false; + }); + + this.stageOrUnstageFile(); + } + + attemptModeStageOperation() { + if (this.stagingOperationInProgress) { + return; + } + + this.stagingOperationInProgress = true; + this.props.switchboard.getChangePatchPromise().then(() => { + this.stagingOperationInProgress = false; + }); + + this.stageOrUnstageModeChange(); + } + + attemptSymlinkStageOperation() { + if (this.stagingOperationInProgress) { + return; + } + + this.stagingOperationInProgress = true; + this.props.switchboard.getChangePatchPromise().then(() => { + this.stagingOperationInProgress = false; + }); + + this.stageOrUnstageSymlinkChange(); + } + + async stageLines(lines) { + this.props.switchboard.didBeginStageOperation({stage: true, line: true}); + + await this.repositoryObserver.getActiveModel().applyPatchToIndex( + this.state.filePatch.getStagePatchForLines(lines), + ); + + this.props.switchboard.didFinishStageOperation({stage: true, line: true}); + } + + async unstageLines(lines) { + this.props.switchboard.didBeginStageOperation({unstage: true, line: true}); + + await this.repositoryObserver.getActiveModel().applyPatchToIndex( + this.state.filePatch.getUnstagePatchForLines(lines), + ); + + this.props.switchboard.didFinishStageOperation({unstage: true, line: true}); + } + + stageOrUnstageLines(lines) { + const stagingStatus = this.state.stagingStatus; + if (stagingStatus === 'unstaged') { + return this.stageLines(lines); + } else if (stagingStatus === 'staged') { + return this.unstageLines(lines); + } else { + throw new Error(`Unknown stagingStatus: ${stagingStatus}`); + } + } + + attemptLineStageOperation(lines) { + if (this.stagingOperationInProgress) { + return; + } + + this.stagingOperationInProgress = true; + this.props.switchboard.getChangePatchPromise().then(() => { + this.stagingOperationInProgress = false; + }); + + this.stageOrUnstageLines(lines); + } + + didSurfaceFile() { + if (this.props.didSurfaceFile) { + this.props.didSurfaceFile(this.props.filePath, this.state.stagingStatus); + } + } + + async diveIntoCorrespondingFilePatch() { + const stagingStatus = this.isStaged() ? 'unstaged' : 'staged'; + const filePatch = await this.getFilePatchForPath(this.props.filePath, stagingStatus === 'staged'); + this.props.quietlySelectItem(this.props.filePath, stagingStatus); + this.setState({filePatch, stagingStatus}); + } + + isStaged() { + return this.state.stagingStatus === 'staged'; + } + + isEmpty() { + return !this.state.filePatch || this.state.filePatch.getHunks().length === 0; + } + + focus() { + if (this.filePatchView) { + this.filePatchView.focus(); + } + } + + wasActivated(isStillActive) { + process.nextTick(() => { + isStillActive() && this.focus(); + }); + } + + async openCurrentFile({lineNumber} = {}) { + const [textEditor] = await this.props.openFiles([this.props.filePath]); + const position = new Point(lineNumber ? lineNumber - 1 : 0, 0); + textEditor.scrollToBufferPosition(position, {center: true}); + textEditor.setCursorBufferPosition(position); + return textEditor; + } + + discardLines(lines) { + return this.props.discardLines(this.state.filePatch, lines, this.repositoryObserver.getActiveModel()); + } + + undoLastDiscard() { + return this.props.undoLastDiscard(this.props.filePath, this.repositoryObserver.getActiveModel()); + } + + hasUndoHistory() { + return this.repositoryObserver.getActiveModel().hasDiscardHistory(this.props.filePath); + } + + destroy() { + this.destroyed = true; + this.subscriptions.dispose(); + this.repositoryObserver.destroy(); + this.emitter.emit('did-destroy'); + } +} diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index a10a28980f..6dbedb6469 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -16,7 +16,7 @@ import CredentialDialog from '../views/credential-dialog'; import Commands, {Command} from '../atom/commands'; import GitTimingsView from '../views/git-timings-view'; import GithubTabController from './github-tab-controller'; -import FilePatchController from './file-patch-controller'; +import FilePatchItem from '../items/file-patch-item'; import IssueishPaneItem from '../items/issueish-pane-item'; import GitTabItem from '../items/git-tab-item'; import StatusBarTileController from './status-bar-tile-controller'; @@ -318,24 +318,14 @@ export default class RootController extends React.Component { + uriPattern={FilePatchItem.uriPattern}> {({itemHolder, params}) => ( - )} @@ -527,7 +517,7 @@ export default class RootController extends React.Component { } const lineNum = editor.getCursorBufferPosition().row + 1; const filePatchItem = await this.props.workspace.open( - FilePatchController.buildURI(filePath, repoPath, stagingStatus), + FilePatchItem.buildURI(filePath, repoPath, stagingStatus), {pending: true, activatePane: true, activateItem: true}, ); await filePatchItem.getRealItemPromise(); diff --git a/lib/items/file-patch-item.js b/lib/items/file-patch-item.js new file mode 100644 index 0000000000..1c02262dac --- /dev/null +++ b/lib/items/file-patch-item.js @@ -0,0 +1,91 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Emitter} from 'event-kit'; + +import FilePatchContainer from '../containers/file-patch-container'; + +export default class FilePatchItem extends React.Component { + static propTypes = { + repository: PropTypes.object.isRequired, + stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), + relPath: PropTypes.string.isRequired, + + tooltips: PropTypes.object.isRequired, + } + + static uriPattern = 'atom-github://file-patch/{relPath...}?workdir={workdir}&stagingStatus={stagingStatus}' + + static buildURI(relPath, workdir, stagingStatus) { + return 'atom-github://file-patch/' + + relPath + + `?workdir=${encodeURIComponent(workdir)}` + + `&stagingStatus=${encodeURIComponent(stagingStatus)}`; + } + + constructor(props) { + super(props); + + this.emitter = new Emitter(); + this.isDestroyed = false; + this.hasTerminatedPendingState = false; + } + + getTitle() { + let title = this.props.stagingStatus === 'staged' ? 'Staged' : 'Unstaged'; + title += ' Changes: '; + title += this.props.relPath; + return title; + } + + terminatePendingState() { + if (!this.hasTerminatedPendingState) { + this.emitter.emit('did-terminate-pending-state'); + this.hasTerminatedPendingState = true; + } + } + + onDidTerminatePendingState(callback) { + return this.emitter.on('did-terminate-pending-state', callback); + } + + destroy() { + if (!this.isDestroyed) { + this.emitter.emit('did-destroy'); + this.isDestroyed = true; + } + } + + onDidDestroy(callback) { + return this.emitter.on('did-destroy', callback); + } + + render() { + return ( + + ); + } + + serialize() { + return { + deserializer: 'FilePatchControllerStub', + uri: this.getURI(), + }; + } + + getStagingStatus() { + return this.props.stagingStatus; + } + + getFilePath() { + return this.props.relPath; + } + + getWorkingDirectory() { + return this.props.repository.getWorkingDirectoryPath(); + } +} diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index e8067025dc..f774c89437 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -1,716 +1,87 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {CompositeDisposable, Disposable} from 'event-kit'; import cx from 'classnames'; -import bytes from 'bytes'; -import HunkView from './hunk-view'; -import SimpleTooltip from '../atom/simple-tooltip'; -import Commands, {Command} from '../atom/commands'; import FilePatchSelection from '../models/file-patch-selection'; -import Switchboard from '../switchboard'; -import RefHolder from '../models/ref-holder'; -import {autobind} from '../helpers'; - -const executableText = { - 100644: 'non executable 100644', - 100755: 'executable 100755', -}; +import AtomTextEditor from '../atom/atom-text-editor'; +import Marker from '../atom/marker'; +import Decoration from '../atom/decoration'; export default class FilePatchView extends React.Component { static propTypes = { - commandRegistry: PropTypes.object.isRequired, - tooltips: PropTypes.object.isRequired, - filePath: PropTypes.string.isRequired, - hunks: PropTypes.arrayOf(PropTypes.object).isRequired, - executableModeChange: PropTypes.shape({ - oldMode: PropTypes.string.isRequired, - newMode: PropTypes.string.isRequired, - }), - symlinkChange: PropTypes.shape({ - oldSymlink: PropTypes.string, - newSymlink: PropTypes.string, - typechange: PropTypes.bool, - filePatchStatus: PropTypes.string, - }), - stagingStatus: PropTypes.oneOf(['unstaged', 'staged']).isRequired, + stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, isPartiallyStaged: PropTypes.bool.isRequired, - hasUndoHistory: PropTypes.bool.isRequired, - attemptLineStageOperation: PropTypes.func.isRequired, - attemptHunkStageOperation: PropTypes.func.isRequired, - attemptFileStageOperation: PropTypes.func.isRequired, - attemptModeStageOperation: PropTypes.func.isRequired, - attemptSymlinkStageOperation: PropTypes.func.isRequired, - discardLines: PropTypes.func.isRequired, - undoLastDiscard: PropTypes.func.isRequired, - openCurrentFile: PropTypes.func.isRequired, - didSurfaceFile: PropTypes.func.isRequired, - didDiveIntoCorrespondingFilePatch: PropTypes.func.isRequired, - switchboard: PropTypes.instanceOf(Switchboard), - displayLargeDiffMessage: PropTypes.bool, - byteCount: PropTypes.number, - handleShowDiffClick: PropTypes.func.isRequired, - } + filePatch: PropTypes.object.isRequired, - static defaultProps = { - switchboard: new Switchboard(), + tooltips: PropTypes.object.isRequired, } - constructor(props, context) { - super(props, context); - autobind( - this, - 'registerCommands', 'renderButtonGroup', 'renderExecutableModeChange', 'renderSymlinkChange', 'contextMenuOnItem', - 'mousedownOnLine', 'mousemoveOnLine', 'mouseup', 'togglePatchSelectionMode', 'selectNext', 'selectNextElement', - 'selectToNext', 'selectPrevious', 'selectPreviousElement', 'selectToPrevious', 'selectFirst', 'selectToFirst', - 'selectLast', 'selectToLast', 'selectAll', 'didConfirm', 'didMoveRight', 'focus', 'openFile', 'stageOrUnstageAll', - 'stageOrUnstageModeChange', 'stageOrUnstageSymlinkChange', 'discardSelection', - ); - - this.mouseSelectionInProgress = false; - this.disposables = new CompositeDisposable(); - - this.refElement = new RefHolder(); + constructor(props) { + super(props); this.state = { - selection: new FilePatchSelection(this.props.hunks), + selection: new FilePatchSelection(this.props.filePatch.getHunks()), }; } - componentDidMount() { - window.addEventListener('mouseup', this.mouseup); - this.disposables.add(new Disposable(() => window.removeEventListener('mouseup', this.mouseup))); - } - - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - const hunksChanged = this.props.hunks.length !== nextProps.hunks.length || - this.props.hunks.some((hunk, index) => hunk !== nextProps.hunks[index]); - - if (hunksChanged) { - this.setState(prevState => { - return { - selection: prevState.selection.updateHunks(nextProps.hunks), - }; - }, () => { - nextProps.switchboard.didChangePatch(); - }); - } - } - - shouldComponentUpdate(nextProps, nextState) { - const deepProps = { - executableModeChange: ['oldMode', 'newMode'], - symlinkChange: ['oldSymlink', 'newSymlink', 'typechange', 'filePatchStatus'], - }; - - for (const propKey in this.constructor.propTypes) { - const subKeys = deepProps[propKey]; - const oldProp = this.props[propKey]; - const newProp = nextProps[propKey]; - - if (subKeys) { - const oldExists = (oldProp !== null && oldProp !== undefined); - const newExists = (newProp !== null && newProp !== undefined); - - if (oldExists !== newExists) { - return true; - } - - if (!oldExists && !newExists) { - continue; - } - - if (subKeys.some(subKey => this.props[propKey][subKey] !== nextProps[propKey][subKey])) { - return true; - } - } else { - if (oldProp !== newProp) { - return true; - } - } - } - - if (this.state.selection !== nextState.selection) { - return true; - } - - return false; - } - - renderEmptyDiffMessage() { - return ( -
- File has no contents -
- ); - } - - renderLargeDiffMessage() { - const human = bytes.format(this.props.byteCount); - - return ( -
-

- This is a large {human} diff. For performance reasons, it is not rendered by default. -

- -
- ); - } - - renderHunks() { - // Render hunks for symlink change only if 'typechange' (which indicates symlink change AND file content change) - const {symlinkChange} = this.props; - if (symlinkChange && !symlinkChange.typechange) { return null; } - - const selectedHunks = this.state.selection.getSelectedHunks(); - const selectedLines = this.state.selection.getSelectedLines(); - const headHunk = this.state.selection.getHeadHunk(); - const headLine = this.state.selection.getHeadLine(); - const hunkSelectionMode = this.state.selection.getMode() === 'hunk'; - - const unstaged = this.props.stagingStatus === 'unstaged'; - const stageButtonLabelPrefix = unstaged ? 'Stage' : 'Unstage'; - - if (this.props.hunks.length === 0) { - return this.renderEmptyDiffMessage(); - } - - return this.props.hunks.map(hunk => { - const isSelected = selectedHunks.has(hunk); - let stageButtonSuffix = (hunkSelectionMode || !isSelected) ? ' Hunk' : ' Selection'; - if (selectedHunks.size > 1 && selectedHunks.has(hunk)) { - stageButtonSuffix += 's'; - } - const stageButtonLabel = stageButtonLabelPrefix + stageButtonSuffix; - const discardButtonLabel = 'Discard' + stageButtonSuffix; - - return ( - this.mousedownOnHeader(e, hunk)} - mousedownOnLine={this.mousedownOnLine} - mousemoveOnLine={this.mousemoveOnLine} - contextMenuOnItem={this.contextMenuOnItem} - didClickStageButton={() => this.didClickStageButtonForHunk(hunk)} - didClickDiscardButton={() => this.didClickDiscardButtonForHunk(hunk)} - /> - ); - }); - - } - render() { - const unstaged = this.props.stagingStatus === 'unstaged'; + const text = this.props.filePatch.getHunks().map(h => h.toString()).join('\n'); + return (
- - {this.registerCommands()} + className={cx('github-FilePatchView', {'is-staged': !this.isUnstaged(), 'is-unstaged': this.isUnstaged()})} + tabIndex="-1"> -
- - {unstaged ? 'Unstaged Changes for ' : 'Staged Changes for '} - {this.props.filePath} - - {this.renderButtonGroup()} -
+ + + + {this.renderFileHeader()} + + + -
- {this.props.executableModeChange && this.renderExecutableModeChange(unstaged)} - {this.props.symlinkChange && this.renderSymlinkChange(unstaged)} - {this.props.displayLargeDiffMessage ? this.renderLargeDiffMessage() : this.renderHunks()} -
); } - registerCommands() { + renderFileHeader() { return ( -
- - - - - - - - - - - - - - - - - this.props.isPartiallyStaged && this.props.didDiveIntoCorrespondingFilePatch()} - /> - - this.props.hasUndoHistory && this.props.undoLastDiscard()} - /> - {this.props.executableModeChange && - } - {this.props.symlinkChange && - } - - - - this.props.hasUndoHistory && this.props.undoLastDiscard()} - /> - - -
+
+ + {this.isUnstaged() ? 'Unstaged Changes for ' : 'Staged Changes for '} + {this.props.filePatch.getPath()} + + {this.renderButtonGroup()} +
); } renderButtonGroup() { - const unstaged = this.props.stagingStatus === 'unstaged'; + const hasHunks = this.props.filePatch.getHunks().length > 0; return ( - {this.props.hasUndoHistory && unstaged ? ( - - ) : null} - {this.props.isPartiallyStaged || !this.props.hunks.length ? ( - - ) : null } ); } - renderExecutableModeChange(unstaged) { - const {executableModeChange} = this.props; - return ( -
-
-
-

Mode change

-
- -
-
-
- File changed mode - - -
-
-
- ); - } - - renderSymlinkChange(unstaged) { - const {symlinkChange} = this.props; - const {oldSymlink, newSymlink} = symlinkChange; - - if (oldSymlink && !newSymlink) { - return ( -
-
-
-

Symlink deleted

-
- -
-
-
- Symlink - - to {oldSymlink} - - deleted. -
-
-
- ); - } else if (!oldSymlink && newSymlink) { - return ( -
-
-
-

Symlink added

-
- -
-
-
- Symlink - - to {newSymlink} - - created. -
-
-
- ); - } else if (oldSymlink && newSymlink) { - return ( -
-
-
-

Symlink changed

-
- -
-
-
- - from {oldSymlink} - - - to {newSymlink} - -
-
-
- ); - } else { - return new Error('Symlink change detected, but missing symlink paths'); - } - } - - componentWillUnmount() { - this.disposables.dispose(); - } - - contextMenuOnItem(event, hunk, line) { - const resend = () => { - const newEvent = new MouseEvent(event.type, event); - setImmediate(() => event.target.parentNode.dispatchEvent(newEvent)); - }; - - const mode = this.state.selection.getMode(); - if (mode === 'hunk' && !this.state.selection.getSelectedHunks().has(hunk)) { - event.stopPropagation(); - - this.setState(prevState => { - return {selection: prevState.selection.selectHunk(hunk, event.shiftKey)}; - }, resend); - } else if (mode === 'line' && !this.state.selection.getSelectedLines().has(line)) { - event.stopPropagation(); - - this.setState(prevState => { - return {selection: prevState.selection.selectLine(line, event.shiftKey)}; - }, resend); - } - } - - mousedownOnHeader(event, hunk) { - if (event.button !== 0) { return; } - const windows = process.platform === 'win32'; - if (event.ctrlKey && !windows) { return; } // simply open context menu - - this.mouseSelectionInProgress = true; - event.persist && event.persist(); - - this.setState(prevState => { - let selection = prevState.selection; - if (event.metaKey || (event.ctrlKey && windows)) { - if (selection.getMode() === 'hunk') { - selection = selection.addOrSubtractHunkSelection(hunk); - } else { - // TODO: optimize - selection = hunk.getLines().reduce( - (current, line) => current.addOrSubtractLineSelection(line).coalesce(), - selection, - ); - } - } else if (event.shiftKey) { - if (selection.getMode() === 'hunk') { - selection = selection.selectHunk(hunk, true); - } else { - const hunkLines = hunk.getLines(); - const tailIndex = selection.getLineSelectionTailIndex(); - const selectedHunkAfterTail = tailIndex < hunkLines[0].diffLineNumber; - if (selectedHunkAfterTail) { - selection = selection.selectLine(hunkLines[hunkLines.length - 1], true); - } else { - selection = selection.selectLine(hunkLines[0], true); - } - } - } else { - selection = selection.selectHunk(hunk, false); - } - - return {selection}; - }); - } - - mousedownOnLine(event, hunk, line) { - if (event.button !== 0) { return; } - const windows = process.platform === 'win32'; - if (event.ctrlKey && !windows) { return; } // simply open context menu - - this.mouseSelectionInProgress = true; - event.persist && event.persist(); - - this.setState(prevState => { - let selection = prevState.selection; - - if (event.metaKey || (event.ctrlKey && windows)) { - if (selection.getMode() === 'hunk') { - selection = selection.addOrSubtractHunkSelection(hunk); - } else { - selection = selection.addOrSubtractLineSelection(line); - } - } else if (event.shiftKey) { - if (selection.getMode() === 'hunk') { - selection = selection.selectHunk(hunk, true); - } else { - selection = selection.selectLine(line, true); - } - } else if (event.detail === 1) { - selection = selection.selectLine(line, false); - } else if (event.detail === 2) { - selection = selection.selectHunk(hunk, false); - } - - return {selection}; - }); - } - - mousemoveOnLine(event, hunk, line) { - if (!this.mouseSelectionInProgress) { return; } - - this.setState(prevState => { - let selection = null; - if (prevState.selection.getMode() === 'hunk') { - selection = prevState.selection.selectHunk(hunk, true); - } else { - selection = prevState.selection.selectLine(line, true); - } - return {selection}; - }); - } - - mouseup() { - this.mouseSelectionInProgress = false; - this.setState(prevState => { - return {selection: prevState.selection.coalesce()}; - }); - } - - togglePatchSelectionMode() { - this.setState(prevState => ({selection: prevState.selection.toggleMode()})); - } - - getPatchSelectionMode() { - return this.state.selection.getMode(); - } - - getSelectedHunks() { - return this.state.selection.getSelectedHunks(); - } - - getSelectedLines() { - return this.state.selection.getSelectedLines(); - } - - selectNext() { - this.setState(prevState => ({selection: prevState.selection.selectNext()})); - } - - selectNextElement() { - if (this.state.selection.isEmpty() && this.props.didSurfaceFile) { - this.props.didSurfaceFile(); - } else { - this.setState(prevState => ({selection: prevState.selection.jumpToNextHunk()})); - } - } - - selectToNext() { - this.setState(prevState => { - return {selection: prevState.selection.selectNext(true).coalesce()}; - }); - } - - selectPrevious() { - this.setState(prevState => ({selection: prevState.selection.selectPrevious()})); - } - - selectPreviousElement() { - if (this.state.selection.isEmpty() && this.props.didSurfaceFile) { - this.props.didSurfaceFile(); - } else { - this.setState(prevState => ({selection: prevState.selection.jumpToPreviousHunk()})); - } - } - - selectToPrevious() { - this.setState(prevState => { - return {selection: prevState.selection.selectPrevious(true).coalesce()}; - }); - } - - selectFirst() { - this.setState(prevState => ({selection: prevState.selection.selectFirst()})); - } - - selectToFirst() { - this.setState(prevState => ({selection: prevState.selection.selectFirst(true)})); - } - - selectLast() { - this.setState(prevState => ({selection: prevState.selection.selectLast()})); - } - - selectToLast() { - this.setState(prevState => ({selection: prevState.selection.selectLast(true)})); - } - - selectAll() { - return new Promise(resolve => { - this.setState(prevState => ({selection: prevState.selection.selectAll()}), resolve); - }); - } - - getNextHunkUpdatePromise() { - return this.state.selection.getNextUpdatePromise(); - } - - didClickStageButtonForHunk(hunk) { - if (this.state.selection.getSelectedHunks().has(hunk)) { - this.props.attemptLineStageOperation(this.state.selection.getSelectedLines()); - } else { - this.setState(prevState => ({selection: prevState.selection.selectHunk(hunk)}), () => { - this.props.attemptHunkStageOperation(hunk); - }); - } - } - - didClickDiscardButtonForHunk(hunk) { - if (this.state.selection.getSelectedHunks().has(hunk)) { - this.discardSelection(); - } else { - this.setState(prevState => ({selection: prevState.selection.selectHunk(hunk)}), () => { - this.discardSelection(); - }); - } - } - - didConfirm() { - return this.didClickStageButtonForHunk([...this.state.selection.getSelectedHunks()][0]); - } - - didMoveRight() { - if (this.props.didSurfaceFile) { - this.props.didSurfaceFile(); - } - } - - focus() { - this.refElement.get().focus(); - } - - openFile() { - let lineNumber = 0; - const firstSelectedLine = Array.from(this.state.selection.getSelectedLines())[0]; - if (firstSelectedLine && firstSelectedLine.newLineNumber > -1) { - lineNumber = firstSelectedLine.newLineNumber; - } else { - const firstSelectedHunk = Array.from(this.state.selection.getSelectedHunks())[0]; - lineNumber = firstSelectedHunk ? firstSelectedHunk.getNewStartRow() : 0; - } - return this.props.openCurrentFile({lineNumber}); - } - - stageOrUnstageAll() { - this.props.attemptFileStageOperation(); - } - - stageOrUnstageModeChange() { - this.props.attemptModeStageOperation(); - } - - stageOrUnstageSymlinkChange() { - this.props.attemptSymlinkStageOperation(); - } - - discardSelection() { - const selectedLines = this.state.selection.getSelectedLines(); - return selectedLines.size ? this.props.discardLines(selectedLines) : null; - } - - goToDiffLine(lineNumber) { - this.setState(prevState => ({selection: prevState.selection.goToDiffLine(lineNumber)})); + isUnstaged() { + return this.props.stagingStatus === 'unstaged'; } } diff --git a/lib/views/file-patch-view.old.js b/lib/views/file-patch-view.old.js new file mode 100644 index 0000000000..e8067025dc --- /dev/null +++ b/lib/views/file-patch-view.old.js @@ -0,0 +1,716 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {CompositeDisposable, Disposable} from 'event-kit'; +import cx from 'classnames'; +import bytes from 'bytes'; + +import HunkView from './hunk-view'; +import SimpleTooltip from '../atom/simple-tooltip'; +import Commands, {Command} from '../atom/commands'; +import FilePatchSelection from '../models/file-patch-selection'; +import Switchboard from '../switchboard'; +import RefHolder from '../models/ref-holder'; +import {autobind} from '../helpers'; + +const executableText = { + 100644: 'non executable 100644', + 100755: 'executable 100755', +}; + +export default class FilePatchView extends React.Component { + static propTypes = { + commandRegistry: PropTypes.object.isRequired, + tooltips: PropTypes.object.isRequired, + filePath: PropTypes.string.isRequired, + hunks: PropTypes.arrayOf(PropTypes.object).isRequired, + executableModeChange: PropTypes.shape({ + oldMode: PropTypes.string.isRequired, + newMode: PropTypes.string.isRequired, + }), + symlinkChange: PropTypes.shape({ + oldSymlink: PropTypes.string, + newSymlink: PropTypes.string, + typechange: PropTypes.bool, + filePatchStatus: PropTypes.string, + }), + stagingStatus: PropTypes.oneOf(['unstaged', 'staged']).isRequired, + isPartiallyStaged: PropTypes.bool.isRequired, + hasUndoHistory: PropTypes.bool.isRequired, + attemptLineStageOperation: PropTypes.func.isRequired, + attemptHunkStageOperation: PropTypes.func.isRequired, + attemptFileStageOperation: PropTypes.func.isRequired, + attemptModeStageOperation: PropTypes.func.isRequired, + attemptSymlinkStageOperation: PropTypes.func.isRequired, + discardLines: PropTypes.func.isRequired, + undoLastDiscard: PropTypes.func.isRequired, + openCurrentFile: PropTypes.func.isRequired, + didSurfaceFile: PropTypes.func.isRequired, + didDiveIntoCorrespondingFilePatch: PropTypes.func.isRequired, + switchboard: PropTypes.instanceOf(Switchboard), + displayLargeDiffMessage: PropTypes.bool, + byteCount: PropTypes.number, + handleShowDiffClick: PropTypes.func.isRequired, + } + + static defaultProps = { + switchboard: new Switchboard(), + } + + constructor(props, context) { + super(props, context); + autobind( + this, + 'registerCommands', 'renderButtonGroup', 'renderExecutableModeChange', 'renderSymlinkChange', 'contextMenuOnItem', + 'mousedownOnLine', 'mousemoveOnLine', 'mouseup', 'togglePatchSelectionMode', 'selectNext', 'selectNextElement', + 'selectToNext', 'selectPrevious', 'selectPreviousElement', 'selectToPrevious', 'selectFirst', 'selectToFirst', + 'selectLast', 'selectToLast', 'selectAll', 'didConfirm', 'didMoveRight', 'focus', 'openFile', 'stageOrUnstageAll', + 'stageOrUnstageModeChange', 'stageOrUnstageSymlinkChange', 'discardSelection', + ); + + this.mouseSelectionInProgress = false; + this.disposables = new CompositeDisposable(); + + this.refElement = new RefHolder(); + + this.state = { + selection: new FilePatchSelection(this.props.hunks), + }; + } + + componentDidMount() { + window.addEventListener('mouseup', this.mouseup); + this.disposables.add(new Disposable(() => window.removeEventListener('mouseup', this.mouseup))); + } + + // eslint-disable-next-line camelcase + UNSAFE_componentWillReceiveProps(nextProps) { + const hunksChanged = this.props.hunks.length !== nextProps.hunks.length || + this.props.hunks.some((hunk, index) => hunk !== nextProps.hunks[index]); + + if (hunksChanged) { + this.setState(prevState => { + return { + selection: prevState.selection.updateHunks(nextProps.hunks), + }; + }, () => { + nextProps.switchboard.didChangePatch(); + }); + } + } + + shouldComponentUpdate(nextProps, nextState) { + const deepProps = { + executableModeChange: ['oldMode', 'newMode'], + symlinkChange: ['oldSymlink', 'newSymlink', 'typechange', 'filePatchStatus'], + }; + + for (const propKey in this.constructor.propTypes) { + const subKeys = deepProps[propKey]; + const oldProp = this.props[propKey]; + const newProp = nextProps[propKey]; + + if (subKeys) { + const oldExists = (oldProp !== null && oldProp !== undefined); + const newExists = (newProp !== null && newProp !== undefined); + + if (oldExists !== newExists) { + return true; + } + + if (!oldExists && !newExists) { + continue; + } + + if (subKeys.some(subKey => this.props[propKey][subKey] !== nextProps[propKey][subKey])) { + return true; + } + } else { + if (oldProp !== newProp) { + return true; + } + } + } + + if (this.state.selection !== nextState.selection) { + return true; + } + + return false; + } + + renderEmptyDiffMessage() { + return ( +
+ File has no contents +
+ ); + } + + renderLargeDiffMessage() { + const human = bytes.format(this.props.byteCount); + + return ( +
+

+ This is a large {human} diff. For performance reasons, it is not rendered by default. +

+ +
+ ); + } + + renderHunks() { + // Render hunks for symlink change only if 'typechange' (which indicates symlink change AND file content change) + const {symlinkChange} = this.props; + if (symlinkChange && !symlinkChange.typechange) { return null; } + + const selectedHunks = this.state.selection.getSelectedHunks(); + const selectedLines = this.state.selection.getSelectedLines(); + const headHunk = this.state.selection.getHeadHunk(); + const headLine = this.state.selection.getHeadLine(); + const hunkSelectionMode = this.state.selection.getMode() === 'hunk'; + + const unstaged = this.props.stagingStatus === 'unstaged'; + const stageButtonLabelPrefix = unstaged ? 'Stage' : 'Unstage'; + + if (this.props.hunks.length === 0) { + return this.renderEmptyDiffMessage(); + } + + return this.props.hunks.map(hunk => { + const isSelected = selectedHunks.has(hunk); + let stageButtonSuffix = (hunkSelectionMode || !isSelected) ? ' Hunk' : ' Selection'; + if (selectedHunks.size > 1 && selectedHunks.has(hunk)) { + stageButtonSuffix += 's'; + } + const stageButtonLabel = stageButtonLabelPrefix + stageButtonSuffix; + const discardButtonLabel = 'Discard' + stageButtonSuffix; + + return ( + this.mousedownOnHeader(e, hunk)} + mousedownOnLine={this.mousedownOnLine} + mousemoveOnLine={this.mousemoveOnLine} + contextMenuOnItem={this.contextMenuOnItem} + didClickStageButton={() => this.didClickStageButtonForHunk(hunk)} + didClickDiscardButton={() => this.didClickDiscardButtonForHunk(hunk)} + /> + ); + }); + + } + + render() { + const unstaged = this.props.stagingStatus === 'unstaged'; + return ( +
+ + {this.registerCommands()} + +
+ + {unstaged ? 'Unstaged Changes for ' : 'Staged Changes for '} + {this.props.filePath} + + {this.renderButtonGroup()} +
+ +
+ {this.props.executableModeChange && this.renderExecutableModeChange(unstaged)} + {this.props.symlinkChange && this.renderSymlinkChange(unstaged)} + {this.props.displayLargeDiffMessage ? this.renderLargeDiffMessage() : this.renderHunks()} +
+
+ ); + } + + registerCommands() { + return ( +
+ + + + + + + + + + + + + + + + + this.props.isPartiallyStaged && this.props.didDiveIntoCorrespondingFilePatch()} + /> + + this.props.hasUndoHistory && this.props.undoLastDiscard()} + /> + {this.props.executableModeChange && + } + {this.props.symlinkChange && + } + + + + this.props.hasUndoHistory && this.props.undoLastDiscard()} + /> + + +
+ ); + } + + renderButtonGroup() { + const unstaged = this.props.stagingStatus === 'unstaged'; + + return ( + + {this.props.hasUndoHistory && unstaged ? ( + + ) : null} + {this.props.isPartiallyStaged || !this.props.hunks.length ? ( + + + ) : null } + + ); + } + + renderExecutableModeChange(unstaged) { + const {executableModeChange} = this.props; + return ( +
+
+
+

Mode change

+
+ +
+
+
+ File changed mode + + +
+
+
+ ); + } + + renderSymlinkChange(unstaged) { + const {symlinkChange} = this.props; + const {oldSymlink, newSymlink} = symlinkChange; + + if (oldSymlink && !newSymlink) { + return ( +
+
+
+

Symlink deleted

+
+ +
+
+
+ Symlink + + to {oldSymlink} + + deleted. +
+
+
+ ); + } else if (!oldSymlink && newSymlink) { + return ( +
+
+
+

Symlink added

+
+ +
+
+
+ Symlink + + to {newSymlink} + + created. +
+
+
+ ); + } else if (oldSymlink && newSymlink) { + return ( +
+
+
+

Symlink changed

+
+ +
+
+
+ + from {oldSymlink} + + + to {newSymlink} + +
+
+
+ ); + } else { + return new Error('Symlink change detected, but missing symlink paths'); + } + } + + componentWillUnmount() { + this.disposables.dispose(); + } + + contextMenuOnItem(event, hunk, line) { + const resend = () => { + const newEvent = new MouseEvent(event.type, event); + setImmediate(() => event.target.parentNode.dispatchEvent(newEvent)); + }; + + const mode = this.state.selection.getMode(); + if (mode === 'hunk' && !this.state.selection.getSelectedHunks().has(hunk)) { + event.stopPropagation(); + + this.setState(prevState => { + return {selection: prevState.selection.selectHunk(hunk, event.shiftKey)}; + }, resend); + } else if (mode === 'line' && !this.state.selection.getSelectedLines().has(line)) { + event.stopPropagation(); + + this.setState(prevState => { + return {selection: prevState.selection.selectLine(line, event.shiftKey)}; + }, resend); + } + } + + mousedownOnHeader(event, hunk) { + if (event.button !== 0) { return; } + const windows = process.platform === 'win32'; + if (event.ctrlKey && !windows) { return; } // simply open context menu + + this.mouseSelectionInProgress = true; + event.persist && event.persist(); + + this.setState(prevState => { + let selection = prevState.selection; + if (event.metaKey || (event.ctrlKey && windows)) { + if (selection.getMode() === 'hunk') { + selection = selection.addOrSubtractHunkSelection(hunk); + } else { + // TODO: optimize + selection = hunk.getLines().reduce( + (current, line) => current.addOrSubtractLineSelection(line).coalesce(), + selection, + ); + } + } else if (event.shiftKey) { + if (selection.getMode() === 'hunk') { + selection = selection.selectHunk(hunk, true); + } else { + const hunkLines = hunk.getLines(); + const tailIndex = selection.getLineSelectionTailIndex(); + const selectedHunkAfterTail = tailIndex < hunkLines[0].diffLineNumber; + if (selectedHunkAfterTail) { + selection = selection.selectLine(hunkLines[hunkLines.length - 1], true); + } else { + selection = selection.selectLine(hunkLines[0], true); + } + } + } else { + selection = selection.selectHunk(hunk, false); + } + + return {selection}; + }); + } + + mousedownOnLine(event, hunk, line) { + if (event.button !== 0) { return; } + const windows = process.platform === 'win32'; + if (event.ctrlKey && !windows) { return; } // simply open context menu + + this.mouseSelectionInProgress = true; + event.persist && event.persist(); + + this.setState(prevState => { + let selection = prevState.selection; + + if (event.metaKey || (event.ctrlKey && windows)) { + if (selection.getMode() === 'hunk') { + selection = selection.addOrSubtractHunkSelection(hunk); + } else { + selection = selection.addOrSubtractLineSelection(line); + } + } else if (event.shiftKey) { + if (selection.getMode() === 'hunk') { + selection = selection.selectHunk(hunk, true); + } else { + selection = selection.selectLine(line, true); + } + } else if (event.detail === 1) { + selection = selection.selectLine(line, false); + } else if (event.detail === 2) { + selection = selection.selectHunk(hunk, false); + } + + return {selection}; + }); + } + + mousemoveOnLine(event, hunk, line) { + if (!this.mouseSelectionInProgress) { return; } + + this.setState(prevState => { + let selection = null; + if (prevState.selection.getMode() === 'hunk') { + selection = prevState.selection.selectHunk(hunk, true); + } else { + selection = prevState.selection.selectLine(line, true); + } + return {selection}; + }); + } + + mouseup() { + this.mouseSelectionInProgress = false; + this.setState(prevState => { + return {selection: prevState.selection.coalesce()}; + }); + } + + togglePatchSelectionMode() { + this.setState(prevState => ({selection: prevState.selection.toggleMode()})); + } + + getPatchSelectionMode() { + return this.state.selection.getMode(); + } + + getSelectedHunks() { + return this.state.selection.getSelectedHunks(); + } + + getSelectedLines() { + return this.state.selection.getSelectedLines(); + } + + selectNext() { + this.setState(prevState => ({selection: prevState.selection.selectNext()})); + } + + selectNextElement() { + if (this.state.selection.isEmpty() && this.props.didSurfaceFile) { + this.props.didSurfaceFile(); + } else { + this.setState(prevState => ({selection: prevState.selection.jumpToNextHunk()})); + } + } + + selectToNext() { + this.setState(prevState => { + return {selection: prevState.selection.selectNext(true).coalesce()}; + }); + } + + selectPrevious() { + this.setState(prevState => ({selection: prevState.selection.selectPrevious()})); + } + + selectPreviousElement() { + if (this.state.selection.isEmpty() && this.props.didSurfaceFile) { + this.props.didSurfaceFile(); + } else { + this.setState(prevState => ({selection: prevState.selection.jumpToPreviousHunk()})); + } + } + + selectToPrevious() { + this.setState(prevState => { + return {selection: prevState.selection.selectPrevious(true).coalesce()}; + }); + } + + selectFirst() { + this.setState(prevState => ({selection: prevState.selection.selectFirst()})); + } + + selectToFirst() { + this.setState(prevState => ({selection: prevState.selection.selectFirst(true)})); + } + + selectLast() { + this.setState(prevState => ({selection: prevState.selection.selectLast()})); + } + + selectToLast() { + this.setState(prevState => ({selection: prevState.selection.selectLast(true)})); + } + + selectAll() { + return new Promise(resolve => { + this.setState(prevState => ({selection: prevState.selection.selectAll()}), resolve); + }); + } + + getNextHunkUpdatePromise() { + return this.state.selection.getNextUpdatePromise(); + } + + didClickStageButtonForHunk(hunk) { + if (this.state.selection.getSelectedHunks().has(hunk)) { + this.props.attemptLineStageOperation(this.state.selection.getSelectedLines()); + } else { + this.setState(prevState => ({selection: prevState.selection.selectHunk(hunk)}), () => { + this.props.attemptHunkStageOperation(hunk); + }); + } + } + + didClickDiscardButtonForHunk(hunk) { + if (this.state.selection.getSelectedHunks().has(hunk)) { + this.discardSelection(); + } else { + this.setState(prevState => ({selection: prevState.selection.selectHunk(hunk)}), () => { + this.discardSelection(); + }); + } + } + + didConfirm() { + return this.didClickStageButtonForHunk([...this.state.selection.getSelectedHunks()][0]); + } + + didMoveRight() { + if (this.props.didSurfaceFile) { + this.props.didSurfaceFile(); + } + } + + focus() { + this.refElement.get().focus(); + } + + openFile() { + let lineNumber = 0; + const firstSelectedLine = Array.from(this.state.selection.getSelectedLines())[0]; + if (firstSelectedLine && firstSelectedLine.newLineNumber > -1) { + lineNumber = firstSelectedLine.newLineNumber; + } else { + const firstSelectedHunk = Array.from(this.state.selection.getSelectedHunks())[0]; + lineNumber = firstSelectedHunk ? firstSelectedHunk.getNewStartRow() : 0; + } + return this.props.openCurrentFile({lineNumber}); + } + + stageOrUnstageAll() { + this.props.attemptFileStageOperation(); + } + + stageOrUnstageModeChange() { + this.props.attemptModeStageOperation(); + } + + stageOrUnstageSymlinkChange() { + this.props.attemptSymlinkStageOperation(); + } + + discardSelection() { + const selectedLines = this.state.selection.getSelectedLines(); + return selectedLines.size ? this.props.discardLines(selectedLines) : null; + } + + goToDiffLine(lineNumber) { + this.setState(prevState => ({selection: prevState.selection.goToDiffLine(lineNumber)})); + } +} From c8d5640c217a4ec71435cb6f6ef212d4b32ee8e8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Jun 2018 13:17:23 -0400 Subject: [PATCH 0004/4053] Convert a FilePatch to a PresentedFilePatch for rendering within a TextEditor --- lib/models/file-patch.js | 5 ++ lib/models/presented-file-patch.js | 59 +++++++++++++++++++++ test/models/file-patch.test.js | 83 ++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 lib/models/presented-file-patch.js diff --git a/lib/models/file-patch.js b/lib/models/file-patch.js index 0e0a91cf0f..41a6976bf8 100644 --- a/lib/models/file-patch.js +++ b/lib/models/file-patch.js @@ -1,5 +1,6 @@ import Hunk from './hunk'; import {toGitPathSep} from '../helpers'; +import PresentedFilePatch from './presented-file-patch'; class File { static empty() { @@ -322,6 +323,10 @@ export default class FilePatch { return hunks; } + present() { + return new PresentedFilePatch(this); + } + toString() { if (this.hasTypechange()) { const left = this.clone({ diff --git a/lib/models/presented-file-patch.js b/lib/models/presented-file-patch.js new file mode 100644 index 0000000000..aef7b69b34 --- /dev/null +++ b/lib/models/presented-file-patch.js @@ -0,0 +1,59 @@ +import {Point} from 'atom'; + +export default class PresentedFilePatch { + constructor(filePatch) { + this.filePatch = filePatch; + + this.hunkStartPositions = []; + this.bufferPositions = { + unchanged: [], + added: [], + deleted: [], + nonewline: [], + }; + + let bufferLine = 0; + this.text = filePatch.getHunks().reduce((str, hunk) => { + this.hunkStartPositions.push(new Point(bufferLine, 0)); + + return hunk.getLines().reduce((hunkStr, line) => { + hunkStr += line.getText() + '\n'; + + this.bufferPositions[line.getStatus()].push( + new Point(bufferLine, 0), + ); + + bufferLine++; + return hunkStr; + }, str); + }, ''); + } + + getFilePatch() { + return this.filePatch; + } + + getText() { + return this.text; + } + + getHunkStartPositions() { + return this.hunkStartPositions; + } + + getUnchangedBufferPositions() { + return this.bufferPositions.unchanged; + } + + getAddedBufferPositions() { + return this.bufferPositions.added; + } + + getDeletedBufferPositions() { + return this.bufferPositions.deleted; + } + + getNoNewlineBufferPositions() { + return this.bufferPositions.nonewline; + } +} diff --git a/test/models/file-patch.test.js b/test/models/file-patch.test.js index e0e7aab8c2..e78e625a73 100644 --- a/test/models/file-patch.test.js +++ b/test/models/file-patch.test.js @@ -387,4 +387,87 @@ describe('FilePatch', function() { assert.strictEqual(filePatch.getByteSize(), 36); }); + + describe('present()', function() { + let presented; + + beforeEach(function() { + const patch = createFilePatch('a.txt', 'a.txt', 'modified', [ + new Hunk(1, 1, 1, 3, '@@ -1,1 +2,2', [ + new HunkLine('line-1', 'added', -1, 1), + new HunkLine('line-2', 'added', -1, 2), + new HunkLine('line-3', 'unchanged', 1, 3), + ]), + new Hunk(5, 7, 5, 4, '@@ -3,3 +4,4', [ + new HunkLine('line-4', 'unchanged', 5, 7), + new HunkLine('line-5', 'deleted', 6, -1), + new HunkLine('line-6', 'deleted', 7, -1), + new HunkLine('line-7', 'added', -1, 8), + new HunkLine('line-8', 'added', -1, 9), + new HunkLine('line-9', 'added', -1, 10), + new HunkLine('line-10', 'deleted', 8, -1), + new HunkLine('line-11', 'deleted', 9, -1), + new HunkLine('line-12', 'unchanged', 5, 7), + ]), + new Hunk(20, 19, 2, 2, '@@ -5,5 +6,6', [ + new HunkLine('line-13', 'deleted', 20, -1), + new HunkLine('line-14', 'added', -1, 19), + new HunkLine('line-15', 'unchanged', 21, 20), + new HunkLine('No newline at end of file', 'nonewline', -1, -1), + ]), + ]); + + presented = patch.present(); + }); + + function assertPositions(actualPositions, expectedPositions) { + assert.lengthOf(actualPositions, expectedPositions.length); + for (let i = 0; i < expectedPositions.length; i++) { + const actual = actualPositions[i]; + const expected = expectedPositions[i]; + + assert.isTrue(actual.isEqual(expected), + `range ${i}: ${actual.toString()} does not equal [${expected.map(e => e.toString()).join(', ')}]`); + } + } + + it('unifies hunks into a continuous, unadorned string of text', function() { + const actualText = presented.getText(); + const expectedText = + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map(num => `line-${num}\n`).join('') + + 'No newline at end of file\n'; + + assert.strictEqual(actualText, expectedText); + }); + + it("returns the buffer positions corresponding to each hunk's beginning", function() { + assertPositions(presented.getHunkStartPositions(), [ + [0, 0], [3, 0], [12, 0], + ]); + }); + + it('returns the buffer positions corresponding to unchanged lines', function() { + assertPositions(presented.getUnchangedBufferPositions(), [ + [2, 0], [3, 0], [11, 0], [14, 0], + ]); + }); + + it('returns the buffer positions corresponding to added lines', function() { + assertPositions(presented.getAddedBufferPositions(), [ + [0, 0], [1, 0], [6, 0], [7, 0], [8, 0], [13, 0], + ]); + }); + + it('returns the buffer positions corresponding to deleted lines', function() { + assertPositions(presented.getDeletedBufferPositions(), [ + [4, 0], [5, 0], [9, 0], [10, 0], [12, 0], + ]); + }); + + it('returns the buffer position of a "no newline" trailer', function() { + assertPositions(presented.getNoNewlineBufferPositions(), [ + [15, 0], + ]); + }); + }); }); From 462f266fac3736bc23e0b3727e1d4134bf970795 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Jun 2018 13:18:05 -0400 Subject: [PATCH 0005/4053] Change StagingView tests to check for FilePatchItems --- lib/views/staging-view.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 0409f73dad..1b563d77d6 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -12,7 +12,7 @@ import ObserveModel from './observe-model'; import MergeConflictListItemView from './merge-conflict-list-item-view'; import CompositeListSelection from '../models/composite-list-selection'; import ResolutionProgress from '../models/conflicts/resolution-progress'; -import FilePatchController from '../controllers/file-patch-controller'; +import FilePatchItem from '../items/file-patch-item'; import Commands, {Command} from '../atom/commands'; import {autobind} from '../helpers'; @@ -541,7 +541,7 @@ export default class StagingView extends React.Component { return; } - const isFilePatchController = realItem instanceof FilePatchController; + const isFilePatchController = realItem instanceof FilePatchItem; const isMatch = realItem.getWorkingDirectory && item.getWorkingDirectory() === this.props.workingDirectoryPath; if (isFilePatchController && isMatch) { @@ -657,7 +657,7 @@ export default class StagingView extends React.Component { const activePane = this.props.workspace.getCenter().getActivePane(); const activePendingItem = activePane.getPendingItem(); const activePaneHasPendingFilePatchItem = activePendingItem && activePendingItem.getRealItem && - activePendingItem.getRealItem() instanceof FilePatchController; + activePendingItem.getRealItem() instanceof FilePatchItem; if (activePaneHasPendingFilePatchItem) { await this.showFilePatchItem(selectedItem.filePath, this.state.selection.getActiveListKey(), { activate: false, @@ -676,7 +676,7 @@ export default class StagingView extends React.Component { const pendingItem = pane.getPendingItem(); if (!pendingItem || !pendingItem.getRealItem) { return false; } const realItem = pendingItem.getRealItem(); - const isDiffViewItem = realItem instanceof FilePatchController; + const isDiffViewItem = realItem instanceof FilePatchItem; // We only want to update pending diff views for currently active repo const isInActiveRepo = realItem.getWorkingDirectory() === this.props.workingDirectoryPath; const isStale = !this.changedFileExists(realItem.getFilePath(), realItem.getStagingStatus()); @@ -691,7 +691,7 @@ export default class StagingView extends React.Component { } async showFilePatchItem(filePath, stagingStatus, {activate, pane} = {activate: false}) { - const uri = FilePatchController.buildURI(filePath, this.props.workingDirectoryPath, stagingStatus); + const uri = FilePatchItem.buildURI(filePath, this.props.workingDirectoryPath, stagingStatus); const filePatchItem = await this.props.workspace.open( uri, {pending: true, activatePane: activate, activateItem: activate, pane}, ); From 0e3e7d39b5768d23a774cfe7193804c08bda3896 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Jun 2018 13:18:24 -0400 Subject: [PATCH 0006/4053] Use a PresentedFilePatch to render --- lib/views/file-patch-view.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index f774c89437..f6244e056a 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -21,18 +21,27 @@ export default class FilePatchView extends React.Component { this.state = { selection: new FilePatchSelection(this.props.filePatch.getHunks()), + presentedFilePatch: this.props.filePatch.present(), }; } - render() { - const text = this.props.filePatch.getHunks().map(h => h.toString()).join('\n'); + static getDerivedStateFromProps(props, state) { + if (props.filePatch !== state.presentedFilePatch.getFilePatch()) { + return { + presentedFilePatch: props.filePatch.present(), + }; + } + + return null; + } + render() { return (
- + {this.renderFileHeader()} From d6189565cd6a618d65a9fb51c2608efa508f146d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Jun 2018 14:53:55 -0400 Subject: [PATCH 0007/4053] FilePatchItem tests --- lib/items/file-patch-item.js | 25 +++---- lib/prop-types.js | 4 ++ test/items/file-patch-item.test.js | 102 +++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 test/items/file-patch-item.test.js diff --git a/lib/items/file-patch-item.js b/lib/items/file-patch-item.js index 1c02262dac..3ab27842fc 100644 --- a/lib/items/file-patch-item.js +++ b/lib/items/file-patch-item.js @@ -2,23 +2,24 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Emitter} from 'event-kit'; +import {WorkdirContextPoolPropType} from '../prop-types'; import FilePatchContainer from '../containers/file-patch-container'; export default class FilePatchItem extends React.Component { static propTypes = { - repository: PropTypes.object.isRequired, - stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), - relPath: PropTypes.string.isRequired, + workdirContextPool: WorkdirContextPoolPropType.isRequired, - tooltips: PropTypes.object.isRequired, + relPath: PropTypes.string.isRequired, + workingDirectory: PropTypes.string.isRequired, + stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), } - static uriPattern = 'atom-github://file-patch/{relPath...}?workdir={workdir}&stagingStatus={stagingStatus}' + static uriPattern = 'atom-github://file-patch/{relPath...}?workdir={workingDirectory}&stagingStatus={stagingStatus}' - static buildURI(relPath, workdir, stagingStatus) { + static buildURI(relPath, workingDirectory, stagingStatus) { return 'atom-github://file-patch/' + relPath + - `?workdir=${encodeURIComponent(workdir)}` + + `?workdir=${encodeURIComponent(workingDirectory)}` + `&stagingStatus=${encodeURIComponent(stagingStatus)}`; } @@ -60,12 +61,12 @@ export default class FilePatchItem extends React.Component { } render() { + const repository = this.props.workdirContextPool.getContext(this.props.workingDirectory).getRepository(); + return ( ); } @@ -86,6 +87,6 @@ export default class FilePatchItem extends React.Component { } getWorkingDirectory() { - return this.props.repository.getWorkingDirectoryPath(); + return this.props.workingDirectory; } } diff --git a/lib/prop-types.js b/lib/prop-types.js index 2d17a1994a..31425b7db5 100644 --- a/lib/prop-types.js +++ b/lib/prop-types.js @@ -10,6 +10,10 @@ export const DOMNodePropType = (props, propName, componentName) => { } }; +export const WorkdirContextPoolPropType = PropTypes.shape({ + getContext: PropTypes.func.isRequired, +}); + export const RemotePropType = PropTypes.shape({ getName: PropTypes.func.isRequired, getUrl: PropTypes.func.isRequired, diff --git a/test/items/file-patch-item.test.js b/test/items/file-patch-item.test.js new file mode 100644 index 0000000000..90a0124db5 --- /dev/null +++ b/test/items/file-patch-item.test.js @@ -0,0 +1,102 @@ +import path from 'path'; +import React from 'react'; +import {mount} from 'enzyme'; + +import PaneItem from '../../lib/atom/pane-item'; +import FilePatchItem from '../../lib/items/file-patch-item'; +import WorkdirContextPool from '../../lib/models/workdir-context-pool'; +import {cloneRepository, buildRepository} from '../helpers'; + +describe('FilePatchItem', function() { + let atomEnv, repository, pool; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + + const workdirPath = await cloneRepository(); + repository = await buildRepository(workdirPath); + + pool = new WorkdirContextPool({ + workspace: atomEnv.workspace, + }); + repository = pool.add(workdirPath).getRepository(); + }); + + afterEach(function() { + atomEnv.destroy(); + pool.clear(); + }); + + function buildPaneApp(overrideProps = {}) { + const props = { + workdirContextPool: pool, + tooltips: atomEnv.tooltips, + ...overrideProps, + }; + + return ( + + {({itemHolder, params}) => { + return ( + + ); + }} + + ); + } + + function open(wrapper, options = {}) { + const opts = { + relPath: 'a.txt', + workingDirectory: repository.getWorkingDirectoryPath(), + stagingStatus: 'unstaged', + ...options, + }; + const uri = FilePatchItem.buildURI(opts.relPath, opts.workingDirectory, opts.stagingStatus); + return atomEnv.workspace.open(uri); + } + + it('locates the repository from the context pool', async function() { + const wrapper = mount(buildPaneApp()); + await open(wrapper); + + assert.strictEqual(wrapper.update().find('FilePatchContainer').prop('repository'), repository); + }); + + it('passes an absent repository if the working directory is unrecognized', async function() { + const wrapper = mount(buildPaneApp()); + await open(wrapper, {workingDirectory: '/nope'}); + + assert.isTrue(wrapper.update().find('FilePatchContainer').prop('repository').isAbsent()); + }); + + it('passes other props to the container', async function() { + const other = Symbol('other'); + const wrapper = mount(buildPaneApp({other})); + await open(wrapper); + + assert.strictEqual(wrapper.update().find('FilePatchContainer').prop('other'), other); + }); + + describe('getTitle()', function() { + it('renders an unstaged title', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(wrapper, {stagingStatus: 'unstaged'}); + + assert.strictEqual(item.getTitle(), 'Unstaged Changes: a.txt'); + }); + + it('renders a staged title', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(wrapper, {stagingStatus: 'staged'}); + + assert.strictEqual(item.getTitle(), 'Staged Changes: a.txt'); + }); + }); +}); From 46a67c57d2d7e5cdb08ecc0b6d997208dbb96b4d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Jun 2018 14:54:28 -0400 Subject: [PATCH 0008/4053] Return null for an empty patch for now --- lib/containers/file-patch-container.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index 9e5d5a4eee..3d7b31d89d 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -41,6 +41,10 @@ export default class FilePatchContainer extends React.Component { return null; } + if (data.filePatch === null) { + return null; + } + return ( Date: Wed, 6 Jun 2018 14:56:42 -0400 Subject: [PATCH 0009/4053] Repository is created from the context pool --- test/items/file-patch-item.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/items/file-patch-item.test.js b/test/items/file-patch-item.test.js index 90a0124db5..d588233824 100644 --- a/test/items/file-patch-item.test.js +++ b/test/items/file-patch-item.test.js @@ -5,7 +5,7 @@ import {mount} from 'enzyme'; import PaneItem from '../../lib/atom/pane-item'; import FilePatchItem from '../../lib/items/file-patch-item'; import WorkdirContextPool from '../../lib/models/workdir-context-pool'; -import {cloneRepository, buildRepository} from '../helpers'; +import {cloneRepository} from '../helpers'; describe('FilePatchItem', function() { let atomEnv, repository, pool; @@ -14,7 +14,6 @@ describe('FilePatchItem', function() { atomEnv = global.buildAtomEnvironment(); const workdirPath = await cloneRepository(); - repository = await buildRepository(workdirPath); pool = new WorkdirContextPool({ workspace: atomEnv.workspace, From b6b92f937424b0ae48771cb33ecd07d826677c60 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Jun 2018 16:05:34 -0400 Subject: [PATCH 0010/4053] FilePatchContainer tests --- lib/containers/file-patch-container.js | 16 +++-- test/containers/file-patch-container.test.js | 65 ++++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 test/containers/file-patch-container.test.js diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index 3d7b31d89d..5375d24f72 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -4,6 +4,7 @@ import yubikiri from 'yubikiri'; import {autobind} from '../helpers'; import ObserveModel from '../views/observe-model'; +import LoadingView from '../views/loading-view'; import FilePatchController from '../controllers/file-patch-controller'; export default class FilePatchContainer extends React.Component { @@ -38,20 +39,27 @@ export default class FilePatchContainer extends React.Component { renderWithData(data) { if (data === null) { - return null; + return ; } if (data.filePatch === null) { - return null; + return this.renderEmptyPatchMessage(); } return ( ); } + + renderEmptyPatchMessage() { + return ( +
+ No changes to display +
+ ); + } } diff --git a/test/containers/file-patch-container.test.js b/test/containers/file-patch-container.test.js new file mode 100644 index 0000000000..2f01eb3772 --- /dev/null +++ b/test/containers/file-patch-container.test.js @@ -0,0 +1,65 @@ +import path from 'path'; +import fs from 'fs-extra'; +import React from 'react'; +import {mount} from 'enzyme'; + +import FilePatchContainer from '../../lib/containers/file-patch-container'; +import {cloneRepository, buildRepository} from '../helpers'; + +describe('FilePatchContainer', function() { + let atomEnv, repository; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + + const workdirPath = await cloneRepository(); + repository = await buildRepository(workdirPath); + sinon.spy(repository, 'getFilePatchForPath'); + sinon.spy(repository, 'isPartiallyStaged'); + + // a.txt: unstaged changes + await fs.writeFile(path.join(workdirPath, 'a.txt'), 'changed\n'); + + // b.txt: staged changes + await fs.writeFile(path.join(workdirPath, 'b.txt'), 'changed\n'); + await repository.stageFiles(['b.txt']); + + // c.txt: untouched + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(overrideProps = {}) { + const props = { + repository, + stagingStatus: 'unstaged', + relPath: 'a.txt', + ...overrideProps, + }; + + return ; + } + + it('renders a loading spinner before file patch data arrives', function() { + const wrapper = mount(buildApp()); + assert.isTrue(wrapper.find('LoadingView').exists()); + }); + + it('renders a message for an empty patch', async function() { + const wrapper = mount(buildApp({relPath: 'c.txt'})); + await assert.async.isTrue(wrapper.update().find('span.icon-info').exists()); + }); + + it('renders a FilePatchView', async function() { + const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + await assert.async.isTrue(wrapper.update().find('FilePatchView').exists()); + }); + + it('passes unrecognized props to the FilePatchView', async function() { + const extra = Symbol('extra'); + const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged', extra})); + await assert.async.strictEqual(wrapper.update().find('FilePatchView').prop('extra'), extra); + }); +}); From 3dd99de35a7d7aaf9d480d4b5d0649b6bf644acf Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Jun 2018 16:16:40 -0400 Subject: [PATCH 0011/4053] Stub test for the stub FilePatchController --- .../controllers/file-patch-controller.test.js | 820 +----------------- .../file-patch-controller.test.old.js | 811 +++++++++++++++++ 2 files changed, 838 insertions(+), 793 deletions(-) create mode 100644 test/controllers/file-patch-controller.test.old.js diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index a7b075e8ec..ad18b3cb4d 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -1,811 +1,45 @@ -import React from 'react'; -import {shallow, mount} from 'enzyme'; -import until from 'test-until'; - -import fs from 'fs'; import path from 'path'; +import fs from 'fs'; +import React from 'react'; +import {mount} from 'enzyme'; -import {cloneRepository, buildRepository} from '../helpers'; -import FilePatch from '../../lib/models/file-patch'; import FilePatchController from '../../lib/controllers/file-patch-controller'; -import Hunk from '../../lib/models/hunk'; -import HunkLine from '../../lib/models/hunk-line'; -import ResolutionProgress from '../../lib/models/conflicts/resolution-progress'; -import Switchboard from '../../lib/switchboard'; - -function createFilePatch(oldFilePath, newFilePath, status, hunks) { - const oldFile = new FilePatch.File({path: oldFilePath}); - const newFile = new FilePatch.File({path: newFilePath}); - const patch = new FilePatch.Patch({status, hunks}); - - return new FilePatch(oldFile, newFile, patch); -} - -let atomEnv, commandRegistry, tooltips, deserializers; -let switchboard, getFilePatchForPath; -let discardLines, didSurfaceFile, didDiveIntoFilePath, quietlySelectItem, undoLastDiscard, openFiles, getRepositoryForWorkdir; -let getSelectedStagingViewItems, resolutionProgress; - -function createComponent(repository, filePath) { - atomEnv = global.buildAtomEnvironment(); - commandRegistry = atomEnv.commands; - deserializers = atomEnv.deserializers; - tooltips = atomEnv.tooltips; - - switchboard = new Switchboard(); - - discardLines = sinon.spy(); - didSurfaceFile = sinon.spy(); - didDiveIntoFilePath = sinon.spy(); - quietlySelectItem = sinon.spy(); - undoLastDiscard = sinon.spy(); - openFiles = sinon.spy(); - getSelectedStagingViewItems = sinon.spy(); +import {cloneRepository, buildRepository} from '../helpers'; - getRepositoryForWorkdir = () => repository; - resolutionProgress = new ResolutionProgress(); +describe('FilePatchController', function() { + let atomEnv, repository, filePatch; - FilePatchController.resetConfirmedLargeFilePatches(); + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); - return ( - - ); -} + const workdirPath = await cloneRepository(); + repository = await buildRepository(workdirPath); -async function refreshRepository(wrapper) { - const workDir = wrapper.prop('workingDirectoryPath'); - const repository = wrapper.prop('getRepositoryForWorkdir')(workDir); + // a.txt: unstaged changes + await fs.writeFile(path.join(workdirPath, 'a.txt'), 'changed\n'); - const promise = wrapper.prop('switchboard').getFinishRepositoryRefreshPromise(); - repository.refresh(); - await promise; - wrapper.update(); -} + filePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); + }); -describe('FilePatchController', function() { afterEach(function() { atomEnv.destroy(); }); - describe('unit tests', function() { - let workdirPath, repository, filePath, component; - beforeEach(async function() { - workdirPath = await cloneRepository('multi-line-file'); - repository = await buildRepository(workdirPath); - filePath = 'sample.js'; - component = createComponent(repository, filePath); - - getFilePatchForPath = sinon.stub(repository, 'getFilePatchForPath'); - }); - - describe('when the FilePatch is too large', function() { - it('renders a confirmation widget', async function() { - const hunk1 = new Hunk(0, 0, 1, 1, '', [ - new HunkLine('line-1', 'added', 1, 1), - new HunkLine('line-2', 'added', 2, 2), - new HunkLine('line-3', 'added', 3, 3), - new HunkLine('line-4', 'added', 4, 4), - new HunkLine('line-5', 'added', 5, 5), - new HunkLine('line-6', 'added', 6, 6), - ]); - const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk1]); - - getFilePatchForPath.returns(filePatch); - - const wrapper = mount(React.cloneElement(component, {largeDiffByteThreshold: 5})); - - await assert.async.match(wrapper.text(), /large .+ diff/); - }); - - it('renders the full diff when the confirmation is clicked', async function() { - const hunk = new Hunk(0, 0, 1, 1, '', [ - new HunkLine('line-1', 'added', 1, 1), - new HunkLine('line-2', 'added', 2, 2), - new HunkLine('line-3', 'added', 3, 3), - new HunkLine('line-4', 'added', 4, 4), - new HunkLine('line-5', 'added', 5, 5), - new HunkLine('line-6', 'added', 6, 6), - ]); - const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk]); - getFilePatchForPath.returns(filePatch); - - const wrapper = mount(React.cloneElement(component, {largeDiffByteThreshold: 5})); - - await assert.async.isTrue(wrapper.update().find('.large-file-patch').exists()); - wrapper.find('.large-file-patch').find('button').simulate('click'); - - assert.isTrue(wrapper.find('HunkView').exists()); - }); - - it('renders the full diff if the file has been confirmed before', async function() { - const hunk = new Hunk(0, 0, 1, 1, '', [ - new HunkLine('line-1', 'added', 1, 1), - new HunkLine('line-2', 'added', 2, 2), - new HunkLine('line-3', 'added', 3, 3), - new HunkLine('line-4', 'added', 4, 4), - new HunkLine('line-5', 'added', 5, 5), - new HunkLine('line-6', 'added', 6, 6), - ]); - const filePatch1 = createFilePatch(filePath, filePath, 'modified', [hunk]); - const filePatch2 = createFilePatch('b.txt', 'b.txt', 'modified', [hunk]); - - getFilePatchForPath.returns(filePatch1); - - const wrapper = mount(React.cloneElement(component, { - filePath: filePatch1.getPath(), largeDiffByteThreshold: 5, - })); - - await assert.async.isTrue(wrapper.update().find('.large-file-patch').exists()); - wrapper.find('.large-file-patch').find('button').simulate('click'); - assert.isTrue(wrapper.find('HunkView').exists()); - - getFilePatchForPath.returns(filePatch2); - wrapper.setProps({filePath: filePatch2.getPath()}); - await assert.async.isTrue(wrapper.update().find('.large-file-patch').exists()); - - getFilePatchForPath.returns(filePatch1); - wrapper.setProps({filePath: filePatch1.getPath()}); - assert.isTrue(wrapper.update().find('HunkView').exists()); - }); - }); - - describe('onRepoRefresh', function() { - it('sets the correct FilePatch as state', async function() { - repository.getFilePatchForPath.restore(); - fs.writeFileSync(path.join(workdirPath, filePath), 'change', 'utf8'); - - const wrapper = mount(component); - - await assert.async.isNotNull(wrapper.state('filePatch')); - - const originalFilePatch = wrapper.state('filePatch'); - assert.equal(wrapper.state('stagingStatus'), 'unstaged'); - - fs.writeFileSync(path.join(workdirPath, 'file.txt'), 'change\nand again!', 'utf8'); - await refreshRepository(wrapper); - - assert.notEqual(originalFilePatch, wrapper.state('filePatch')); - assert.equal(wrapper.state('stagingStatus'), 'unstaged'); - }); - }); - - it('renders FilePatchView only if FilePatch has hunks', async function() { - const emptyFilePatch = createFilePatch(filePath, filePath, 'modified', []); - getFilePatchForPath.returns(emptyFilePatch); - - const wrapper = mount(component); - - assert.isTrue(wrapper.find('FilePatchView').exists()); - assert.isTrue(wrapper.find('FilePatchView').text().includes('File has no contents')); - - const hunk1 = new Hunk(0, 0, 1, 1, '', [new HunkLine('line-1', 'added', 1, 1)]); - const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk1]); - getFilePatchForPath.returns(filePatch); - - wrapper.instance().onRepoRefresh(repository); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - assert.isTrue(wrapper.find('HunkView').text().includes('@@ -0,1 +0,1 @@')); - }); - - it('updates the FilePatch after a repo update', async function() { - const hunk1 = new Hunk(5, 5, 2, 1, '', [new HunkLine('line-1', 'added', -1, 5)]); - const hunk2 = new Hunk(8, 8, 1, 1, '', [new HunkLine('line-5', 'deleted', 8, -1)]); - const filePatch0 = createFilePatch(filePath, filePath, 'modified', [hunk1, hunk2]); - getFilePatchForPath.returns(filePatch0); - - const wrapper = shallow(component); - - let view0; - await until(() => { - view0 = wrapper.update().find('FilePatchView').shallow(); - return view0.find({hunk: hunk1}).exists(); - }); - assert.isTrue(view0.find({hunk: hunk2}).exists()); - - const hunk3 = new Hunk(8, 8, 1, 1, '', [new HunkLine('line-10', 'modified', 10, 10)]); - const filePatch1 = createFilePatch(filePath, filePath, 'modified', [hunk1, hunk3]); - getFilePatchForPath.returns(filePatch1); - - wrapper.instance().onRepoRefresh(repository); - let view1; - await until(() => { - view1 = wrapper.update().find('FilePatchView').shallow(); - return view1.find({hunk: hunk3}).exists(); - }); - assert.isTrue(view1.find({hunk: hunk1}).exists()); - assert.isFalse(view1.find({hunk: hunk2}).exists()); - }); - - it('invokes a didSurfaceFile callback with the current file path', async function() { - const filePatch = createFilePatch(filePath, filePath, 'modified', [new Hunk(1, 1, 1, 3, '', [])]); - getFilePatchForPath.returns(filePatch); - - const wrapper = mount(component); - - await assert.async.isTrue(wrapper.update().find('Commands').exists()); - commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'core:move-right'); - assert.isTrue(didSurfaceFile.calledWith(filePath, 'unstaged')); - }); - - describe('openCurrentFile({lineNumber})', () => { - it('sets the cursor on the correct line of the opened text editor', async function() { - const editorSpy = { - relativePath: null, - scrollToBufferPosition: sinon.spy(), - setCursorBufferPosition: sinon.spy(), - }; - - const openFilesStub = relativePaths => { - assert.lengthOf(relativePaths, 1); - editorSpy.relativePath = relativePaths[0]; - return Promise.resolve([editorSpy]); - }; - - const hunk = new Hunk(5, 5, 2, 1, '', [new HunkLine('line-1', 'added', -1, 5)]); - const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk]); - getFilePatchForPath.returns(filePatch); - - const wrapper = mount(React.cloneElement(component, {openFiles: openFilesStub})); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - - wrapper.find('LineView').simulate('mousedown', {button: 0, detail: 1}); - window.dispatchEvent(new MouseEvent('mouseup')); - commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'github:open-file'); - wrapper.update(); - - await assert.async.isTrue(editorSpy.setCursorBufferPosition.called); - - assert.isTrue(editorSpy.relativePath === filePath); - - const scrollCall = editorSpy.scrollToBufferPosition.firstCall; - assert.isTrue(scrollCall.args[0].isEqual([4, 0])); - assert.deepEqual(scrollCall.args[1], {center: true}); - - const cursorCall = editorSpy.setCursorBufferPosition.firstCall; - assert.isTrue(cursorCall.args[0].isEqual([4, 0])); - }); - }); - }); - - describe('integration tests', function() { - describe('handling symlink files', function() { - async function indexModeAndOid(repository, filename) { - const output = await repository.git.exec(['ls-files', '-s', '--', filename]); - if (output) { - const parts = output.split(' '); - return {mode: parts[0], oid: parts[1]}; - } else { - return null; - } - } - - it('unstages added lines that don\'t require symlink change', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - - // correctly handle symlinks on Windows - await repository.git.exec(['config', 'core.symlinks', 'true']); - - const deletedSymlinkAddedFilePath = 'symlink.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedSymlinkAddedFilePath)); - fs.writeFileSync(path.join(workingDirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n', 'utf8'); - - // Stage whole file - await repository.stageFiles([deletedSymlinkAddedFilePath]); - - const component = createComponent(repository, deletedSymlinkAddedFilePath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedSymlinkAddedFilePath, initialStagingStatus: 'staged'})); - - // index shows symlink deltion and added lines - assert.autocrlfEqual(await repository.readFileFromIndex(deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n'); - assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); - - // Unstage a couple added lines, but not all - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); - hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // index shows symlink deletions still staged, only a couple of lines have been unstaged - assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); - assert.autocrlfEqual(await repository.readFileFromIndex(deletedSymlinkAddedFilePath), 'qux\nbaz\nzoo\n'); - }); - - it('stages deleted lines that don\'t require symlink change', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - - const deletedFileAddedSymlinkPath = 'a.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedFileAddedSymlinkPath)); - fs.symlinkSync(path.join(workingDirPath, 'regular-file.txt'), path.join(workingDirPath, deletedFileAddedSymlinkPath)); - - const component = createComponent(repository, deletedFileAddedSymlinkPath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedFileAddedSymlinkPath, initialStagingStatus: 'unstaged'})); - - // index shows file is not a symlink, no deleted lines - assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); - assert.autocrlfEqual(await repository.readFileFromIndex(deletedFileAddedSymlinkPath), 'foo\nbar\nbaz\n\n'); - - // stage a couple of lines, but not all - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); - hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // index shows symlink change has not been staged, a couple of lines have been deleted - assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); - assert.autocrlfEqual(await repository.readFileFromIndex(deletedFileAddedSymlinkPath), 'foo\n\n'); - }); - - it('stages symlink change when staging added lines that depend on change', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - - // correctly handle symlinks on Windows - await repository.git.exec(['config', 'core.symlinks', 'true']); - - const deletedSymlinkAddedFilePath = 'symlink.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedSymlinkAddedFilePath)); - fs.writeFileSync(path.join(workingDirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n', 'utf8'); - - const component = createComponent(repository, deletedSymlinkAddedFilePath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedSymlinkAddedFilePath})); - - // index shows file is symlink - assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '120000'); - - // Stage a couple added lines, but not all - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); - hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // index no longer shows file is symlink (symlink has been deleted), now a regular file with contents - assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); - assert.autocrlfEqual(await repository.readFileFromIndex(deletedSymlinkAddedFilePath), 'foo\nbar\n'); - }); - - it('unstages symlink change when unstaging deleted lines that depend on change', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - - const deletedFileAddedSymlinkPath = 'a.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedFileAddedSymlinkPath)); - fs.symlinkSync(path.join(workingDirPath, 'regular-file.txt'), path.join(workingDirPath, deletedFileAddedSymlinkPath)); - await repository.stageFiles([deletedFileAddedSymlinkPath]); - - const component = createComponent(repository, deletedFileAddedSymlinkPath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedFileAddedSymlinkPath, initialStagingStatus: 'staged'})); - - // index shows file is symlink - assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '120000'); - - // unstage a couple of lines, but not all - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); - hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // index no longer shows file is symlink (symlink creation has been unstaged), shows contents of file that existed prior to symlink - assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); - assert.autocrlfEqual(await repository.readFileFromIndex(deletedFileAddedSymlinkPath), 'bar\nbaz\n'); - }); - - it('stages file deletion when all deleted lines are staged', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - await repository.getLoadPromise(); - - const deletedFileAddedSymlinkPath = 'a.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedFileAddedSymlinkPath)); - fs.symlinkSync(path.join(workingDirPath, 'regular-file.txt'), path.join(workingDirPath, deletedFileAddedSymlinkPath)); - - const component = createComponent(repository, deletedFileAddedSymlinkPath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedFileAddedSymlinkPath})); - - assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); - - // stage all deleted lines - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('.github-HunkView-title').simulate('click'); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // File is not on index, file deletion has been staged - assert.isNull(await indexModeAndOid(repository, deletedFileAddedSymlinkPath)); - const {stagedFiles, unstagedFiles} = await repository.getStatusesForChangedFiles(); - assert.equal(unstagedFiles[deletedFileAddedSymlinkPath], 'added'); - assert.equal(stagedFiles[deletedFileAddedSymlinkPath], 'deleted'); - }); - - it('unstages file creation when all added lines are unstaged', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - - await repository.git.exec(['config', 'core.symlinks', 'true']); - - const deletedSymlinkAddedFilePath = 'symlink.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedSymlinkAddedFilePath)); - fs.writeFileSync(path.join(workingDirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n', 'utf8'); - await repository.stageFiles([deletedSymlinkAddedFilePath]); - - const component = createComponent(repository, deletedSymlinkAddedFilePath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedSymlinkAddedFilePath, initialStagingStatus: 'staged'})); - - assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); - - // unstage all added lines - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('.github-HunkView-title').simulate('click'); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // File is not on index, file creation has been unstaged - assert.isNull(await indexModeAndOid(repository, deletedSymlinkAddedFilePath)); - const {stagedFiles, unstagedFiles} = await repository.getStatusesForChangedFiles(); - assert.equal(unstagedFiles[deletedSymlinkAddedFilePath], 'added'); - assert.equal(stagedFiles[deletedSymlinkAddedFilePath], 'deleted'); - }); - }); - - describe('handling non-symlink changes', function() { - let workdirPath, repository, filePath, component; - beforeEach(async function() { - workdirPath = await cloneRepository('multi-line-file'); - repository = await buildRepository(workdirPath); - filePath = 'sample.js'; - component = createComponent(repository, filePath); - }); - - it('stages and unstages hunks when the stage button is clicked on hunk views with no individual lines selected', async function() { - const absFilePath = path.join(workdirPath, filePath); - const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); - const unstagedLines = originalLines.slice(); - unstagedLines.splice(1, 1, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - unstagedLines.splice(11, 2, 'this is a modified line'); - fs.writeFileSync(absFilePath, unstagedLines.join('\n')); - - const wrapper = mount(component); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'core:move-down'); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const hunkView0 = wrapper.find('HunkView').at(0); - assert.isFalse(hunkView0.prop('isSelected')); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - const expectedStagedLines = originalLines.slice(); - expectedStagedLines.splice(1, 1, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedStagedLines.join('\n')); - const updatePromise0 = switchboard.getChangePatchPromise(); - const stagedFilePatch = await repository.getFilePatchForPath('sample.js', {staged: true}); - wrapper.setState({ - stagingStatus: 'staged', - filePatch: stagedFilePatch, - }); - await updatePromise0; - const hunkView1 = wrapper.find('HunkView').at(0); - const opPromise1 = switchboard.getFinishStageOperationPromise(); - hunkView1.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise1; - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), originalLines.join('\n')); - }); - - it('stages and unstages individual lines when the stage button is clicked on a hunk with selected lines', async function() { - const absFilePath = path.join(workdirPath, filePath); - const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); - - // write some unstaged changes - const unstagedLines = originalLines.slice(); - unstagedLines.splice(1, 1, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - unstagedLines.splice(11, 2, 'this is a modified line'); - fs.writeFileSync(absFilePath, unstagedLines.join('\n')); - - // stage a subset of lines from first hunk - const wrapper = mount(component); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); - hunkView0.find('LineView').at(3).find('.github-HunkView-line').simulate('mousemove', {}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - const expectedLines0 = originalLines.slice(); - expectedLines0.splice(1, 1, - 'this is a modified line', - 'this is a new line', - ); - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedLines0.join('\n')); - - // stage remaining lines in hunk - const opPromise1 = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); - await opPromise1; - - await refreshRepository(wrapper); - - const expectedLines1 = originalLines.slice(); - expectedLines1.splice(1, 1, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedLines1.join('\n')); - - // unstage a subset of lines from the first hunk - wrapper.setState({stagingStatus: 'staged'}); - await refreshRepository(wrapper); - - const hunkView2 = wrapper.find('HunkView').at(0); - hunkView2.find('LineView').at(1).find('.github-HunkView-line') - .simulate('mousedown', {button: 0, detail: 1}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView2.find('LineView').at(2).find('.github-HunkView-line') - .simulate('mousedown', {button: 0, detail: 1, metaKey: true}); - window.dispatchEvent(new MouseEvent('mouseup')); - - const opPromise2 = switchboard.getFinishStageOperationPromise(); - hunkView2.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise2; - - await refreshRepository(wrapper); - - const expectedLines2 = originalLines.slice(); - expectedLines2.splice(2, 0, - 'this is a new line', - 'this is another new line', - ); - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedLines2.join('\n')); - - // unstage the rest of the hunk - commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'github:toggle-patch-selection-mode'); - - const opPromise3 = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); - await opPromise3; - - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), originalLines.join('\n')); - }); - - // https://github.com/atom/github/issues/417 - describe('when unstaging the last lines/hunks from a file', function() { - it('removes added files from index when last hunk is unstaged', async function() { - const absFilePath = path.join(workdirPath, 'new-file.txt'); - - fs.writeFileSync(absFilePath, 'foo\n'); - await repository.stageFiles(['new-file.txt']); - - const wrapper = mount(React.cloneElement(component, { - filePath: 'new-file.txt', - initialStagingStatus: 'staged', - })); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - - const opPromise = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); - await opPromise; - - const stagedChanges = await repository.getStagedChanges(); - assert.equal(stagedChanges.length, 0); - }); - - it('removes added files from index when last lines are unstaged', async function() { - const absFilePath = path.join(workdirPath, 'new-file.txt'); - - fs.writeFileSync(absFilePath, 'foo\n'); - await repository.stageFiles(['new-file.txt']); - - const wrapper = mount(React.cloneElement(component, { - filePath: 'new-file.txt', - initialStagingStatus: 'staged', - })); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - - const viewNode = wrapper.find('FilePatchView').getDOMNode(); - commandRegistry.dispatch(viewNode, 'github:toggle-patch-selection-mode'); - commandRegistry.dispatch(viewNode, 'core:select-all'); - - const opPromise = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); - await opPromise; - - const stagedChanges = await repository.getStagedChanges(); - assert.lengthOf(stagedChanges, 0); - }); - }); - - // https://github.com/atom/github/issues/341 - describe('when duplicate staging occurs', function() { - it('avoids patch conflicts with pending line staging operations', async function() { - const absFilePath = path.join(workdirPath, filePath); - const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); - - // write some unstaged changes - const unstagedLines = originalLines.slice(); - unstagedLines.splice(1, 0, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - unstagedLines.splice(11, 2, 'this is a modified line'); - fs.writeFileSync(absFilePath, unstagedLines.join('\n')); - - const wrapper = mount(component); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line') - .simulate('mousedown', {button: 0, detail: 1}); - window.dispatchEvent(new MouseEvent('mouseup')); - - // stage lines in rapid succession - // second stage action is a no-op since the first staging operation is in flight - const line1StagingPromise = switchboard.getFinishStageOperationPromise(); - hunkView0.find('.github-HunkView-stageButton').simulate('click'); - hunkView0.find('.github-HunkView-stageButton').simulate('click'); - await line1StagingPromise; - - const changePatchPromise = switchboard.getChangePatchPromise(); - - // assert that only line 1 has been staged - await refreshRepository(wrapper); // clear the cached file patches - let expectedLines = originalLines.slice(); - expectedLines.splice(1, 0, - 'this is a modified line', - ); - let actualLines = await repository.readFileFromIndex(filePath); - assert.autocrlfEqual(actualLines, expectedLines.join('\n')); - await changePatchPromise; - wrapper.update(); - - const hunkView1 = wrapper.find('HunkView').at(0); - hunkView1.find('LineView').at(2).find('.github-HunkView-line') - .simulate('mousedown', {button: 0, detail: 1}); - window.dispatchEvent(new MouseEvent('mouseup')); - const line2StagingPromise = switchboard.getFinishStageOperationPromise(); - hunkView1.find('.github-HunkView-stageButton').simulate('click'); - await line2StagingPromise; - - // assert that line 2 has now been staged - expectedLines = originalLines.slice(); - expectedLines.splice(1, 0, - 'this is a modified line', - 'this is a new line', - ); - actualLines = await repository.readFileFromIndex(filePath); - assert.autocrlfEqual(actualLines, expectedLines.join('\n')); - }); - - it('avoids patch conflicts with pending hunk staging operations', async function() { - const absFilePath = path.join(workdirPath, filePath); - const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); - - // write some unstaged changes - const unstagedLines = originalLines.slice(); - unstagedLines.splice(1, 0, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - unstagedLines.splice(11, 2, 'this is a modified line'); - fs.writeFileSync(absFilePath, unstagedLines.join('\n')); - - const wrapper = mount(component); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - - // ensure staging the same hunk twice does not cause issues - // second stage action is a no-op since the first staging operation is in flight - const hunk1StagingPromise = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('.github-HunkView-stageButton').simulate('click'); - wrapper.find('HunkView').at(0).find('.github-HunkView-stageButton').simulate('click'); - await hunk1StagingPromise; - - const patchPromise0 = switchboard.getChangePatchPromise(); - await refreshRepository(wrapper); // clear the cached file patches - const modifiedFilePatch = await repository.getFilePatchForPath(filePath); - wrapper.setState({filePatch: modifiedFilePatch}); - await patchPromise0; + function buildApp(overrideProps = {}) { + const props = { + stagingStatus: 'unstaged', + isPartiallyStaged: false, + filePatch, + ...overrideProps, + }; - let expectedLines = originalLines.slice(); - expectedLines.splice(1, 0, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - let actualLines = await repository.readFileFromIndex(filePath); - assert.autocrlfEqual(actualLines, expectedLines.join('\n')); + return ; + } - const hunk2StagingPromise = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('.github-HunkView-stageButton').simulate('click'); - await hunk2StagingPromise; + it('passes extra props to the FilePatchView', function() { + const extra = Symbol('extra'); + const wrapper = mount(buildApp({extra})); - expectedLines = originalLines.slice(); - expectedLines.splice(1, 0, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - expectedLines.splice(11, 2, 'this is a modified line'); - actualLines = await repository.readFileFromIndex(filePath); - assert.autocrlfEqual(actualLines, expectedLines.join('\n')); - }); - }); - }); + assert.strictEqual(wrapper.find('FilePatchView').prop('extra'), extra); }); }); diff --git a/test/controllers/file-patch-controller.test.old.js b/test/controllers/file-patch-controller.test.old.js new file mode 100644 index 0000000000..a7b075e8ec --- /dev/null +++ b/test/controllers/file-patch-controller.test.old.js @@ -0,0 +1,811 @@ +import React from 'react'; +import {shallow, mount} from 'enzyme'; +import until from 'test-until'; + +import fs from 'fs'; +import path from 'path'; + +import {cloneRepository, buildRepository} from '../helpers'; +import FilePatch from '../../lib/models/file-patch'; +import FilePatchController from '../../lib/controllers/file-patch-controller'; +import Hunk from '../../lib/models/hunk'; +import HunkLine from '../../lib/models/hunk-line'; +import ResolutionProgress from '../../lib/models/conflicts/resolution-progress'; +import Switchboard from '../../lib/switchboard'; + +function createFilePatch(oldFilePath, newFilePath, status, hunks) { + const oldFile = new FilePatch.File({path: oldFilePath}); + const newFile = new FilePatch.File({path: newFilePath}); + const patch = new FilePatch.Patch({status, hunks}); + + return new FilePatch(oldFile, newFile, patch); +} + +let atomEnv, commandRegistry, tooltips, deserializers; +let switchboard, getFilePatchForPath; +let discardLines, didSurfaceFile, didDiveIntoFilePath, quietlySelectItem, undoLastDiscard, openFiles, getRepositoryForWorkdir; +let getSelectedStagingViewItems, resolutionProgress; + +function createComponent(repository, filePath) { + atomEnv = global.buildAtomEnvironment(); + commandRegistry = atomEnv.commands; + deserializers = atomEnv.deserializers; + tooltips = atomEnv.tooltips; + + switchboard = new Switchboard(); + + discardLines = sinon.spy(); + didSurfaceFile = sinon.spy(); + didDiveIntoFilePath = sinon.spy(); + quietlySelectItem = sinon.spy(); + undoLastDiscard = sinon.spy(); + openFiles = sinon.spy(); + getSelectedStagingViewItems = sinon.spy(); + + getRepositoryForWorkdir = () => repository; + resolutionProgress = new ResolutionProgress(); + + FilePatchController.resetConfirmedLargeFilePatches(); + + return ( + + ); +} + +async function refreshRepository(wrapper) { + const workDir = wrapper.prop('workingDirectoryPath'); + const repository = wrapper.prop('getRepositoryForWorkdir')(workDir); + + const promise = wrapper.prop('switchboard').getFinishRepositoryRefreshPromise(); + repository.refresh(); + await promise; + wrapper.update(); +} + +describe('FilePatchController', function() { + afterEach(function() { + atomEnv.destroy(); + }); + + describe('unit tests', function() { + let workdirPath, repository, filePath, component; + beforeEach(async function() { + workdirPath = await cloneRepository('multi-line-file'); + repository = await buildRepository(workdirPath); + filePath = 'sample.js'; + component = createComponent(repository, filePath); + + getFilePatchForPath = sinon.stub(repository, 'getFilePatchForPath'); + }); + + describe('when the FilePatch is too large', function() { + it('renders a confirmation widget', async function() { + const hunk1 = new Hunk(0, 0, 1, 1, '', [ + new HunkLine('line-1', 'added', 1, 1), + new HunkLine('line-2', 'added', 2, 2), + new HunkLine('line-3', 'added', 3, 3), + new HunkLine('line-4', 'added', 4, 4), + new HunkLine('line-5', 'added', 5, 5), + new HunkLine('line-6', 'added', 6, 6), + ]); + const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk1]); + + getFilePatchForPath.returns(filePatch); + + const wrapper = mount(React.cloneElement(component, {largeDiffByteThreshold: 5})); + + await assert.async.match(wrapper.text(), /large .+ diff/); + }); + + it('renders the full diff when the confirmation is clicked', async function() { + const hunk = new Hunk(0, 0, 1, 1, '', [ + new HunkLine('line-1', 'added', 1, 1), + new HunkLine('line-2', 'added', 2, 2), + new HunkLine('line-3', 'added', 3, 3), + new HunkLine('line-4', 'added', 4, 4), + new HunkLine('line-5', 'added', 5, 5), + new HunkLine('line-6', 'added', 6, 6), + ]); + const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk]); + getFilePatchForPath.returns(filePatch); + + const wrapper = mount(React.cloneElement(component, {largeDiffByteThreshold: 5})); + + await assert.async.isTrue(wrapper.update().find('.large-file-patch').exists()); + wrapper.find('.large-file-patch').find('button').simulate('click'); + + assert.isTrue(wrapper.find('HunkView').exists()); + }); + + it('renders the full diff if the file has been confirmed before', async function() { + const hunk = new Hunk(0, 0, 1, 1, '', [ + new HunkLine('line-1', 'added', 1, 1), + new HunkLine('line-2', 'added', 2, 2), + new HunkLine('line-3', 'added', 3, 3), + new HunkLine('line-4', 'added', 4, 4), + new HunkLine('line-5', 'added', 5, 5), + new HunkLine('line-6', 'added', 6, 6), + ]); + const filePatch1 = createFilePatch(filePath, filePath, 'modified', [hunk]); + const filePatch2 = createFilePatch('b.txt', 'b.txt', 'modified', [hunk]); + + getFilePatchForPath.returns(filePatch1); + + const wrapper = mount(React.cloneElement(component, { + filePath: filePatch1.getPath(), largeDiffByteThreshold: 5, + })); + + await assert.async.isTrue(wrapper.update().find('.large-file-patch').exists()); + wrapper.find('.large-file-patch').find('button').simulate('click'); + assert.isTrue(wrapper.find('HunkView').exists()); + + getFilePatchForPath.returns(filePatch2); + wrapper.setProps({filePath: filePatch2.getPath()}); + await assert.async.isTrue(wrapper.update().find('.large-file-patch').exists()); + + getFilePatchForPath.returns(filePatch1); + wrapper.setProps({filePath: filePatch1.getPath()}); + assert.isTrue(wrapper.update().find('HunkView').exists()); + }); + }); + + describe('onRepoRefresh', function() { + it('sets the correct FilePatch as state', async function() { + repository.getFilePatchForPath.restore(); + fs.writeFileSync(path.join(workdirPath, filePath), 'change', 'utf8'); + + const wrapper = mount(component); + + await assert.async.isNotNull(wrapper.state('filePatch')); + + const originalFilePatch = wrapper.state('filePatch'); + assert.equal(wrapper.state('stagingStatus'), 'unstaged'); + + fs.writeFileSync(path.join(workdirPath, 'file.txt'), 'change\nand again!', 'utf8'); + await refreshRepository(wrapper); + + assert.notEqual(originalFilePatch, wrapper.state('filePatch')); + assert.equal(wrapper.state('stagingStatus'), 'unstaged'); + }); + }); + + it('renders FilePatchView only if FilePatch has hunks', async function() { + const emptyFilePatch = createFilePatch(filePath, filePath, 'modified', []); + getFilePatchForPath.returns(emptyFilePatch); + + const wrapper = mount(component); + + assert.isTrue(wrapper.find('FilePatchView').exists()); + assert.isTrue(wrapper.find('FilePatchView').text().includes('File has no contents')); + + const hunk1 = new Hunk(0, 0, 1, 1, '', [new HunkLine('line-1', 'added', 1, 1)]); + const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk1]); + getFilePatchForPath.returns(filePatch); + + wrapper.instance().onRepoRefresh(repository); + + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + assert.isTrue(wrapper.find('HunkView').text().includes('@@ -0,1 +0,1 @@')); + }); + + it('updates the FilePatch after a repo update', async function() { + const hunk1 = new Hunk(5, 5, 2, 1, '', [new HunkLine('line-1', 'added', -1, 5)]); + const hunk2 = new Hunk(8, 8, 1, 1, '', [new HunkLine('line-5', 'deleted', 8, -1)]); + const filePatch0 = createFilePatch(filePath, filePath, 'modified', [hunk1, hunk2]); + getFilePatchForPath.returns(filePatch0); + + const wrapper = shallow(component); + + let view0; + await until(() => { + view0 = wrapper.update().find('FilePatchView').shallow(); + return view0.find({hunk: hunk1}).exists(); + }); + assert.isTrue(view0.find({hunk: hunk2}).exists()); + + const hunk3 = new Hunk(8, 8, 1, 1, '', [new HunkLine('line-10', 'modified', 10, 10)]); + const filePatch1 = createFilePatch(filePath, filePath, 'modified', [hunk1, hunk3]); + getFilePatchForPath.returns(filePatch1); + + wrapper.instance().onRepoRefresh(repository); + let view1; + await until(() => { + view1 = wrapper.update().find('FilePatchView').shallow(); + return view1.find({hunk: hunk3}).exists(); + }); + assert.isTrue(view1.find({hunk: hunk1}).exists()); + assert.isFalse(view1.find({hunk: hunk2}).exists()); + }); + + it('invokes a didSurfaceFile callback with the current file path', async function() { + const filePatch = createFilePatch(filePath, filePath, 'modified', [new Hunk(1, 1, 1, 3, '', [])]); + getFilePatchForPath.returns(filePatch); + + const wrapper = mount(component); + + await assert.async.isTrue(wrapper.update().find('Commands').exists()); + commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'core:move-right'); + assert.isTrue(didSurfaceFile.calledWith(filePath, 'unstaged')); + }); + + describe('openCurrentFile({lineNumber})', () => { + it('sets the cursor on the correct line of the opened text editor', async function() { + const editorSpy = { + relativePath: null, + scrollToBufferPosition: sinon.spy(), + setCursorBufferPosition: sinon.spy(), + }; + + const openFilesStub = relativePaths => { + assert.lengthOf(relativePaths, 1); + editorSpy.relativePath = relativePaths[0]; + return Promise.resolve([editorSpy]); + }; + + const hunk = new Hunk(5, 5, 2, 1, '', [new HunkLine('line-1', 'added', -1, 5)]); + const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk]); + getFilePatchForPath.returns(filePatch); + + const wrapper = mount(React.cloneElement(component, {openFiles: openFilesStub})); + + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + + wrapper.find('LineView').simulate('mousedown', {button: 0, detail: 1}); + window.dispatchEvent(new MouseEvent('mouseup')); + commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'github:open-file'); + wrapper.update(); + + await assert.async.isTrue(editorSpy.setCursorBufferPosition.called); + + assert.isTrue(editorSpy.relativePath === filePath); + + const scrollCall = editorSpy.scrollToBufferPosition.firstCall; + assert.isTrue(scrollCall.args[0].isEqual([4, 0])); + assert.deepEqual(scrollCall.args[1], {center: true}); + + const cursorCall = editorSpy.setCursorBufferPosition.firstCall; + assert.isTrue(cursorCall.args[0].isEqual([4, 0])); + }); + }); + }); + + describe('integration tests', function() { + describe('handling symlink files', function() { + async function indexModeAndOid(repository, filename) { + const output = await repository.git.exec(['ls-files', '-s', '--', filename]); + if (output) { + const parts = output.split(' '); + return {mode: parts[0], oid: parts[1]}; + } else { + return null; + } + } + + it('unstages added lines that don\'t require symlink change', async function() { + const workingDirPath = await cloneRepository('symlinks'); + const repository = await buildRepository(workingDirPath); + + // correctly handle symlinks on Windows + await repository.git.exec(['config', 'core.symlinks', 'true']); + + const deletedSymlinkAddedFilePath = 'symlink.txt'; + fs.unlinkSync(path.join(workingDirPath, deletedSymlinkAddedFilePath)); + fs.writeFileSync(path.join(workingDirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n', 'utf8'); + + // Stage whole file + await repository.stageFiles([deletedSymlinkAddedFilePath]); + + const component = createComponent(repository, deletedSymlinkAddedFilePath); + const wrapper = mount(React.cloneElement(component, {filePath: deletedSymlinkAddedFilePath, initialStagingStatus: 'staged'})); + + // index shows symlink deltion and added lines + assert.autocrlfEqual(await repository.readFileFromIndex(deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n'); + assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); + + // Unstage a couple added lines, but not all + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + const opPromise0 = switchboard.getFinishStageOperationPromise(); + const hunkView0 = wrapper.find('HunkView').at(0); + hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); + hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); + window.dispatchEvent(new MouseEvent('mouseup')); + hunkView0.find('button.github-HunkView-stageButton').simulate('click'); + await opPromise0; + + await refreshRepository(wrapper); + + // index shows symlink deletions still staged, only a couple of lines have been unstaged + assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); + assert.autocrlfEqual(await repository.readFileFromIndex(deletedSymlinkAddedFilePath), 'qux\nbaz\nzoo\n'); + }); + + it('stages deleted lines that don\'t require symlink change', async function() { + const workingDirPath = await cloneRepository('symlinks'); + const repository = await buildRepository(workingDirPath); + + const deletedFileAddedSymlinkPath = 'a.txt'; + fs.unlinkSync(path.join(workingDirPath, deletedFileAddedSymlinkPath)); + fs.symlinkSync(path.join(workingDirPath, 'regular-file.txt'), path.join(workingDirPath, deletedFileAddedSymlinkPath)); + + const component = createComponent(repository, deletedFileAddedSymlinkPath); + const wrapper = mount(React.cloneElement(component, {filePath: deletedFileAddedSymlinkPath, initialStagingStatus: 'unstaged'})); + + // index shows file is not a symlink, no deleted lines + assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); + assert.autocrlfEqual(await repository.readFileFromIndex(deletedFileAddedSymlinkPath), 'foo\nbar\nbaz\n\n'); + + // stage a couple of lines, but not all + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + const opPromise0 = switchboard.getFinishStageOperationPromise(); + const hunkView0 = wrapper.find('HunkView').at(0); + hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); + hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); + window.dispatchEvent(new MouseEvent('mouseup')); + hunkView0.find('button.github-HunkView-stageButton').simulate('click'); + await opPromise0; + + await refreshRepository(wrapper); + + // index shows symlink change has not been staged, a couple of lines have been deleted + assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); + assert.autocrlfEqual(await repository.readFileFromIndex(deletedFileAddedSymlinkPath), 'foo\n\n'); + }); + + it('stages symlink change when staging added lines that depend on change', async function() { + const workingDirPath = await cloneRepository('symlinks'); + const repository = await buildRepository(workingDirPath); + + // correctly handle symlinks on Windows + await repository.git.exec(['config', 'core.symlinks', 'true']); + + const deletedSymlinkAddedFilePath = 'symlink.txt'; + fs.unlinkSync(path.join(workingDirPath, deletedSymlinkAddedFilePath)); + fs.writeFileSync(path.join(workingDirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n', 'utf8'); + + const component = createComponent(repository, deletedSymlinkAddedFilePath); + const wrapper = mount(React.cloneElement(component, {filePath: deletedSymlinkAddedFilePath})); + + // index shows file is symlink + assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '120000'); + + // Stage a couple added lines, but not all + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + const opPromise0 = switchboard.getFinishStageOperationPromise(); + const hunkView0 = wrapper.find('HunkView').at(0); + hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); + hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); + window.dispatchEvent(new MouseEvent('mouseup')); + hunkView0.find('button.github-HunkView-stageButton').simulate('click'); + await opPromise0; + + await refreshRepository(wrapper); + + // index no longer shows file is symlink (symlink has been deleted), now a regular file with contents + assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); + assert.autocrlfEqual(await repository.readFileFromIndex(deletedSymlinkAddedFilePath), 'foo\nbar\n'); + }); + + it('unstages symlink change when unstaging deleted lines that depend on change', async function() { + const workingDirPath = await cloneRepository('symlinks'); + const repository = await buildRepository(workingDirPath); + + const deletedFileAddedSymlinkPath = 'a.txt'; + fs.unlinkSync(path.join(workingDirPath, deletedFileAddedSymlinkPath)); + fs.symlinkSync(path.join(workingDirPath, 'regular-file.txt'), path.join(workingDirPath, deletedFileAddedSymlinkPath)); + await repository.stageFiles([deletedFileAddedSymlinkPath]); + + const component = createComponent(repository, deletedFileAddedSymlinkPath); + const wrapper = mount(React.cloneElement(component, {filePath: deletedFileAddedSymlinkPath, initialStagingStatus: 'staged'})); + + // index shows file is symlink + assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '120000'); + + // unstage a couple of lines, but not all + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + const opPromise0 = switchboard.getFinishStageOperationPromise(); + const hunkView0 = wrapper.find('HunkView').at(0); + hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); + hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); + window.dispatchEvent(new MouseEvent('mouseup')); + hunkView0.find('button.github-HunkView-stageButton').simulate('click'); + await opPromise0; + + await refreshRepository(wrapper); + + // index no longer shows file is symlink (symlink creation has been unstaged), shows contents of file that existed prior to symlink + assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); + assert.autocrlfEqual(await repository.readFileFromIndex(deletedFileAddedSymlinkPath), 'bar\nbaz\n'); + }); + + it('stages file deletion when all deleted lines are staged', async function() { + const workingDirPath = await cloneRepository('symlinks'); + const repository = await buildRepository(workingDirPath); + await repository.getLoadPromise(); + + const deletedFileAddedSymlinkPath = 'a.txt'; + fs.unlinkSync(path.join(workingDirPath, deletedFileAddedSymlinkPath)); + fs.symlinkSync(path.join(workingDirPath, 'regular-file.txt'), path.join(workingDirPath, deletedFileAddedSymlinkPath)); + + const component = createComponent(repository, deletedFileAddedSymlinkPath); + const wrapper = mount(React.cloneElement(component, {filePath: deletedFileAddedSymlinkPath})); + + assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); + + // stage all deleted lines + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + const opPromise0 = switchboard.getFinishStageOperationPromise(); + const hunkView0 = wrapper.find('HunkView').at(0); + hunkView0.find('.github-HunkView-title').simulate('click'); + hunkView0.find('button.github-HunkView-stageButton').simulate('click'); + await opPromise0; + + await refreshRepository(wrapper); + + // File is not on index, file deletion has been staged + assert.isNull(await indexModeAndOid(repository, deletedFileAddedSymlinkPath)); + const {stagedFiles, unstagedFiles} = await repository.getStatusesForChangedFiles(); + assert.equal(unstagedFiles[deletedFileAddedSymlinkPath], 'added'); + assert.equal(stagedFiles[deletedFileAddedSymlinkPath], 'deleted'); + }); + + it('unstages file creation when all added lines are unstaged', async function() { + const workingDirPath = await cloneRepository('symlinks'); + const repository = await buildRepository(workingDirPath); + + await repository.git.exec(['config', 'core.symlinks', 'true']); + + const deletedSymlinkAddedFilePath = 'symlink.txt'; + fs.unlinkSync(path.join(workingDirPath, deletedSymlinkAddedFilePath)); + fs.writeFileSync(path.join(workingDirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n', 'utf8'); + await repository.stageFiles([deletedSymlinkAddedFilePath]); + + const component = createComponent(repository, deletedSymlinkAddedFilePath); + const wrapper = mount(React.cloneElement(component, {filePath: deletedSymlinkAddedFilePath, initialStagingStatus: 'staged'})); + + assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); + + // unstage all added lines + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + const opPromise0 = switchboard.getFinishStageOperationPromise(); + const hunkView0 = wrapper.find('HunkView').at(0); + hunkView0.find('.github-HunkView-title').simulate('click'); + hunkView0.find('button.github-HunkView-stageButton').simulate('click'); + await opPromise0; + + await refreshRepository(wrapper); + + // File is not on index, file creation has been unstaged + assert.isNull(await indexModeAndOid(repository, deletedSymlinkAddedFilePath)); + const {stagedFiles, unstagedFiles} = await repository.getStatusesForChangedFiles(); + assert.equal(unstagedFiles[deletedSymlinkAddedFilePath], 'added'); + assert.equal(stagedFiles[deletedSymlinkAddedFilePath], 'deleted'); + }); + }); + + describe('handling non-symlink changes', function() { + let workdirPath, repository, filePath, component; + beforeEach(async function() { + workdirPath = await cloneRepository('multi-line-file'); + repository = await buildRepository(workdirPath); + filePath = 'sample.js'; + component = createComponent(repository, filePath); + }); + + it('stages and unstages hunks when the stage button is clicked on hunk views with no individual lines selected', async function() { + const absFilePath = path.join(workdirPath, filePath); + const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); + const unstagedLines = originalLines.slice(); + unstagedLines.splice(1, 1, + 'this is a modified line', + 'this is a new line', + 'this is another new line', + ); + unstagedLines.splice(11, 2, 'this is a modified line'); + fs.writeFileSync(absFilePath, unstagedLines.join('\n')); + + const wrapper = mount(component); + + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'core:move-down'); + + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + const hunkView0 = wrapper.find('HunkView').at(0); + assert.isFalse(hunkView0.prop('isSelected')); + const opPromise0 = switchboard.getFinishStageOperationPromise(); + hunkView0.find('button.github-HunkView-stageButton').simulate('click'); + await opPromise0; + + const expectedStagedLines = originalLines.slice(); + expectedStagedLines.splice(1, 1, + 'this is a modified line', + 'this is a new line', + 'this is another new line', + ); + assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedStagedLines.join('\n')); + const updatePromise0 = switchboard.getChangePatchPromise(); + const stagedFilePatch = await repository.getFilePatchForPath('sample.js', {staged: true}); + wrapper.setState({ + stagingStatus: 'staged', + filePatch: stagedFilePatch, + }); + await updatePromise0; + const hunkView1 = wrapper.find('HunkView').at(0); + const opPromise1 = switchboard.getFinishStageOperationPromise(); + hunkView1.find('button.github-HunkView-stageButton').simulate('click'); + await opPromise1; + assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), originalLines.join('\n')); + }); + + it('stages and unstages individual lines when the stage button is clicked on a hunk with selected lines', async function() { + const absFilePath = path.join(workdirPath, filePath); + const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); + + // write some unstaged changes + const unstagedLines = originalLines.slice(); + unstagedLines.splice(1, 1, + 'this is a modified line', + 'this is a new line', + 'this is another new line', + ); + unstagedLines.splice(11, 2, 'this is a modified line'); + fs.writeFileSync(absFilePath, unstagedLines.join('\n')); + + // stage a subset of lines from first hunk + const wrapper = mount(component); + + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + const opPromise0 = switchboard.getFinishStageOperationPromise(); + const hunkView0 = wrapper.find('HunkView').at(0); + hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); + hunkView0.find('LineView').at(3).find('.github-HunkView-line').simulate('mousemove', {}); + window.dispatchEvent(new MouseEvent('mouseup')); + hunkView0.find('button.github-HunkView-stageButton').simulate('click'); + await opPromise0; + + await refreshRepository(wrapper); + + const expectedLines0 = originalLines.slice(); + expectedLines0.splice(1, 1, + 'this is a modified line', + 'this is a new line', + ); + assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedLines0.join('\n')); + + // stage remaining lines in hunk + const opPromise1 = switchboard.getFinishStageOperationPromise(); + wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); + await opPromise1; + + await refreshRepository(wrapper); + + const expectedLines1 = originalLines.slice(); + expectedLines1.splice(1, 1, + 'this is a modified line', + 'this is a new line', + 'this is another new line', + ); + assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedLines1.join('\n')); + + // unstage a subset of lines from the first hunk + wrapper.setState({stagingStatus: 'staged'}); + await refreshRepository(wrapper); + + const hunkView2 = wrapper.find('HunkView').at(0); + hunkView2.find('LineView').at(1).find('.github-HunkView-line') + .simulate('mousedown', {button: 0, detail: 1}); + window.dispatchEvent(new MouseEvent('mouseup')); + hunkView2.find('LineView').at(2).find('.github-HunkView-line') + .simulate('mousedown', {button: 0, detail: 1, metaKey: true}); + window.dispatchEvent(new MouseEvent('mouseup')); + + const opPromise2 = switchboard.getFinishStageOperationPromise(); + hunkView2.find('button.github-HunkView-stageButton').simulate('click'); + await opPromise2; + + await refreshRepository(wrapper); + + const expectedLines2 = originalLines.slice(); + expectedLines2.splice(2, 0, + 'this is a new line', + 'this is another new line', + ); + assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedLines2.join('\n')); + + // unstage the rest of the hunk + commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'github:toggle-patch-selection-mode'); + + const opPromise3 = switchboard.getFinishStageOperationPromise(); + wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); + await opPromise3; + + assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), originalLines.join('\n')); + }); + + // https://github.com/atom/github/issues/417 + describe('when unstaging the last lines/hunks from a file', function() { + it('removes added files from index when last hunk is unstaged', async function() { + const absFilePath = path.join(workdirPath, 'new-file.txt'); + + fs.writeFileSync(absFilePath, 'foo\n'); + await repository.stageFiles(['new-file.txt']); + + const wrapper = mount(React.cloneElement(component, { + filePath: 'new-file.txt', + initialStagingStatus: 'staged', + })); + + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + + const opPromise = switchboard.getFinishStageOperationPromise(); + wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); + await opPromise; + + const stagedChanges = await repository.getStagedChanges(); + assert.equal(stagedChanges.length, 0); + }); + + it('removes added files from index when last lines are unstaged', async function() { + const absFilePath = path.join(workdirPath, 'new-file.txt'); + + fs.writeFileSync(absFilePath, 'foo\n'); + await repository.stageFiles(['new-file.txt']); + + const wrapper = mount(React.cloneElement(component, { + filePath: 'new-file.txt', + initialStagingStatus: 'staged', + })); + + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + + const viewNode = wrapper.find('FilePatchView').getDOMNode(); + commandRegistry.dispatch(viewNode, 'github:toggle-patch-selection-mode'); + commandRegistry.dispatch(viewNode, 'core:select-all'); + + const opPromise = switchboard.getFinishStageOperationPromise(); + wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); + await opPromise; + + const stagedChanges = await repository.getStagedChanges(); + assert.lengthOf(stagedChanges, 0); + }); + }); + + // https://github.com/atom/github/issues/341 + describe('when duplicate staging occurs', function() { + it('avoids patch conflicts with pending line staging operations', async function() { + const absFilePath = path.join(workdirPath, filePath); + const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); + + // write some unstaged changes + const unstagedLines = originalLines.slice(); + unstagedLines.splice(1, 0, + 'this is a modified line', + 'this is a new line', + 'this is another new line', + ); + unstagedLines.splice(11, 2, 'this is a modified line'); + fs.writeFileSync(absFilePath, unstagedLines.join('\n')); + + const wrapper = mount(component); + + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + const hunkView0 = wrapper.find('HunkView').at(0); + hunkView0.find('LineView').at(1).find('.github-HunkView-line') + .simulate('mousedown', {button: 0, detail: 1}); + window.dispatchEvent(new MouseEvent('mouseup')); + + // stage lines in rapid succession + // second stage action is a no-op since the first staging operation is in flight + const line1StagingPromise = switchboard.getFinishStageOperationPromise(); + hunkView0.find('.github-HunkView-stageButton').simulate('click'); + hunkView0.find('.github-HunkView-stageButton').simulate('click'); + await line1StagingPromise; + + const changePatchPromise = switchboard.getChangePatchPromise(); + + // assert that only line 1 has been staged + await refreshRepository(wrapper); // clear the cached file patches + let expectedLines = originalLines.slice(); + expectedLines.splice(1, 0, + 'this is a modified line', + ); + let actualLines = await repository.readFileFromIndex(filePath); + assert.autocrlfEqual(actualLines, expectedLines.join('\n')); + await changePatchPromise; + wrapper.update(); + + const hunkView1 = wrapper.find('HunkView').at(0); + hunkView1.find('LineView').at(2).find('.github-HunkView-line') + .simulate('mousedown', {button: 0, detail: 1}); + window.dispatchEvent(new MouseEvent('mouseup')); + const line2StagingPromise = switchboard.getFinishStageOperationPromise(); + hunkView1.find('.github-HunkView-stageButton').simulate('click'); + await line2StagingPromise; + + // assert that line 2 has now been staged + expectedLines = originalLines.slice(); + expectedLines.splice(1, 0, + 'this is a modified line', + 'this is a new line', + ); + actualLines = await repository.readFileFromIndex(filePath); + assert.autocrlfEqual(actualLines, expectedLines.join('\n')); + }); + + it('avoids patch conflicts with pending hunk staging operations', async function() { + const absFilePath = path.join(workdirPath, filePath); + const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); + + // write some unstaged changes + const unstagedLines = originalLines.slice(); + unstagedLines.splice(1, 0, + 'this is a modified line', + 'this is a new line', + 'this is another new line', + ); + unstagedLines.splice(11, 2, 'this is a modified line'); + fs.writeFileSync(absFilePath, unstagedLines.join('\n')); + + const wrapper = mount(component); + + await assert.async.isTrue(wrapper.update().find('HunkView').exists()); + + // ensure staging the same hunk twice does not cause issues + // second stage action is a no-op since the first staging operation is in flight + const hunk1StagingPromise = switchboard.getFinishStageOperationPromise(); + wrapper.find('HunkView').at(0).find('.github-HunkView-stageButton').simulate('click'); + wrapper.find('HunkView').at(0).find('.github-HunkView-stageButton').simulate('click'); + await hunk1StagingPromise; + + const patchPromise0 = switchboard.getChangePatchPromise(); + await refreshRepository(wrapper); // clear the cached file patches + const modifiedFilePatch = await repository.getFilePatchForPath(filePath); + wrapper.setState({filePatch: modifiedFilePatch}); + await patchPromise0; + + let expectedLines = originalLines.slice(); + expectedLines.splice(1, 0, + 'this is a modified line', + 'this is a new line', + 'this is another new line', + ); + let actualLines = await repository.readFileFromIndex(filePath); + assert.autocrlfEqual(actualLines, expectedLines.join('\n')); + + const hunk2StagingPromise = switchboard.getFinishStageOperationPromise(); + wrapper.find('HunkView').at(0).find('.github-HunkView-stageButton').simulate('click'); + await hunk2StagingPromise; + + expectedLines = originalLines.slice(); + expectedLines.splice(1, 0, + 'this is a modified line', + 'this is a new line', + 'this is another new line', + ); + expectedLines.splice(11, 2, 'this is a modified line'); + actualLines = await repository.readFileFromIndex(filePath); + assert.autocrlfEqual(actualLines, expectedLines.join('\n')); + }); + }); + }); + }); +}); From 52e36e127a96ae556c91beec1513f56c96c09d3a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Jun 2018 16:16:50 -0400 Subject: [PATCH 0012/4053] :fire: unused spies --- test/containers/file-patch-container.test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/containers/file-patch-container.test.js b/test/containers/file-patch-container.test.js index 2f01eb3772..02fdd93eed 100644 --- a/test/containers/file-patch-container.test.js +++ b/test/containers/file-patch-container.test.js @@ -14,8 +14,6 @@ describe('FilePatchContainer', function() { const workdirPath = await cloneRepository(); repository = await buildRepository(workdirPath); - sinon.spy(repository, 'getFilePatchForPath'); - sinon.spy(repository, 'isPartiallyStaged'); // a.txt: unstaged changes await fs.writeFile(path.join(workdirPath, 'a.txt'), 'changed\n'); From f872f576c9b7bde450110643ed78744e5b725ad4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Jun 2018 16:17:47 -0400 Subject: [PATCH 0013/4053] Out of the way, old FilePatchView tests --- .../{file-patch-view.test.js => file-patch-view.test.old.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/views/{file-patch-view.test.js => file-patch-view.test.old.js} (100%) diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.old.js similarity index 100% rename from test/views/file-patch-view.test.js rename to test/views/file-patch-view.test.old.js From d64d8b30c5931c0989d33b4c186a9ef7c7ba79fe Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 7 Jun 2018 09:28:33 -0400 Subject: [PATCH 0014/4053] (Failing) tests for FilePatchView --- test/views/file-patch-view.test.js | 60 ++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 test/views/file-patch-view.test.js diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js new file mode 100644 index 0000000000..c5225a1564 --- /dev/null +++ b/test/views/file-patch-view.test.js @@ -0,0 +1,60 @@ +import path from 'path'; +import fs from 'fs-extra'; +import React from 'react'; +import {mount} from 'enzyme'; + +import {cloneRepository, buildRepository} from '../helpers'; +import FilePatchView from '../../lib/views/file-patch-view'; + +describe('FilePatchView', function() { + let atomEnv, filePatch; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + + const workdirPath = await cloneRepository(); + const repository = await buildRepository(workdirPath); + + // a.txt: unstaged changes + await fs.writeFile(path.join(workdirPath, 'a.txt'), 'changed\n'); + filePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(overrideProps = {}) { + const props = { + stagingStatus: 'unstaged', + isPartiallyStaged: false, + filePatch, + tooltips: atomEnv.tooltips, + ...overrideProps, + }; + + return ; + } + + it('renders the file header', function() { + const wrapper = mount(buildApp()); + assert.isTrue(wrapper.find('FilePatchHeader').exists()); + }); + + it('renders the file patch within an editor', function() { + const wrapper = mount(buildApp()); + + const editor = wrapper.find('AtomTextEditor'); + assert.strictEqual(editor.instance().getModel().getText(), filePatch.present().getText()); + }); + + it('renders a header for each hunk'); + + describe('hunk lines', function() { + it('decorates added lines'); + + it('decorates deleted lines'); + + it('decorates the nonewlines line'); + }); +}); From b1e1fd8e97dcd1b573825ef72e1d45bfa054da77 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 7 Jun 2018 10:57:10 -0400 Subject: [PATCH 0015/4053] Write and test a FilePatchHeaderView component --- lib/views/file-patch-header-view.js | 135 +++++++++++++++++++ test/views/file-patch-header-view.test.js | 154 ++++++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 lib/views/file-patch-header-view.js create mode 100644 test/views/file-patch-header-view.test.js diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js new file mode 100644 index 0000000000..3514460e6b --- /dev/null +++ b/lib/views/file-patch-header-view.js @@ -0,0 +1,135 @@ +import React, {Fragment} from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; + +import RefHolder from '../models/ref-holder'; +import Tooltip from '../atom/tooltip'; + +export default class FilePatchHeaderView extends React.Component { + static propTypes = { + relPath: PropTypes.string.isRequired, + stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, + isPartiallyStaged: PropTypes.bool.isRequired, + hasHunks: PropTypes.bool.isRequired, + hasUndoHistory: PropTypes.bool.isRequired, + + tooltips: PropTypes.object.isRequired, + + undoLastDiscard: PropTypes.func.isRequired, + diveIntoMirrorPatch: PropTypes.func.isRequired, + openFile: PropTypes.func.isRequired, + toggleFile: PropTypes.func.isRequired, + }; + + constructor(props) { + super(props); + + this.refMirrorButton = new RefHolder(); + this.refOpenFileButton = new RefHolder(); + } + + render() { + return ( +
+ + {this.renderTitle()} + + {this.renderButtonGroup()} +
+ ); + } + + renderTitle() { + const status = this.props.stagingStatus; + return `${status[0].toUpperCase()}${status.slice(1)} Changes for ${this.props.relPath}`; + } + + renderButtonGroup() { + return ( + + {this.renderUndoDiscardButton()} + {this.renderMirrorPatchButton()} + {this.renderOpenFileButton()} + {this.renderToggleFileButton()} + + ); + } + + renderUndoDiscardButton() { + if (!this.props.hasUndoHistory || this.props.stagingStatus !== 'unstaged') { + return null; + } + + return ( + + ); + } + + renderMirrorPatchButton() { + if (!this.props.isPartiallyStaged && this.props.hasHunks) { + return null; + } + + const attrs = this.props.stagingStatus === 'unstaged' + ? { + iconClass: 'icon-tasklist', + tooltipText: 'View staged changes', + } + : { + iconClass: 'icon-list-unordered', + tooltipText: 'View unstaged changes', + }; + + return ( + + + ); + } +} diff --git a/test/views/file-patch-header-view.test.js b/test/views/file-patch-header-view.test.js new file mode 100644 index 0000000000..d56eb3605b --- /dev/null +++ b/test/views/file-patch-header-view.test.js @@ -0,0 +1,154 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import FilePatchHeaderView from '../../lib/views/file-patch-header-view'; + +describe('FilePatchHeaderView', function() { + let atomEnv; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(overrideProps = {}) { + return ( + {}} + diveIntoMirrorPatch={() => {}} + openFile={() => {}} + toggleFile={() => {}} + + {...overrideProps} + /> + ); + } + + describe('the title', function() { + it('renders for an unstaged patch', function() { + const wrapper = shallow(buildApp({stagingStatus: 'unstaged'})); + assert.strictEqual(wrapper.find('.github-FilePatchView-title').text(), 'Unstaged Changes for dir/a.txt'); + }); + + it('renders for a staged patch', function() { + const wrapper = shallow(buildApp({stagingStatus: 'staged'})); + assert.strictEqual(wrapper.find('.github-FilePatchView-title').text(), 'Staged Changes for dir/a.txt'); + }); + }); + + describe('the button group', function() { + it('includes undo discard if undo history is available and the patch is unstaged', function() { + const undoLastDiscard = sinon.stub(); + const wrapper = shallow(buildApp({hasUndoHistory: true, stagingStatus: 'unstaged', undoLastDiscard})); + assert.isTrue(wrapper.find('button.icon-history').exists()); + + wrapper.find('button.icon-history').simulate('click'); + assert.isTrue(undoLastDiscard.called); + + wrapper.setProps({hasUndoHistory: false, stagingStatus: 'unstaged'}); + assert.isFalse(wrapper.find('button.icon-history').exists()); + + wrapper.setProps({hasUndoHistory: true, stagingStatus: 'staged'}); + assert.isFalse(wrapper.find('button.icon-history').exists()); + }); + + function createPatchToggleTest({overrideProps, stagingStatus, buttonClass, oppositeButtonClass, tooltip}) { + return function() { + const diveIntoMirrorPatch = sinon.stub(); + const wrapper = shallow(buildApp({stagingStatus, diveIntoMirrorPatch, ...overrideProps})); + + assert.isTrue(wrapper.find(`button.${buttonClass}`).exists(), + `${buttonClass} expected, but not found`); + assert.isFalse(wrapper.find(`button.${oppositeButtonClass}`).exists(), + `${oppositeButtonClass} not expected, but found`); + + wrapper.find(`button.${buttonClass}`).simulate('click'); + assert.isTrue(diveIntoMirrorPatch.called, `${buttonClass} click did nothing`); + + assert.isTrue(wrapper.find('Tooltip').someWhere(n => n.prop('title') === tooltip)); + }; + } + + function createUnstagedPatchToggleTest(overrideProps) { + return createPatchToggleTest({ + overrideProps, + stagingStatus: 'unstaged', + buttonClass: 'icon-tasklist', + oppositeButtonClass: 'icon-list-unordered', + tooltip: 'View staged changes', + }); + } + + function createStagedPatchToggleTest(overrideProps) { + return createPatchToggleTest({ + overrideProps, + stagingStatus: 'staged', + buttonClass: 'icon-list-unordered', + oppositeButtonClass: 'icon-tasklist', + tooltip: 'View unstaged changes', + }); + } + + describe('when the patch is partially staged', function() { + const props = {isPartiallyStaged: true}; + + it('includes a toggle to staged button when unstaged', createUnstagedPatchToggleTest(props)); + + it('includes a toggle to unstaged button when staged', createStagedPatchToggleTest(props)); + }); + + describe('when the patch contains no hunks', function() { + const props = {hasHunks: false}; + + it('includes a toggle to staged button when unstaged', createUnstagedPatchToggleTest(props)); + + it('includes a toggle to unstaged button when staged', createStagedPatchToggleTest(props)); + }); + + it('includes an open file button', function() { + const openFile = sinon.stub(); + const wrapper = shallow(buildApp({openFile})); + + wrapper.find('button.icon-code').simulate('click'); + assert.isTrue(openFile.called); + }); + + function createToggleFileTest({stagingStatus, buttonClass, oppositeButtonClass}) { + return function() { + const toggleFile = sinon.stub(); + const wrapper = shallow(buildApp({toggleFile, stagingStatus})); + + assert.isTrue(wrapper.find(`button.${buttonClass}`).exists(), + `${buttonClass} expected, but not found`); + assert.isFalse(wrapper.find(`button.${oppositeButtonClass}`).exists(), + `${oppositeButtonClass} not expected, but found`); + + wrapper.find(`button.${buttonClass}`).simulate('click'); + assert.isTrue(toggleFile.called, `${buttonClass} click did nothing`); + }; + } + + it('includes a stage file button when unstaged', createToggleFileTest({ + stagingStatus: 'unstaged', + buttonClass: 'icon-move-down', + oppositeButtonClass: 'icon-move-up', + })); + + it('includes an unstage file button when staged', createToggleFileTest({ + stagingStatus: 'staged', + buttonClass: 'icon-move-up', + oppositeButtonClass: 'icon-move-down', + })); + }); +}); From 7cdfaf223725eb862befea373b8e3f412a51aa3d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 7 Jun 2018 11:11:04 -0400 Subject: [PATCH 0016/4053] Render the FilePatchHeaderView within the FilePatchView --- lib/views/file-patch-view.js | 77 ++++++++++++------------------ test/views/file-patch-view.test.js | 14 ++++-- 2 files changed, 41 insertions(+), 50 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index f6244e056a..538ba31bf2 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -6,14 +6,22 @@ import FilePatchSelection from '../models/file-patch-selection'; import AtomTextEditor from '../atom/atom-text-editor'; import Marker from '../atom/marker'; import Decoration from '../atom/decoration'; +import FilePatchHeaderView from './file-patch-header-view'; export default class FilePatchView extends React.Component { static propTypes = { + relPath: PropTypes.string.isRequired, stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, isPartiallyStaged: PropTypes.bool.isRequired, filePatch: PropTypes.object.isRequired, + repository: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, + + undoLastDiscard: PropTypes.func.isRequired, + diveIntoMirrorPatch: PropTypes.func.isRequired, + openFile: PropTypes.func.isRequired, + toggleFile: PropTypes.func.isRequired, } constructor(props) { @@ -38,59 +46,34 @@ export default class FilePatchView extends React.Component { render() { return (
- - - - {this.renderFileHeader()} - - - + 0} + hasUndoHistory={this.props.repository.hasDiscardHistory(this.props.relPath)} -
- ); - } + tooltips={this.props.tooltips} - renderFileHeader() { - return ( -
- - {this.isUnstaged() ? 'Unstaged Changes for ' : 'Staged Changes for '} - {this.props.filePatch.getPath()} - - {this.renderButtonGroup()} -
- ); - } + undoLastDiscard={this.props.undoLastDiscard} + diveIntoMirrorPatch={this.props.diveIntoMirrorPatch} + openFile={this.props.openFile} + toggleFile={this.props.toggleFile} + /> - renderButtonGroup() { - const hasHunks = this.props.filePatch.getHunks().length > 0; +
+ + + + + + +
- return ( - - {this.props.isPartiallyStaged || !hasHunks ? ( - - ) : null } - +
); } - - isUnstaged() { - return this.props.stagingStatus === 'unstaged'; - } } diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index c5225a1564..2d4b8095dc 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -7,13 +7,13 @@ import {cloneRepository, buildRepository} from '../helpers'; import FilePatchView from '../../lib/views/file-patch-view'; describe('FilePatchView', function() { - let atomEnv, filePatch; + let atomEnv, repository, filePatch; beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); const workdirPath = await cloneRepository(); - const repository = await buildRepository(workdirPath); + repository = await buildRepository(workdirPath); // a.txt: unstaged changes await fs.writeFile(path.join(workdirPath, 'a.txt'), 'changed\n'); @@ -26,10 +26,18 @@ describe('FilePatchView', function() { function buildApp(overrideProps = {}) { const props = { + relPath: 'a.txt', stagingStatus: 'unstaged', isPartiallyStaged: false, filePatch, + repository, tooltips: atomEnv.tooltips, + + undoLastDiscard: () => {}, + diveIntoMirrorPatch: () => {}, + openFile: () => {}, + toggleFile: () => {}, + ...overrideProps, }; @@ -38,7 +46,7 @@ describe('FilePatchView', function() { it('renders the file header', function() { const wrapper = mount(buildApp()); - assert.isTrue(wrapper.find('FilePatchHeader').exists()); + assert.isTrue(wrapper.find('FilePatchHeaderView').exists()); }); it('renders the file patch within an editor', function() { From 36523deafec0ec228bda09be97c1af6a0cfa4e57 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 7 Jun 2018 14:15:29 -0400 Subject: [PATCH 0017/4053] Render FilePatchMetaViews for symlink and file mode changes --- lib/views/file-patch-meta-view.js | 37 +++++++ lib/views/file-patch-view.js | 130 +++++++++++++++++++++++- test/views/file-patch-meta-view.test.js | 53 ++++++++++ test/views/file-patch-view.test.js | 114 ++++++++++++++++++++- 4 files changed, 331 insertions(+), 3 deletions(-) create mode 100644 lib/views/file-patch-meta-view.js create mode 100644 test/views/file-patch-meta-view.test.js diff --git a/lib/views/file-patch-meta-view.js b/lib/views/file-patch-meta-view.js new file mode 100644 index 0000000000..4edbb69417 --- /dev/null +++ b/lib/views/file-patch-meta-view.js @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; + +export default class FilePatchMetaView extends React.Component { + static propTypes = { + title: PropTypes.string.isRequired, + actionIcon: PropTypes.string.isRequired, + actionText: PropTypes.string.isRequired, + + action: PropTypes.func.isRequired, + + children: PropTypes.element.isRequired, + }; + + render() { + return ( +
+
+
+

{this.props.title}

+
+ +
+
+
+ {this.props.children} +
+
+
+ ); + } +} diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 538ba31bf2..056573a260 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; @@ -7,6 +7,12 @@ import AtomTextEditor from '../atom/atom-text-editor'; import Marker from '../atom/marker'; import Decoration from '../atom/decoration'; import FilePatchHeaderView from './file-patch-header-view'; +import FilePatchMetaView from './file-patch-meta-view'; + +const executableText = { + 100644: 'non executable', + 100755: 'executable', +}; export default class FilePatchView extends React.Component { static propTypes = { @@ -22,6 +28,8 @@ export default class FilePatchView extends React.Component { diveIntoMirrorPatch: PropTypes.func.isRequired, openFile: PropTypes.func.isRequired, toggleFile: PropTypes.func.isRequired, + toggleModeChange: PropTypes.func.isRequired, + toggleSymlinkChange: PropTypes.func.isRequired, } constructor(props) { @@ -68,6 +76,10 @@ export default class FilePatchView extends React.Component { + + {this.renderExecutableModeChangeMeta()} + {this.renderSymlinkChangeMeta()} + @@ -76,4 +88,120 @@ export default class FilePatchView extends React.Component { ); } + + renderExecutableModeChangeMeta() { + if (!this.props.filePatch.didChangeExecutableMode()) { + return null; + } + + const oldMode = this.props.filePatch.getOldMode(); + const newMode = this.props.filePatch.getNewMode(); + + const attrs = this.props.stagingStatus === 'unstaged' + ? { + actionIcon: 'icon-move-down', + actionText: 'Stage Mode Change', + } + : { + actionIcon: 'icon-move-up', + actionText: 'Unstage Mode Change', + }; + + return ( + + + File changed mode + + from {executableText[oldMode]} {oldMode} + + + to {executableText[newMode]} {newMode} + + + + ); + } + + renderSymlinkChangeMeta() { + if (!this.props.filePatch.hasSymlink()) { + return null; + } + + let detail =
; + let title = ''; + const oldSymlink = this.props.filePatch.getOldSymlink(); + const newSymlink = this.props.filePatch.getNewSymlink(); + if (oldSymlink && newSymlink) { + detail = ( + + Symlink changed + + from {oldSymlink} + + + to {newSymlink} + . + + ); + title = 'Symlink changed'; + } else if (oldSymlink && !newSymlink) { + detail = ( + + Symlink + + to {oldSymlink} + + deleted. + + ); + title = 'Symlink deleted'; + } else if (!oldSymlink && newSymlink) { + detail = ( + + Symlink + + to {newSymlink} + + created. + + ); + title = 'Symlink created'; + } else { + return null; + } + + const attrs = this.props.stagingStatus === 'unstaged' + ? { + actionIcon: 'icon-move-down', + actionText: 'Stage Symlink Change', + } + : { + actionIcon: 'icon-move-up', + actionText: 'Unstage Symlink Change', + }; + + return ( + + + {detail} + + + ); + } } diff --git a/test/views/file-patch-meta-view.test.js b/test/views/file-patch-meta-view.test.js new file mode 100644 index 0000000000..8602e9a9f5 --- /dev/null +++ b/test/views/file-patch-meta-view.test.js @@ -0,0 +1,53 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import FilePatchMetaView from '../../lib/views/file-patch-meta-view'; + +describe('FilePatchMetaView', function() { + let atomEnv; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(overrideProps = {}, children =
) { + return ( + {}} + + {...overrideProps}> + {children} + + ); + } + + it('renders the title', function() { + const wrapper = shallow(buildApp({title: 'Yes'})); + assert.strictEqual(wrapper.find('.github-FilePatchView-metaTitle').text(), 'Yes'); + }); + + it('renders a control button with the correct text and callback', function() { + const action = sinon.stub(); + const wrapper = shallow(buildApp({action, actionText: 'do the thing', actionIcon: 'icon-move-down'})); + + const button = wrapper.find('button.icon-move-down'); + + assert.strictEqual(button.text(), 'do the thing'); + + button.simulate('click'); + assert.isTrue(action.called); + }); + + it('renders child elements as details', function() { + const wrapper = shallow(buildApp({},
)); + assert.isTrue(wrapper.find('.github-FilePatchView-metaDetails .child').exists()); + }); +}); diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 2d4b8095dc..e5d477cc96 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -1,7 +1,7 @@ import path from 'path'; import fs from 'fs-extra'; import React from 'react'; -import {mount} from 'enzyme'; +import {shallow, mount} from 'enzyme'; import {cloneRepository, buildRepository} from '../helpers'; import FilePatchView from '../../lib/views/file-patch-view'; @@ -37,6 +37,8 @@ describe('FilePatchView', function() { diveIntoMirrorPatch: () => {}, openFile: () => {}, toggleFile: () => {}, + toggleModeChange: () => {}, + toggleSymlinkChange: () => {}, ...overrideProps, }; @@ -45,7 +47,7 @@ describe('FilePatchView', function() { } it('renders the file header', function() { - const wrapper = mount(buildApp()); + const wrapper = shallow(buildApp()); assert.isTrue(wrapper.find('FilePatchHeaderView').exists()); }); @@ -56,6 +58,114 @@ describe('FilePatchView', function() { assert.strictEqual(editor.instance().getModel().getText(), filePatch.present().getText()); }); + describe('executable mode changes', function() { + it('does not render if the mode has not changed', function() { + sinon.stub(filePatch, 'getOldMode').returns('100644'); + sinon.stub(filePatch, 'getNewMode').returns('100644'); + + const wrapper = shallow(buildApp()); + assert.isFalse(wrapper.find('FilePatchMetaView[title="Mode change"]').exists()); + }); + + it('renders change details within a meta container', function() { + sinon.stub(filePatch, 'getOldMode').returns('100644'); + sinon.stub(filePatch, 'getNewMode').returns('100755'); + + const wrapper = mount(buildApp({stagingStatus: 'unstaged'})); + + const meta = wrapper.find('FilePatchMetaView[title="Mode change"]'); + assert.isTrue(meta.exists()); + assert.strictEqual(meta.prop('actionIcon'), 'icon-move-down'); + assert.strictEqual(meta.prop('actionText'), 'Stage Mode Change'); + + const details = meta.find('.github-FilePatchView-metaDetails'); + assert.strictEqual(details.text(), 'File changed modefrom non executable 100644to executable 100755'); + }); + + it("stages or unstages the mode change when the meta container's action is triggered", function() { + sinon.stub(filePatch, 'getOldMode').returns('100644'); + sinon.stub(filePatch, 'getNewMode').returns('100755'); + + const toggleModeChange = sinon.stub(); + const wrapper = shallow(buildApp({stagingStatus: 'staged', toggleModeChange})); + + const meta = wrapper.find('FilePatchMetaView[title="Mode change"]'); + assert.isTrue(meta.exists()); + assert.strictEqual(meta.prop('actionIcon'), 'icon-move-up'); + assert.strictEqual(meta.prop('actionText'), 'Unstage Mode Change'); + + meta.prop('action')(); + assert.isTrue(toggleModeChange.called); + }); + }); + + describe('symlink changes', function() { + it('does not render if the symlink status is unchanged', function() { + const wrapper = mount(buildApp()); + assert.lengthOf(wrapper.find('FilePatchMetaView').filterWhere(v => v.prop('title').startsWith('Symlink')), 0); + }); + + it('renders symlink change information within a meta container', function() { + sinon.stub(filePatch, 'hasSymlink').returns(true); + sinon.stub(filePatch, 'getOldSymlink').returns('/old.txt'); + sinon.stub(filePatch, 'getNewSymlink').returns('/new.txt'); + + const wrapper = mount(buildApp({stagingStatus: 'unstaged'})); + const meta = wrapper.find('FilePatchMetaView[title="Symlink changed"]'); + assert.isTrue(meta.exists()); + assert.strictEqual(meta.prop('actionIcon'), 'icon-move-down'); + assert.strictEqual(meta.prop('actionText'), 'Stage Symlink Change'); + assert.strictEqual( + meta.find('.github-FilePatchView-metaDetails').text(), + 'Symlink changedfrom /old.txtto /new.txt.', + ); + }); + + it('stages or unstages the symlink change', function() { + const toggleSymlinkChange = sinon.stub(); + sinon.stub(filePatch, 'hasSymlink').returns(true); + sinon.stub(filePatch, 'getOldSymlink').returns('/old.txt'); + sinon.stub(filePatch, 'getNewSymlink').returns('/new.txt'); + + const wrapper = mount(buildApp({stagingStatus: 'staged', toggleSymlinkChange})); + const meta = wrapper.find('FilePatchMetaView[title="Symlink changed"]'); + assert.isTrue(meta.exists()); + assert.strictEqual(meta.prop('actionIcon'), 'icon-move-up'); + assert.strictEqual(meta.prop('actionText'), 'Unstage Symlink Change'); + + meta.find('button.icon-move-up').simulate('click'); + assert.isTrue(toggleSymlinkChange.called); + }); + + it('renders details for a symlink deletion', function() { + sinon.stub(filePatch, 'hasSymlink').returns(true); + sinon.stub(filePatch, 'getOldSymlink').returns('/old.txt'); + sinon.stub(filePatch, 'getNewSymlink').returns(null); + + const wrapper = mount(buildApp()); + const meta = wrapper.find('FilePatchMetaView[title="Symlink deleted"]'); + assert.isTrue(meta.exists()); + assert.strictEqual( + meta.find('.github-FilePatchView-metaDetails').text(), + 'Symlinkto /old.txtdeleted.', + ); + }); + + it('renders details for a symlink creation', function() { + sinon.stub(filePatch, 'hasSymlink').returns(true); + sinon.stub(filePatch, 'getOldSymlink').returns(null); + sinon.stub(filePatch, 'getNewSymlink').returns('/new.txt'); + + const wrapper = mount(buildApp()); + const meta = wrapper.find('FilePatchMetaView[title="Symlink created"]'); + assert.isTrue(meta.exists()); + assert.strictEqual( + meta.find('.github-FilePatchView-metaDetails').text(), + 'Symlinkto /new.txtcreated.', + ); + }); + }); + it('renders a header for each hunk'); describe('hunk lines', function() { From 33595a847fe7294d975ef2994288640e43bc51de Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 9 Jun 2018 20:38:52 -0400 Subject: [PATCH 0018/4053] HunkHeaderView --- lib/views/hunk-header-view.js | 60 ++++++++++++++++++++++ test/views/hunk-header-view.test.js | 78 +++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 lib/views/hunk-header-view.js create mode 100644 test/views/hunk-header-view.test.js diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js new file mode 100644 index 0000000000..e0ba9f9b46 --- /dev/null +++ b/lib/views/hunk-header-view.js @@ -0,0 +1,60 @@ +import React, {Fragment} from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; + +import RefHolder from '../models/ref-holder'; +import Tooltip from '../atom/tooltip'; + +export default class HunkHeaderView extends React.Component { + static propTypes = { + hunk: PropTypes.object.isRequired, + isSelected: PropTypes.bool.isRequired, + stagingStatus: PropTypes.oneOf(['unstaged', 'staged']).isRequired, + selectionMode: PropTypes.oneOf(['hunk', 'line']).isRequired, + toggleSelectionLabel: PropTypes.string.isRequired, + discardSelectionLabel: PropTypes.string.isRequired, + + tooltips: PropTypes.object.isRequired, + + toggleSelection: PropTypes.func.isRequired, + discardSelection: PropTypes.func.isRequired, + }; + + constructor(props) { + super(props); + + this.refDiscardButton = new RefHolder(); + } + + render() { + const conditional = { + 'is-selected': this.props.isSelected, + 'is-hunkMode': this.props.selectionMode === 'hunk', + }; + + return ( +
+ + {this.props.hunk.getHeader().trim()} {this.props.hunk.getSectionHeading().trim()} + + + {this.props.stagingStatus === 'unstaged' && ( + +
+ ); + } +} diff --git a/test/views/hunk-header-view.test.js b/test/views/hunk-header-view.test.js new file mode 100644 index 0000000000..525aa9a10c --- /dev/null +++ b/test/views/hunk-header-view.test.js @@ -0,0 +1,78 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import HunkHeaderView from '../../lib/views/hunk-header-view'; +import Hunk from '../../lib/models/hunk'; + +describe('HunkHeaderView', function() { + let atomEnv, hunk; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + hunk = new Hunk(0, 1, 10, 11, 'section heading', []); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(overrideProps = {}) { + return ( + {}} + discardSelection={() => {}} + + {...overrideProps} + /> + ); + } + + it('applies a CSS class when selected', function() { + const wrapper = shallow(buildApp({isSelected: true})); + assert.isTrue(wrapper.find('.github-HunkHeaderView').hasClass('is-selected')); + + wrapper.setProps({isSelected: false}); + assert.isFalse(wrapper.find('.github-HunkHeaderView').hasClass('is-selected')); + }); + + it('applies a CSS class in hunk selection mode', function() { + const wrapper = shallow(buildApp({selectionMode: 'hunk'})); + assert.isTrue(wrapper.find('.github-HunkHeaderView').hasClass('is-hunkMode')); + + wrapper.setProps({selectionMode: 'line'}); + assert.isFalse(wrapper.find('.github-HunkHeaderView').hasClass('is-hunkMode')); + }); + + it('renders the hunk header title', function() { + const wrapper = shallow(buildApp()); + assert.strictEqual(wrapper.find('.github-HunkHeaderView-title').text(), '@@ -0,10 +1,11 @@ section heading'); + }); + + it('renders a button to toggle the selection', function() { + const toggleSelection = sinon.stub(); + const wrapper = shallow(buildApp({toggleSelectionLabel: 'Do the thing', toggleSelection})); + const button = wrapper.find('button.github-HunkHeaderView-stageButton'); + assert.strictEqual(button.text(), 'Do the thing'); + button.simulate('click'); + assert.isTrue(toggleSelection.called); + }); + + it('renders a button to discard an unstaged selection', function() { + const discardSelection = sinon.stub(); + const wrapper = shallow(buildApp({stagingStatus: 'unstaged', discardSelectionLabel: 'Nope', discardSelection})); + const button = wrapper.find('button.github-HunkHeaderView-discardButton'); + assert.isTrue(button.exists()); + assert.isTrue(wrapper.find('Tooltip[title="Nope"]').exists()); + button.simulate('click'); + assert.isTrue(discardSelection.called); + }); +}); From 381ff5b6a183a179942ac9938b3a6b7982bf1b6d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 11 Jun 2018 11:15:49 -0400 Subject: [PATCH 0019/4053] Use PropTypes.node instead of PropTypes.element --- lib/atom/atom-text-editor.js | 2 +- lib/atom/decoration.js | 2 +- lib/atom/marker-layer.js | 2 +- lib/atom/marker.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index ab7eedf561..b2b77c276e 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -43,7 +43,7 @@ export default class AtomTextEditor extends React.PureComponent { text: PropTypes.string, didChange: PropTypes.func, didChangeCursorPosition: PropTypes.func, - children: PropTypes.element, + children: PropTypes.node, } static defaultProps = { diff --git a/lib/atom/decoration.js b/lib/atom/decoration.js index 121f9c46ed..1da7256eac 100644 --- a/lib/atom/decoration.js +++ b/lib/atom/decoration.js @@ -16,7 +16,7 @@ class WrappedDecoration extends React.Component { type: PropTypes.oneOf(['line', 'line-number', 'highlight', 'overlay', 'gutter', 'block']).isRequired, position: PropTypes.oneOf(['head', 'tail', 'before', 'after']), className: PropTypes.string, - children: PropTypes.element, + children: PropTypes.node, itemHolder: RefHolderPropType, options: PropTypes.object, } diff --git a/lib/atom/marker-layer.js b/lib/atom/marker-layer.js index 0ff781c1e2..5bf1e2f9cb 100644 --- a/lib/atom/marker-layer.js +++ b/lib/atom/marker-layer.js @@ -17,7 +17,7 @@ class WrappedMarkerLayer extends React.Component { static propTypes = { ...markerLayerProps, editor: PropTypes.object, - children: PropTypes.element, + children: PropTypes.node, handleID: PropTypes.func, }; diff --git a/lib/atom/marker.js b/lib/atom/marker.js index 34ed5db4f4..72db45c203 100644 --- a/lib/atom/marker.js +++ b/lib/atom/marker.js @@ -41,7 +41,7 @@ class WrappedMarker extends React.Component { screenRange: RangePropType, screenPosition: PointPropType, markableHolder: RefHolderPropType, - children: PropTypes.element, + children: PropTypes.node, handleID: PropTypes.func, } From d876ddc9db2956c1e9bece43542a2d5637f73122 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 11 Jun 2018 11:52:24 -0400 Subject: [PATCH 0020/4053] Decorate a parent Marker or MarkerLayer --- lib/atom/decoration.js | 19 +++++++++++++------ lib/atom/marker-layer.js | 10 +++++++++- lib/atom/marker.js | 11 ++++++++++- test/atom/decoration.test.js | 12 ++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/lib/atom/decoration.js b/lib/atom/decoration.js index 1da7256eac..08ec2b901a 100644 --- a/lib/atom/decoration.js +++ b/lib/atom/decoration.js @@ -6,13 +6,14 @@ import {Disposable} from 'event-kit'; import {createItem, autobind} from '../helpers'; import {RefHolderPropType} from '../prop-types'; import {TextEditorContext} from './atom-text-editor'; -import {MarkerContext} from './marker'; +import {DecorableContext} from './marker'; import RefHolder from '../models/ref-holder'; class WrappedDecoration extends React.Component { static propTypes = { editorHolder: RefHolderPropType.isRequired, markerHolder: RefHolderPropType.isRequired, + decorateMethod: PropTypes.oneOf(['decorateMarker', 'decorateMarkerLayer']), type: PropTypes.oneOf(['line', 'line-number', 'highlight', 'overlay', 'gutter', 'block']).isRequired, position: PropTypes.oneOf(['head', 'tail', 'before', 'after']), className: PropTypes.string, @@ -22,6 +23,7 @@ class WrappedDecoration extends React.Component { } static defaultProps = { + decorateMethod: 'decorateMarker', options: {}, position: 'head', } @@ -102,7 +104,7 @@ class WrappedDecoration extends React.Component { const marker = this.props.markerHolder.get(); this.decorationHolder.setter( - editor.decorateMarker(marker, options), + editor[this.props.decorateMethod](marker, options), ); } @@ -160,11 +162,16 @@ export default class Decoration extends React.Component { return ( {editorHolder => ( - - {markerHolder => ( - + + {({holder, decorateMethod}) => ( + )} - + )} ); diff --git a/lib/atom/marker-layer.js b/lib/atom/marker-layer.js index 5bf1e2f9cb..8793c3ee50 100644 --- a/lib/atom/marker-layer.js +++ b/lib/atom/marker-layer.js @@ -5,6 +5,7 @@ import {Disposable} from 'event-kit'; import {autobind, extractProps} from '../helpers'; import RefHolder from '../models/ref-holder'; import {TextEditorContext} from './atom-text-editor'; +import {DecorableContext} from './marker'; const markerLayerProps = { maintainHistory: PropTypes.bool, @@ -35,6 +36,11 @@ class WrappedMarkerLayer extends React.Component { this.state = { editorHolder: RefHolder.on(this.props.editor), }; + + this.decorable = { + holder: this.layerHolder, + decorateMethod: 'decorateMarkerLayer', + }; } static getDerivedStateFromProps(props, state) { @@ -54,7 +60,9 @@ class WrappedMarkerLayer extends React.Component { render() { return ( - {this.props.children} + + {this.props.children} + ); } diff --git a/lib/atom/marker.js b/lib/atom/marker.js index 72db45c203..22e620f166 100644 --- a/lib/atom/marker.js +++ b/lib/atom/marker.js @@ -33,6 +33,8 @@ const markerProps = { export const MarkerContext = React.createContext(); +export const DecorableContext = React.createContext(); + class WrappedMarker extends React.Component { static propTypes = { ...markerProps, @@ -56,6 +58,11 @@ class WrappedMarker extends React.Component { this.sub = new Disposable(); this.markerHolder = new RefHolder(); + + this.decorable = { + holder: this.markerHolder, + decorateMethod: 'decorateMarker', + }; } componentDidMount() { @@ -65,7 +72,9 @@ class WrappedMarker extends React.Component { render() { return ( - {this.props.children} + + {this.props.children} + ); } diff --git a/test/atom/decoration.test.js b/test/atom/decoration.test.js index 45feeaee37..441c5da836 100644 --- a/test/atom/decoration.test.js +++ b/test/atom/decoration.test.js @@ -5,6 +5,7 @@ import {mount} from 'enzyme'; import Decoration from '../../lib/atom/decoration'; import AtomTextEditor from '../../lib/atom/atom-text-editor'; import Marker from '../../lib/atom/marker'; +import MarkerLayer from '../../lib/atom/marker-layer'; describe('Decoration', function() { let atomEnv, editor, marker; @@ -126,4 +127,15 @@ describe('Decoration', function() { assert.lengthOf(theEditor.getLineDecorations({position: 'head', class: 'whatever'}), 1); }); + + it('decorates a parent MarkerLayer', function() { + mount( + + + + + + , + ); + }); }); From d82f5649920fa88e0b72d7e57b5e73007f50cc68 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 11 Jun 2018 11:55:50 -0400 Subject: [PATCH 0021/4053] Render a HunkHeaderView for each hunk header --- lib/views/file-patch-view.js | 41 ++++++++++++++++++++++++++++++ test/views/file-patch-view.test.js | 12 ++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 056573a260..429b468215 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -8,6 +8,7 @@ import Marker from '../atom/marker'; import Decoration from '../atom/decoration'; import FilePatchHeaderView from './file-patch-header-view'; import FilePatchMetaView from './file-patch-meta-view'; +import HunkHeaderView from './hunk-header-view'; const executableText = { 100644: 'non executable', @@ -74,6 +75,7 @@ export default class FilePatchView extends React.Component {
+ @@ -82,6 +84,8 @@ export default class FilePatchView extends React.Component { + + {this.renderHunkHeaders()}
@@ -204,4 +208,41 @@ export default class FilePatchView extends React.Component { ); } + + renderHunkHeaders() { + const selectedHunks = this.state.selection.getSelectedHunks(); + const isHunkSelectionMode = this.state.selection.getMode() === 'hunk'; + const toggleVerb = this.props.stagingStatus === 'unstaged' ? 'Stage' : 'Unstage'; + + return this.props.filePatch.getHunks().map((hunk, index) => { + const isSelected = selectedHunks.has(hunk); + let buttonSuffix = (isHunkSelectionMode || !isSelected) ? ' Hunk' : ' Selection'; + if (isSelected && selectedHunks.size > 1) { + buttonSuffix += 's'; + } + const toggleSelectionLabel = `${toggleVerb}${buttonSuffix}`; + const discardSelectionLabel = `Discard${buttonSuffix}`; + const bufferPosition = this.state.presentedFilePatch.getHunkStartPositions()[index]; + + return ( + + + {}} + discardSelection={() => {}} + /> + + + ); + }); + } } diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index e5d477cc96..244972580c 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -166,7 +166,17 @@ describe('FilePatchView', function() { }); }); - it('renders a header for each hunk'); + it('renders a header for each hunk', function() { + const hunks = [ + new Hunk(0, 0, 5, 5, 'hunk 0', []), + new Hunk(10, 10, 15, 15, 'hunk 1', []), + ]; + sinon.stub(filePatch, 'getHunks').returns(hunks); + + const wrapper = mount(buildApp()); + assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[0])); + assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[1])); + }); describe('hunk lines', function() { it('decorates added lines'); From 0850afe1f92485448cd1a4396e7cee6dcff3d6a3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 11 Jun 2018 11:56:04 -0400 Subject: [PATCH 0022/4053] Decorate lines --- lib/views/file-patch-view.js | 27 ++++++++++++++ test/views/file-patch-view.test.js | 57 ++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 429b468215..b1ecb833e4 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -5,6 +5,7 @@ import cx from 'classnames'; import FilePatchSelection from '../models/file-patch-selection'; import AtomTextEditor from '../atom/atom-text-editor'; import Marker from '../atom/marker'; +import MarkerLayer from '../atom/marker-layer'; import Decoration from '../atom/decoration'; import FilePatchHeaderView from './file-patch-header-view'; import FilePatchMetaView from './file-patch-meta-view'; @@ -86,6 +87,20 @@ export default class FilePatchView extends React.Component { {this.renderHunkHeaders()} + + {this.renderLineDecorations( + this.state.presentedFilePatch.getAddedBufferPositions(), + 'github-FilePatchView-line--added', + )} + {this.renderLineDecorations( + this.state.presentedFilePatch.getDeletedBufferPositions(), + 'github-FilePatchView-line--deleted', + )} + {this.renderLineDecorations( + this.state.presentedFilePatch.getNoNewlineBufferPositions(), + 'github-FilePatchView-line--nonewline', + )} + @@ -245,4 +260,16 @@ export default class FilePatchView extends React.Component { ); }); } + + renderLineDecorations(positions, lineClass) { + return ( + + {positions.map((position, index) => { + return ; + })} + + + + ); + } } diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 244972580c..2209313379 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -4,6 +4,8 @@ import React from 'react'; import {shallow, mount} from 'enzyme'; import {cloneRepository, buildRepository} from '../helpers'; +import Hunk from '../../lib/models/hunk'; +import HunkLine from '../../lib/models/hunk-line'; import FilePatchView from '../../lib/views/file-patch-view'; describe('FilePatchView', function() { @@ -179,10 +181,59 @@ describe('FilePatchView', function() { }); describe('hunk lines', function() { - it('decorates added lines'); + it('decorates added lines', function() { + const hunks = [ + new Hunk(0, 0, 1, 1, 'hunk 0', [ + new HunkLine('line 0', 'added', 0, 1, 0), + new HunkLine('line 1', 'deleted', 0, 1, 0), + ]), + ]; + sinon.stub(filePatch, 'getHunks').returns(hunks); - it('decorates deleted lines'); + const wrapper = mount(buildApp()); + assert.lengthOf( + wrapper.find('Decoration').filterWhere(h => { + return h.prop('type') === 'line' && h.prop('className') === 'github-FilePatchView-line--added'; + }), + 1, + ); + }); + + it('decorates deleted lines', function() { + const hunks = [ + new Hunk(0, 0, 1, 1, 'hunk 0', [ + new HunkLine('line 0', 'added', 0, 1, 0), + new HunkLine('line 1', 'deleted', 0, 1, 0), + ]), + ]; + sinon.stub(filePatch, 'getHunks').returns(hunks); + + const wrapper = mount(buildApp()); + assert.lengthOf( + wrapper.find('Decoration').filterWhere(h => { + return h.prop('type') === 'line' && h.prop('className') === 'github-FilePatchView-line--deleted'; + }), + 1, + ); + }); + + it('decorates the nonewline line', function() { + const hunks = [ + new Hunk(0, 0, 1, 1, 'hunk 0', [ + new HunkLine('line 0', 'added', 0, 1, 0), + new HunkLine('line 1', 'deleted', 0, 1, 0), + new HunkLine('no newline', 'nonewline', 0, 1, 0), + ]), + ]; + sinon.stub(filePatch, 'getHunks').returns(hunks); - it('decorates the nonewlines line'); + const wrapper = mount(buildApp()); + assert.lengthOf( + wrapper.find('Decoration').filterWhere(h => { + return h.prop('type') === 'line' && h.prop('className') === 'github-FilePatchView-line--nonewline'; + }), + 1, + ); + }); }); }); From 0b3faa3eef8eb27c368084f14035f982924b520c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 11 Jun 2018 13:38:57 -0400 Subject: [PATCH 0023/4053] Pass the WorkdirContextPool into the React component tree --- lib/controllers/root-controller.js | 9 +++++++-- lib/github-package.js | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 6dbedb6469..77ff3107bb 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -26,6 +26,7 @@ import GitCacheView from '../views/git-cache-view'; import Conflict from '../models/conflicts/conflict'; import RefHolder from '../models/ref-holder'; import Switchboard from '../switchboard'; +import {WorkdirContextPoolPropType} from '../prop-types'; import {destroyFilePatchPaneItems, destroyEmptyFilePatchPaneItems, autobind} from '../helpers'; import {GitError} from '../git-shell-out-strategy'; @@ -53,6 +54,7 @@ export default class RootController extends React.Component { destroyGitTabItem: PropTypes.func.isRequired, destroyGithubTabItem: PropTypes.func.isRequired, pipelineManager: PropTypes.object, + workdirContextPool: WorkdirContextPoolPropType.isRequired, } static defaultProps = { @@ -322,10 +324,13 @@ export default class RootController extends React.Component { {({itemHolder, params}) => ( )} diff --git a/lib/github-package.js b/lib/github-package.js index 1223b648ae..f5e6c4d9fc 100644 --- a/lib/github-package.js +++ b/lib/github-package.js @@ -288,7 +288,7 @@ export default class GithubPackage { destroyGitTabItem={this.destroyGitTabItem} destroyGithubTabItem={this.destroyGithubTabItem} removeFilePatchItem={this.removeFilePatchItem} - getRepositoryForWorkdir={this.getRepositoryForWorkdir} + workdirContextPool={this.contextPool} />, this.element, callback, ); } From 0017501d0c5cbc368562889a255e5ac8a31a6540 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 11 Jun 2018 13:39:07 -0400 Subject: [PATCH 0024/4053] Start shifting CSS around --- styles/file-patch-view.less | 20 +++ styles/hunk-header-view.less | 162 ++++++++++++++++++ styles/{hunk-view.less => hunk-view.old.less} | 0 3 files changed, 182 insertions(+) create mode 100644 styles/hunk-header-view.less rename styles/{hunk-view.less => hunk-view.old.less} (100%) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 224f1bb133..0c0dd50899 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -1,5 +1,8 @@ @import "variables"; +@hunk-fg-color: @text-color-subtle; +@hunk-bg-color: @pane-item-background-color; + .github-FilePatchView { display: flex; flex-direction: column; @@ -136,4 +139,21 @@ } } + // Line decorations + + &-line { + // mixin + .hunk-line-mixin(@fg; @bg) { + color: saturate( mix(@fg, @text-color-highlight, 20%), 20%); + background-color: saturate( mix(@bg, @hunk-bg-color, 15%), 20%); + } + + &--deleted { + .hunk-line-mixin(@text-color-error, @background-color-error); + } + + &--added { + .hunk-line-mixin(@text-color-success, @background-color-success); + } + } } diff --git a/styles/hunk-header-view.less b/styles/hunk-header-view.less new file mode 100644 index 0000000000..a1e1e19449 --- /dev/null +++ b/styles/hunk-header-view.less @@ -0,0 +1,162 @@ +@import "ui-variables"; + +@hunk-fg-color: @text-color-subtle; +@hunk-bg-color: @pane-item-background-color; + +.github-HunkHeaderView { + font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace; + + display: flex; + align-items: stretch; + font-size: .9em; + background-color: @panel-heading-background-color; + border-bottom: 1px solid @panel-heading-border-color; + + &-title { + flex: 1; + line-height: 2.4; + padding: 0 @component-padding; + color: @text-color-subtle; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + -webkit-font-smoothing: antialiased; + } + + &-stageButton, + &-discardButton { + line-height: 1; + padding-left: @component-padding; + padding-right: @component-padding; + font-family: @font-family; + border: none; + border-left: 1px solid @panel-heading-border-color; + background-color: transparent; + cursor: default; + &:hover { background-color: @button-background-color-hover; } + &:active { background-color: @panel-heading-border-color; } + } + + // pixel fit the icon + &-discardButton:before { + text-align: left; + width: auto; + } + + &-line { + display: table-row; + line-height: 1.5em; + color: @hunk-fg-color; + &.is-unchanged { + -webkit-font-smoothing: antialiased; + } + } + + &-lineNumber { + display: table-cell; + min-width: 3.5em; // min 4 chars + overflow: hidden; + padding: 0 .5em; + text-align: right; + border-right: 1px solid @base-border-color; + -webkit-font-smoothing: antialiased; + } + + &-plusMinus { + margin-right: 1ch; + color: fade(@text-color, 50%); + vertical-align: top; + } + + &-lineContent { + display: table-cell; + padding: 0 .5em 0 3ch; // indent 3 characters + text-indent: -2ch; // remove indentation for the +/- + white-space: pre-wrap; + word-break: break-word; + width: 100%; + vertical-align: top; + } + + &-lineText { + display: inline-block; + text-indent: 0; + } +} + + +// +// States +// ------------------------------- + +.github-HunkView.is-selected.is-hunkMode .github-HunkView-header { + background-color: @background-color-selected; + .github-HunkView-title { + color: @text-color; + } + .github-HunkView-stageButton, .github-HunkView-discardButton { + border-color: mix(@text-color, @background-color-selected, 25%); + } +} + +.github-HunkView-title:hover { + color: @text-color-highlight; +} + +.github-HunkView-line { + + // mixin + .hunk-line-mixin(@fg; @bg) { + &:hover { + background-color: @background-color-highlight; + } + &.is-selected { + color: @text-color; + background-color: @background-color-selected; + } + .github-HunkView-lineContent { + color: saturate( mix(@fg, @text-color-highlight, 20%), 20%); + background-color: saturate( mix(@bg, @hunk-bg-color, 15%), 20%); + } + // hightlight when focused + selected + .github-FilePatchView:focus &.is-selected .github-HunkView-lineContent { + color: saturate( mix(@fg, @text-color-highlight, 10%), 10%); + background-color: saturate( mix(@bg, @hunk-bg-color, 25%), 10%); + } + } + + &.is-deleted { + .hunk-line-mixin(@text-color-error, @background-color-error); + } + + &.is-added { + .hunk-line-mixin(@text-color-success, @background-color-success); + } + + // divider line between added and deleted lines + &.is-deleted + .is-added .github-HunkView-lineContent { + box-shadow: 0 -1px 0 hsla(0,0%,50%,.1); + } + +} + +// focus colors +.github-FilePatchView:focus { + .github-HunkView.is-selected.is-hunkMode .github-HunkView-title, + .github-HunkView.is-selected.is-hunkMode .github-HunkView-header, + .github-HunkView-line.is-selected .github-HunkView-lineNumber { + color: contrast(@button-background-color-selected); + background: @button-background-color-selected; + } + .github-HunkView-line.is-selected .github-HunkView-lineNumber { + border-color: mix(@button-border-color, @button-background-color-selected, 25%); + } + .github-HunkView.is-selected.is-hunkMode .github-HunkView { + &-stageButton, + &-discardButton { + border-color: mix(@hunk-bg-color, @button-background-color-selected, 30%); + &:hover { background-color: mix(@hunk-bg-color, @button-background-color-selected, 10%); } + &:active { background-color: @button-background-color-selected; } + } + } +} diff --git a/styles/hunk-view.less b/styles/hunk-view.old.less similarity index 100% rename from styles/hunk-view.less rename to styles/hunk-view.old.less From e763bc5e681610a332d1ce937f573d9d5d745a7d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 20 Jul 2018 17:59:17 -0400 Subject: [PATCH 0025/4053] package-lock updates --- package-lock.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7af141b276..210339e328 100644 --- a/package-lock.json +++ b/package-lock.json @@ -345,7 +345,7 @@ "dev": true, "requires": { "ast-types-flow": "0.0.7", - "commander": "2.16.0" + "commander": "^2.11.0" }, "dependencies": { "commander": { @@ -380,8 +380,8 @@ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, "array-union": { @@ -2782,14 +2782,14 @@ "integrity": "sha512-JsxNKqa3TwmPypeXNnI75FntkUktGzI1wSa1LgNZdSOMI+B4sxnr1lSF8m8lPiz4mKiC+14ysZQM4scewUrP7A==", "dev": true, "requires": { - "aria-query": "3.0.0", - "array-includes": "3.0.3", - "ast-types-flow": "0.0.7", - "axobject-query": "2.0.1", - "damerau-levenshtein": "1.0.4", - "emoji-regex": "6.5.1", - "has": "1.0.3", - "jsx-ast-utils": "2.0.1" + "aria-query": "^3.0.0", + "array-includes": "^3.0.3", + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.1", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^6.5.1", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1" }, "dependencies": { "has": { @@ -2798,7 +2798,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, "jsx-ast-utils": { @@ -2807,7 +2807,7 @@ "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", "dev": true, "requires": { - "array-includes": "3.0.3" + "array-includes": "^3.0.3" } } } From ee38d14098096868c2cda9e75449b0be570e66d3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 12:49:58 -0400 Subject: [PATCH 0026/4053] Use a Gutter component as a bridge to the addGutter API --- lib/atom/gutter.js | 110 +++++++++++++++++++++++++++++++++++++++ test/atom/gutter.test.js | 84 ++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 lib/atom/gutter.js create mode 100644 test/atom/gutter.test.js diff --git a/lib/atom/gutter.js b/lib/atom/gutter.js new file mode 100644 index 0000000000..34a29873cc --- /dev/null +++ b/lib/atom/gutter.js @@ -0,0 +1,110 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Disposable} from 'event-kit'; + +import {autobind, extractProps} from '../helpers'; +import {RefHolderPropType} from '../prop-types'; +import {TextEditorContext} from './atom-text-editor'; +import RefHolder from '../models/ref-holder'; + +const gutterProps = { + name: PropTypes.string.isRequired, + priority: PropTypes.number.isRequired, + visible: PropTypes.bool, + type: PropTypes.oneOf(['line-number', 'decorated']), + labelFn: PropTypes.func, + onMouseDown: PropTypes.func, + onMouseMove: PropTypes.func, +}; + +class BareGutter extends React.Component { + static propTypes = { + editorHolder: RefHolderPropType.isRequired, + className: PropTypes.string, + ...gutterProps, + } + + static defaultProps = { + visible: true, + type: 'decorated', + labelFn: () => {}, + } + + constructor(props) { + super(props); + autobind(this, 'observeEditor', 'forceUpdate'); + + this.state = { + gutter: null, + editorComponent: null, + }; + + this.sub = new Disposable(); + } + + componentDidMount() { + this.sub = this.props.editorHolder.observe(this.observeEditor); + } + + componentDidUpdate(prevProps) { + if (this.props.editorHolder !== prevProps.editorHolder) { + this.sub.dispose(); + this.sub = this.props.editorHolder.observe(this.observeEditor); + } + } + + componentWillUnmount() { + if (this.state.gutter !== null) { + this.state.gutter.destroy(); + } + this.sub.dispose(); + } + + render() { + return null; + } + + observeEditor(editor) { + this.setState((prevState, props) => { + if (prevState.gutter !== null) { + prevState.gutter.destroy(); + } + + const options = extractProps(props, gutterProps); + options.class = props.className; + return {gutter: editor.addGutter(options)}; + }); + } +} + +export default class Gutter extends React.Component { + static propTypes = { + editor: PropTypes.object, + } + + constructor(props) { + super(props); + this.state = { + editorHolder: RefHolder.on(this.props.editor), + }; + } + + static getDerivedStateFromProps(props, state) { + const editorChanged = state.editorHolder.map(editor => editor !== props.editor).getOr(props.editor !== undefined); + return editorChanged ? RefHolder.on(props.editor) : null; + } + + render() { + if (!this.state.editorHolder.isEmpty()) { + return ; + } + + return ( + + {editorHolder => ( + + )} + + ); + } +} diff --git a/test/atom/gutter.test.js b/test/atom/gutter.test.js new file mode 100644 index 0000000000..a97f5236b8 --- /dev/null +++ b/test/atom/gutter.test.js @@ -0,0 +1,84 @@ +import React from 'react'; +import {mount} from 'enzyme'; + +import AtomTextEditor from '../../lib/atom/atom-text-editor'; +import Gutter from '../../lib/atom/gutter'; + +describe('Gutter', function() { + let atomEnv, domRoot; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + + domRoot = document.createElement('div'); + domRoot.id = 'github-Gutter-test'; + document.body.appendChild(domRoot); + + const workspaceElement = atomEnv.workspace.getElement(); + domRoot.appendChild(workspaceElement); + }); + + afterEach(function() { + atomEnv.destroy(); + document.body.removeChild(domRoot); + }); + + it('adds a custom gutter to an editor supplied by prop', async function() { + const editor = await atomEnv.workspace.open(__filename); + + const app = ( + + ); + const wrapper = mount(app); + + const gutter = editor.gutterWithName('aaa'); + assert.isNotNull(gutter); + assert.isTrue(gutter.isVisible()); + assert.strictEqual(gutter.priority, 10); + + wrapper.unmount(); + + assert.isNull(editor.gutterWithName('aaa')); + }); + + it('adds a custom gutter to an editor from a context', function() { + const app = ( + + + + ); + const wrapper = mount(app); + + const editor = wrapper.instance().getModel(); + const gutter = editor.gutterWithName('bbb'); + assert.isNotNull(gutter); + assert.isTrue(gutter.isVisible()); + assert.strictEqual(gutter.priority, 20); + }); + + it('uses a function to derive number labels', async function() { + const text = '000\n111\n222\n333\n444\n555\n666\n777\n888\n999\n'; + const labelFn = ({bufferRow, screenRow}) => `custom ${bufferRow} ${screenRow}`; + + const app = ( + + + + ); + const wrapper = mount(app, {attachTo: domRoot}); + + const editorRoot = wrapper.getDOMNode(); + await assert.async.lengthOf(editorRoot.querySelectorAll('.yyy .line-number'), 12); + + const lineNumbers = editorRoot.querySelectorAll('.yyy .line-number'); + assert.strictEqual(lineNumbers[1].innerText, 'custom 0 0'); + assert.strictEqual(lineNumbers[2].innerText, 'custom 1 1'); + assert.strictEqual(lineNumbers[3].innerText, 'custom 2 2'); + }); +}); From 149b250a434b36a55c56c9112bc93fc3ac467b38 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 14:26:17 -0400 Subject: [PATCH 0027/4053] CSS to get the FilePatchItem editor to scroll properly --- lib/views/file-patch-view.js | 6 +++++- styles/file-patch-view.less | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index b1ecb833e4..3bda3fa743 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -75,7 +75,11 @@ export default class FilePatchView extends React.Component { />
- + diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 0c0dd50899..0b86273d43 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -10,6 +10,7 @@ cursor: default; flex: 1; min-width: 0; + height: 100%; &-header { display: flex; @@ -39,8 +40,8 @@ &-container { flex: 1; - overflow-y: auto; display: flex; + height: 100%; flex-direction: column; .is-blank, @@ -57,6 +58,11 @@ .large-file-patch { flex-direction: column; } + + atom-text-editor { + width: 100%; + height: 100%; + } } From 2191fbf974b3147c8f7ef82237e0b114ea157874 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 24 Jul 2018 14:26:33 -0400 Subject: [PATCH 0028/4053] Render old and new line numbers --- lib/models/presented-file-patch.js | 24 ++++++++++++++++++++ lib/views/file-patch-view.js | 36 ++++++++++++++++++++++++++++++ styles/file-patch-view.less | 16 +++++++++++++ 3 files changed, 76 insertions(+) diff --git a/lib/models/presented-file-patch.js b/lib/models/presented-file-patch.js index aef7b69b34..9fcddde44c 100644 --- a/lib/models/presented-file-patch.js +++ b/lib/models/presented-file-patch.js @@ -11,6 +11,9 @@ export default class PresentedFilePatch { deleted: [], nonewline: [], }; + this.oldLineNumbers = new Map(); + this.newLineNumbers = new Map(); + this.maxLineNumberWidth = 0; let bufferLine = 0; this.text = filePatch.getHunks().reduce((str, hunk) => { @@ -18,6 +21,15 @@ export default class PresentedFilePatch { return hunk.getLines().reduce((hunkStr, line) => { hunkStr += line.getText() + '\n'; + this.oldLineNumbers.set(bufferLine, line.getOldLineNumber()); + this.newLineNumbers.set(bufferLine, line.getNewLineNumber()); + + if (line.getOldLineNumber().toString().length > this.maxLineNumberWidth) { + this.maxLineNumberWidth = line.getOldLineNumber().toString().length; + } + if (line.getNewLineNumber().toString().length > this.maxLineNumberWidth) { + this.maxLineNumberWidth = line.getNewLineNumber().toString().length; + } this.bufferPositions[line.getStatus()].push( new Point(bufferLine, 0), @@ -56,4 +68,16 @@ export default class PresentedFilePatch { getNoNewlineBufferPositions() { return this.bufferPositions.nonewline; } + + getOldLineNumberAt(bufferRow) { + return this.oldLineNumbers.get(bufferRow); + } + + getNewLineNumberAt(bufferRow) { + return this.newLineNumbers.get(bufferRow); + } + + getMaxLineNumberWidth() { + return this.maxLineNumberWidth; + } } diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 3bda3fa743..aa8acc1004 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -2,11 +2,13 @@ import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; +import {autobind} from '../helpers'; import FilePatchSelection from '../models/file-patch-selection'; import AtomTextEditor from '../atom/atom-text-editor'; import Marker from '../atom/marker'; import MarkerLayer from '../atom/marker-layer'; import Decoration from '../atom/decoration'; +import Gutter from '../atom/gutter'; import FilePatchHeaderView from './file-patch-header-view'; import FilePatchMetaView from './file-patch-meta-view'; import HunkHeaderView from './hunk-header-view'; @@ -16,6 +18,8 @@ const executableText = { 100755: 'executable', }; +const NBSP_CHARACTER = '\u00a0'; + export default class FilePatchView extends React.Component { static propTypes = { relPath: PropTypes.string.isRequired, @@ -36,6 +40,7 @@ export default class FilePatchView extends React.Component { constructor(props) { super(props); + autobind(this, 'oldLineNumberLabel', 'newLineNumberLabel'); this.state = { selection: new FilePatchSelection(this.props.filePatch.getHunks()), @@ -80,6 +85,20 @@ export default class FilePatchView extends React.Component { lineNumberGutterVisible={false} autoWidth={false} autoHeight={false}> + + @@ -276,4 +295,21 @@ export default class FilePatchView extends React.Component { ); } + + oldLineNumberLabel({bufferRow}) { + return this.pad(this.state.presentedFilePatch.getOldLineNumberAt(bufferRow)); + } + + newLineNumberLabel({bufferRow}) { + return this.pad(this.state.presentedFilePatch.getNewLineNumberAt(bufferRow)); + } + + pad(num) { + const maxDigits = this.state.presentedFilePatch.getMaxLineNumberWidth(); + if (num === undefined || num === -1) { + return NBSP_CHARACTER.repeat(maxDigits); + } else { + return NBSP_CHARACTER.repeat(maxDigits - num.toString().length) + num.toString(); + } + } } diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 0b86273d43..1b5ed499eb 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -163,3 +163,19 @@ } } } + +.gutter.old { + width: 5em; + + .line-number { + opacity: 1; + } +} + +.gutter.new { + width: 5em; + + .line-number { + opacity: 1; + } +} From 90b2af315b4e366f30a6095dcf2e36c5239a06e7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 25 Jul 2018 09:55:20 -0400 Subject: [PATCH 0029/4053] Move controller methods over --- lib/containers/file-patch-container.js | 4 + lib/controllers/file-patch-controller.js | 211 ++++++++++++++++++++++- lib/controllers/root-controller.js | 4 +- lib/items/file-patch-item.js | 8 + lib/models/hunk-line.js | 4 + lib/models/presented-file-patch.js | 32 +++- lib/views/file-patch-view.js | 121 +++++++++++-- lib/views/hunk-header-view.js | 7 + 8 files changed, 370 insertions(+), 21 deletions(-) diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index 5375d24f72..8bb5f11645 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -13,7 +13,11 @@ export default class FilePatchContainer extends React.Component { stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), relPath: PropTypes.string.isRequired, + workspace: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, + + destroy: PropTypes.func.isRequired, + undoLastDiscard: PropTypes.func.isRequired, } constructor(props) { diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 3df858ab0a..ba20e3d24e 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -1,9 +1,218 @@ import React from 'react'; +import PropTypes from 'prop-types'; +import {Point} from 'atom'; +import path from 'path'; +import {autobind} from '../helpers'; +import FilePatchSelection from '../models/file-patch-selection'; +import FilePatchItem from '../items/file-patch-item'; import FilePatchView from '../views/file-patch-view'; export default class FilePatchController extends React.Component { + static propTypes = { + repository: PropTypes.object.isRequired, + stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), + relPath: PropTypes.string.isRequired, + filePatch: PropTypes.object.isRequired, + + workspace: PropTypes.object.isRequired, + tooltips: PropTypes.object.isRequired, + + destroy: PropTypes.func.isRequired, + undoLastDiscard: PropTypes.func.isRequired, + } + + constructor(props) { + super(props); + autobind( + this, + 'mouseDownOnHeader', 'mouseDownOnLineNumber', 'mouseMoveOnLineNumber', 'mouseUp', + 'undoLastDiscard', 'diveIntoMirrorPatch', 'openFile', 'toggleFile', 'toggleModeChange', 'toggleSymlinkChange', + ); + + this.state = { + selection: new FilePatchSelection(this.props.filePatch.getHunks()), + }; + + this.mouseSelectionInProgress = false; + } + render() { - return ; + return ( + + ); + } + + mouseDownOnHeader(event, hunk) { + if (event.button !== 0) { return; } + const windows = process.platform === 'win32'; + if (event.ctrlKey && !windows) { return; } // simply open context menu + + this.mouseSelectionInProgress = true; + event.persist && event.persist(); + + this.setState(prevState => { + let selection = prevState.selection; + if (event.metaKey || (event.ctrlKey && windows)) { + if (selection.getMode() === 'hunk') { + selection = selection.addOrSubtractHunkSelection(hunk); + } else { + // TODO: optimize + selection = hunk.getLines().reduce( + (current, line) => current.addOrSubtractLineSelection(line).coalesce(), + selection, + ); + } + } else if (event.shiftKey) { + if (selection.getMode() === 'hunk') { + selection = selection.selectHunk(hunk, true); + } else { + const hunkLines = hunk.getLines(); + const tailIndex = selection.getLineSelectionTailIndex(); + const selectedHunkAfterTail = tailIndex < hunkLines[0].diffLineNumber; + if (selectedHunkAfterTail) { + selection = selection.selectLine(hunkLines[hunkLines.length - 1], true); + } else { + selection = selection.selectLine(hunkLines[0], true); + } + } + } else { + selection = selection.selectHunk(hunk, false); + } + + return {selection}; + }); + } + + mouseDownOnLineNumber(event, hunk, line) { + if (event.button !== 0) { return; } + const windows = process.platform === 'win32'; + if (event.ctrlKey && !windows) { return; } // simply open context menu + + this.mouseSelectionInProgress = true; + event.persist && event.persist(); + + this.setState(prevState => { + let selection = prevState.selection; + + if (event.metaKey || (event.ctrlKey && windows)) { + if (selection.getMode() === 'hunk') { + selection = selection.addOrSubtractHunkSelection(hunk); + } else { + selection = selection.addOrSubtractLineSelection(line); + } + } else if (event.shiftKey) { + if (selection.getMode() === 'hunk') { + selection = selection.selectHunk(hunk, true); + } else { + selection = selection.selectLine(line, true); + } + } else if (event.detail === 1) { + selection = selection.selectLine(line, false); + } else if (event.detail === 2) { + selection = selection.selectHunk(hunk, false); + } + + return {selection}; + }); + } + + mouseMoveOnLineNumber(event, hunk, line) { + if (!this.mouseSelectionInProgress) { return; } + + this.setState(prevState => { + let selection = null; + if (prevState.selection.getMode() === 'hunk') { + selection = prevState.selection.selectHunk(hunk, true); + } else { + selection = prevState.selection.selectLine(line, true); + } + return {selection}; + }); + } + + mouseUp() { + this.mouseSelectionInProgress = false; + this.setState(prevState => { + return {selection: prevState.selection.coalesce()}; + }); + } + + undoLastDiscard() { + return this.props.undoLastDiscard(this.props.relPath, this.props.repository); + } + + async diveIntoMirrorPatch() { + const mirrorStatus = this.withStagingStatus({staged: 'unstaged', unstaged: 'staged'}); + const workingDirectory = this.props.repository.getWorkingDirectoryPath(); + const uri = FilePatchItem.buildURI(this.props.relPath, workingDirectory, mirrorStatus); + + this.destroy(); + await this.props.workspace.open(uri); + } + + async openFile(lineNumber = null) { + const absolutePath = path.join(this.props.repository.getWorkingDirectoryPath(), this.props.relPath); + const editor = await this.props.workspace.open(absolutePath, {pending: true}); + const position = new Point(lineNumber ? lineNumber - 1 : 0, 0); + editor.scrollToBufferPosition(position, {center: true}); + editor.setCursorBufferPosition(position); + } + + toggleFile() { + const methodName = this.withStagingStatus({staged: 'unstageFile', unstaged: 'stageFile'}); + return this.props.repository[methodName]([this.props.relPath]); + } + + toggleModeChange() { + const targetMode = this.withStagingStatus({ + unstaged: this.props.filePatch.getNewMode(), + staged: this.props.filePatch.getOldMode(), + }); + return this.props.repository.stageFileModeChange(this.props.relPath, targetMode); + } + + toggleSymlinkChange() { + const {filePatch, relPath, repository} = this.props; + return this.withStagingStatus({ + unstaged: () => { + if (filePatch.hasTypechange() && filePatch.getStatus() === 'added') { + return repository.stageFileSymlinkChange(relPath); + } + + return repository.stageFiles([relPath]); + }, + staged: () => { + if (filePatch.hasTypechange() && filePatch.getStatus() === 'deleted') { + return repository.stageFileSymlinkChange(relPath); + } + + return repository.unstageFiles([relPath]); + }, + }); + } + + withStagingStatus(callbacks) { + const callback = callbacks[this.props.stagingStatus]; + if (!callback) { + throw new Error(`Unknown staging status: ${this.props.stagingStatus}`); + } + return callback instanceof Function ? callback() : callback; } } diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 2d6c4814ef..a0a373e63b 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -40,7 +40,6 @@ export default class RootController extends React.Component { project: PropTypes.object.isRequired, loginModel: PropTypes.object.isRequired, confirm: PropTypes.func.isRequired, - workdirContextPool: WorkdirContextPoolPropType.isRequired, getRepositoryForWorkdir: PropTypes.func.isRequired, createRepositoryForProjectPath: PropTypes.func, cloneRepositoryForProjectPath: PropTypes.func, @@ -316,6 +315,9 @@ export default class RootController extends React.Component { stagingStatus={params.stagingStatus} tooltips={this.props.tooltips} + workspace={this.props.workspace} + + undoLastDiscard={this.undoLastDiscard} /> )} diff --git a/lib/items/file-patch-item.js b/lib/items/file-patch-item.js index 3ab27842fc..b8b108e354 100644 --- a/lib/items/file-patch-item.js +++ b/lib/items/file-patch-item.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import {Emitter} from 'event-kit'; import {WorkdirContextPoolPropType} from '../prop-types'; +import {autobind} from '../helpers'; import FilePatchContainer from '../containers/file-patch-container'; export default class FilePatchItem extends React.Component { @@ -12,6 +13,11 @@ export default class FilePatchItem extends React.Component { relPath: PropTypes.string.isRequired, workingDirectory: PropTypes.string.isRequired, stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), + + workspace: PropTypes.object.isRequired, + tooltips: PropTypes.object.isRequired, + + undoLastDiscard: PropTypes.func.isRequired, } static uriPattern = 'atom-github://file-patch/{relPath...}?workdir={workingDirectory}&stagingStatus={stagingStatus}' @@ -25,6 +31,7 @@ export default class FilePatchItem extends React.Component { constructor(props) { super(props); + autobind(this, 'destroy'); this.emitter = new Emitter(); this.isDestroyed = false; @@ -66,6 +73,7 @@ export default class FilePatchItem extends React.Component { return ( ); diff --git a/lib/models/hunk-line.js b/lib/models/hunk-line.js index fcf4826e17..8ab430836e 100644 --- a/lib/models/hunk-line.js +++ b/lib/models/hunk-line.js @@ -35,6 +35,10 @@ export default class HunkLine { return this.newLineNumber; } + getDiffLineNumber() { + return this.diffLineNumber; + } + getStatus() { return this.status; } diff --git a/lib/models/presented-file-patch.js b/lib/models/presented-file-patch.js index 9fcddde44c..02a5b2ba09 100644 --- a/lib/models/presented-file-patch.js +++ b/lib/models/presented-file-patch.js @@ -11,8 +11,9 @@ export default class PresentedFilePatch { deleted: [], nonewline: [], }; - this.oldLineNumbers = new Map(); - this.newLineNumbers = new Map(); + this.hunkIndex = new Map(); + this.lineIndex = new Map(); + this.lineReverseIndex = new Map(); this.maxLineNumberWidth = 0; let bufferLine = 0; @@ -21,8 +22,9 @@ export default class PresentedFilePatch { return hunk.getLines().reduce((hunkStr, line) => { hunkStr += line.getText() + '\n'; - this.oldLineNumbers.set(bufferLine, line.getOldLineNumber()); - this.newLineNumbers.set(bufferLine, line.getNewLineNumber()); + this.hunkIndex.set(bufferLine, hunk); + this.lineIndex.set(bufferLine, line); + this.lineReverseIndex.set(line, bufferLine); if (line.getOldLineNumber().toString().length > this.maxLineNumberWidth) { this.maxLineNumberWidth = line.getOldLineNumber().toString().length; @@ -69,12 +71,30 @@ export default class PresentedFilePatch { return this.bufferPositions.nonewline; } + getHunkAt(bufferRow) { + return this.hunkIndex.get(bufferRow); + } + + getLineAt(bufferRow) { + return this.lineIndex.get(bufferRow); + } + getOldLineNumberAt(bufferRow) { - return this.oldLineNumbers.get(bufferRow); + const line = this.getLineAt(bufferRow); + return line ? line.getOldLineNumber() : -1; } getNewLineNumberAt(bufferRow) { - return this.newLineNumbers.get(bufferRow); + const line = this.getLineAt(bufferRow); + return line ? line.getNewLineNumber() : -1; + } + + getPositionForLine(line) { + const bufferRow = this.lineReverseIndex.get(line); + if (bufferRow === undefined) { + return null; + } + return new Point(bufferRow, 0); } getMaxLineNumberWidth() { diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index aa8acc1004..0297545c14 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import {autobind} from '../helpers'; -import FilePatchSelection from '../models/file-patch-selection'; import AtomTextEditor from '../atom/atom-text-editor'; import Marker from '../atom/marker'; import MarkerLayer from '../atom/marker-layer'; @@ -26,10 +25,16 @@ export default class FilePatchView extends React.Component { stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, isPartiallyStaged: PropTypes.bool.isRequired, filePatch: PropTypes.object.isRequired, + selection: PropTypes.object.isRequired, repository: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, + mouseDownOnHeader: PropTypes.func.isRequired, + mouseDownOnLineNumber: PropTypes.func.isRequired, + mouseMoveOnLineNumber: PropTypes.func.isRequired, + mouseUp: PropTypes.func.isRequired, + undoLastDiscard: PropTypes.func.isRequired, diveIntoMirrorPatch: PropTypes.func.isRequired, openFile: PropTypes.func.isRequired, @@ -40,22 +45,52 @@ export default class FilePatchView extends React.Component { constructor(props) { super(props); - autobind(this, 'oldLineNumberLabel', 'newLineNumberLabel'); + autobind( + this, + 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', 'oldLineNumberLabel', 'newLineNumberLabel', + ); + const presentedFilePatch = this.props.filePatch.present(); + const selectedLines = this.props.selection.getSelectedLines(); this.state = { - selection: new FilePatchSelection(this.props.filePatch.getHunks()), - presentedFilePatch: this.props.filePatch.present(), + lastSelection: this.props.selection, + selectedHunks: this.props.selection.getSelectedHunks(), + selectedLines, + presentedFilePatch, + selectedLinePositions: Array.from(selectedLines, line => presentedFilePatch.getPositionForLine(line)), }; + + this.lastMouseMoveLine = null; } static getDerivedStateFromProps(props, state) { + const nextState = {}; + let currentPresentedFilePatch = state.presentedFilePatch; + if (props.filePatch !== state.presentedFilePatch.getFilePatch()) { - return { - presentedFilePatch: props.filePatch.present(), - }; + currentPresentedFilePatch = props.filePatch.present(); + nextState.presentedFilePatch = currentPresentedFilePatch; } - return null; + if (props.selection !== state.lastSelection) { + nextState.lastSelection = props.selection; + nextState.selectedHunks = props.selection.getSelectedHunks(); + nextState.selectedLines = props.selection.getSelectedLines(); + + nextState.selectedLinePositions = Array.from(nextState.selectedLines, line => { + return currentPresentedFilePatch.getPositionForLine(line); + }); + } + + return nextState; + } + + componentDidMount() { + window.addEventListener('mouseup', this.didMouseUp); + } + + componentWillUnmount() { + window.removeEventListener('mouseup', this.didMouseUp); } render() { @@ -91,6 +126,8 @@ export default class FilePatchView extends React.Component { className="old" type="line-number" labelFn={this.oldLineNumberLabel} + onMouseDown={this.didMouseDownOnLineNumber} + onMouseMove={this.didMouseMoveOnLineNumber} /> @@ -111,17 +150,25 @@ export default class FilePatchView extends React.Component { {this.renderHunkHeaders()} + {this.renderLineDecorations( + this.state.selectedLinePositions, + 'github-FilePatchView-line--selected', + {gutter: true}, + )} {this.renderLineDecorations( this.state.presentedFilePatch.getAddedBufferPositions(), 'github-FilePatchView-line--added', + {line: true}, )} {this.renderLineDecorations( this.state.presentedFilePatch.getDeletedBufferPositions(), 'github-FilePatchView-line--deleted', + {line: true}, )} {this.renderLineDecorations( this.state.presentedFilePatch.getNoNewlineBufferPositions(), 'github-FilePatchView-line--nonewline', + {line: true}, )} @@ -248,8 +295,8 @@ export default class FilePatchView extends React.Component { } renderHunkHeaders() { - const selectedHunks = this.state.selection.getSelectedHunks(); - const isHunkSelectionMode = this.state.selection.getMode() === 'hunk'; + const selectedHunks = this.state.selectedHunks; + const isHunkSelectionMode = this.props.selection.getMode() === 'hunk'; const toggleVerb = this.props.stagingStatus === 'unstaged' ? 'Stage' : 'Unstage'; return this.props.filePatch.getHunks().map((hunk, index) => { @@ -269,7 +316,7 @@ export default class FilePatchView extends React.Component { hunk={hunk} isSelected={isSelected} stagingStatus={this.props.stagingStatus} - selectionMode={this.state.selection.getMode()} + selectionMode={this.props.selection.getMode()} toggleSelectionLabel={toggleSelectionLabel} discardSelectionLabel={discardSelectionLabel} @@ -277,6 +324,7 @@ export default class FilePatchView extends React.Component { toggleSelection={() => {}} discardSelection={() => {}} + mouseDown={this.props.mouseDownOnHeader} /> @@ -284,18 +332,65 @@ export default class FilePatchView extends React.Component { }); } - renderLineDecorations(positions, lineClass) { + renderSelectedLines() { + return ( + + {Array.from(this.state.selectedLines, line => { + const position = this.state.presentedFilePatch.getPositionForLine(line); + return ; + })} + + ); + } + + renderLineDecorations(positions, lineClass, {line, gutter}) { return ( {positions.map((position, index) => { return ; })} - + {line && } + {gutter && ( + + + + + )} ); } + didMouseDownOnLineNumber(event) { + const line = this.state.presentedFilePatch.getLineAt(event.bufferRow); + const hunk = this.state.presentedFilePatch.getHunkAt(event.bufferRow); + + if (line === undefined || hunk === undefined) { + return; + } + + this.props.mouseDownOnLineNumber(event.domEvent, hunk, line); + } + + didMouseMoveOnLineNumber(event) { + const line = this.state.presentedFilePatch.getLineAt(event.bufferRow); + if (this.lastMouseMoveLine === line || line === undefined) { + return; + } + this.lastMouseMoveLine = line; + + const hunk = this.state.presentedFilePatch.getHunkAt(event.bufferRow); + if (hunk === undefined) { + return; + } + + this.props.mouseMoveOnLineNumber(event.domEvent, hunk, line); + } + + didMouseUp() { + this.props.mouseUp(); + } + oldLineNumberLabel({bufferRow}) { return this.pad(this.state.presentedFilePatch.getOldLineNumberAt(bufferRow)); } diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index e0ba9f9b46..ca725f9bd4 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -2,6 +2,7 @@ import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; +import {autobind} from '../helpers'; import RefHolder from '../models/ref-holder'; import Tooltip from '../atom/tooltip'; @@ -18,10 +19,12 @@ export default class HunkHeaderView extends React.Component { toggleSelection: PropTypes.func.isRequired, discardSelection: PropTypes.func.isRequired, + mouseDown: PropTypes.func.isRequired, }; constructor(props) { super(props); + autobind(this, 'didMouseDown'); this.refDiscardButton = new RefHolder(); } @@ -57,4 +60,8 @@ export default class HunkHeaderView extends React.Component {
); } + + didMouseDown(event) { + return this.props.mouseDown(event, this.props.hunk); + } } From 6e085366d0657241ad6295a041e694ac1e72d8f8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 25 Jul 2018 09:57:03 -0400 Subject: [PATCH 0030/4053] Some inexpert styling :art: --- lib/views/hunk-header-view.js | 6 +++--- styles/file-patch-view.less | 15 ++++++++------- styles/hunk-header-view.less | 34 +++++++++++++++++----------------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index ca725f9bd4..f943c048e6 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -31,12 +31,12 @@ export default class HunkHeaderView extends React.Component { render() { const conditional = { - 'is-selected': this.props.isSelected, - 'is-hunkMode': this.props.selectionMode === 'hunk', + 'github-HunkHeaderView--isSelected': this.props.isSelected, + 'github-HunkHeaderView--isHunkMode': this.props.selectionMode === 'hunk', }; return ( -
+
{this.props.hunk.getHeader().trim()} {this.props.hunk.getSectionHeading().trim()} diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 1b5ed499eb..193a97d759 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -164,18 +164,19 @@ } } -.gutter.old { +.gutter.old, .gutter.new { width: 5em; - .line-number { - opacity: 1; + div { + width: 100%; } -} - -.gutter.new { - width: 5em; .line-number { opacity: 1; + + &.github-FilePatchView-line--selected { + color: contrast(@button-background-color-selected); + background: @button-background-color-selected; + } } } diff --git a/styles/hunk-header-view.less b/styles/hunk-header-view.less index a1e1e19449..253c0c2075 100644 --- a/styles/hunk-header-view.less +++ b/styles/hunk-header-view.less @@ -11,6 +11,7 @@ font-size: .9em; background-color: @panel-heading-background-color; border-bottom: 1px solid @panel-heading-border-color; + cursor: default; &-title { flex: 1; @@ -81,29 +82,28 @@ &-lineText { display: inline-block; text-indent: 0; - } + } } - // // States // ------------------------------- -.github-HunkView.is-selected.is-hunkMode .github-HunkView-header { - background-color: @background-color-selected; - .github-HunkView-title { - color: @text-color; +.github-HunkHeaderView--isSelected.github-HunkHeaderView--isHunkMode { + background-color: @button-background-color-selected; + .github-HunkHeaderView-title { + color: contrast(@button-background-color-selected); } - .github-HunkView-stageButton, .github-HunkView-discardButton { + .github-HunkHeaderView-stageButton, .github-HunkHeaderView-discardButton { border-color: mix(@text-color, @background-color-selected, 25%); } } -.github-HunkView-title:hover { +.github-HunkHeaderView-title:hover { color: @text-color-highlight; } -.github-HunkView-line { +.github-HunkHeaderView-line { // mixin .hunk-line-mixin(@fg; @bg) { @@ -114,12 +114,12 @@ color: @text-color; background-color: @background-color-selected; } - .github-HunkView-lineContent { + .github-HunkHeaderView-lineContent { color: saturate( mix(@fg, @text-color-highlight, 20%), 20%); background-color: saturate( mix(@bg, @hunk-bg-color, 15%), 20%); } // hightlight when focused + selected - .github-FilePatchView:focus &.is-selected .github-HunkView-lineContent { + .github-FilePatchView:focus &.is-selected .github-HunkHeaderView-lineContent { color: saturate( mix(@fg, @text-color-highlight, 10%), 10%); background-color: saturate( mix(@bg, @hunk-bg-color, 25%), 10%); } @@ -134,7 +134,7 @@ } // divider line between added and deleted lines - &.is-deleted + .is-added .github-HunkView-lineContent { + &.is-deleted + .is-added .github-HunkHeaderView-lineContent { box-shadow: 0 -1px 0 hsla(0,0%,50%,.1); } @@ -142,16 +142,16 @@ // focus colors .github-FilePatchView:focus { - .github-HunkView.is-selected.is-hunkMode .github-HunkView-title, - .github-HunkView.is-selected.is-hunkMode .github-HunkView-header, - .github-HunkView-line.is-selected .github-HunkView-lineNumber { + .github-HunkHeaderView.is-selected.is-hunkMode .github-HunkHeaderView-title, + .github-HunkHeaderView.is-selected.is-hunkMode .github-HunkHeaderView-header, + .github-HunkHeaderView-line.is-selected .github-HunkHeaderView-lineNumber { color: contrast(@button-background-color-selected); background: @button-background-color-selected; } - .github-HunkView-line.is-selected .github-HunkView-lineNumber { + .github-HunkHeaderView-line.is-selected .github-HunkHeaderView-lineNumber { border-color: mix(@button-border-color, @button-background-color-selected, 25%); } - .github-HunkView.is-selected.is-hunkMode .github-HunkView { + .github-HunkHeaderView.is-selected.is-hunkMode .github-HunkHeaderView { &-stageButton, &-discardButton { border-color: mix(@hunk-bg-color, @button-background-color-selected, 30%); From b5c7619be436fd4d03c2492394eed66e4ba029c3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 25 Jul 2018 10:22:00 -0400 Subject: [PATCH 0031/4053] :scissors: unused code --- lib/views/file-patch-view.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 0297545c14..824816fb3a 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -332,17 +332,6 @@ export default class FilePatchView extends React.Component { }); } - renderSelectedLines() { - return ( - - {Array.from(this.state.selectedLines, line => { - const position = this.state.presentedFilePatch.getPositionForLine(line); - return ; - })} - - ); - } - renderLineDecorations(positions, lineClass, {line, gutter}) { return ( From 963e303b92a08ba3d9d2e67f9e83e0ff79089a4c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 25 Jul 2018 10:22:15 -0400 Subject: [PATCH 0032/4053] Use the position as a key for Marker components --- lib/views/file-patch-view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 824816fb3a..3c271e6858 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -335,8 +335,8 @@ export default class FilePatchView extends React.Component { renderLineDecorations(positions, lineClass, {line, gutter}) { return ( - {positions.map((position, index) => { - return ; + {positions.map(position => { + return ; })} {line && } From 6677a6d4bc8beb9277876c1c193dc91d607d7b60 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 25 Jul 2018 13:54:12 -0400 Subject: [PATCH 0033/4053] Staging and discard actions --- lib/controllers/file-patch-controller.js | 134 +++++++++++++++++++---- lib/controllers/root-controller.js | 1 + lib/items/file-patch-item.js | 1 + lib/views/file-patch-view.js | 29 ++++- lib/views/hunk-header-view.js | 10 +- 5 files changed, 147 insertions(+), 28 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index ba20e3d24e..3b6741a218 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -19,6 +19,7 @@ export default class FilePatchController extends React.Component { tooltips: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, + discardLines: PropTypes.func.isRequired, undoLastDiscard: PropTypes.func.isRequired, } @@ -27,14 +28,42 @@ export default class FilePatchController extends React.Component { autobind( this, 'mouseDownOnHeader', 'mouseDownOnLineNumber', 'mouseMoveOnLineNumber', 'mouseUp', - 'undoLastDiscard', 'diveIntoMirrorPatch', 'openFile', 'toggleFile', 'toggleModeChange', 'toggleSymlinkChange', + 'undoLastDiscard', 'diveIntoMirrorPatch', 'openFile', + 'toggleFile', 'selectAndToggleHunk', 'toggleLines', 'toggleModeChange', 'toggleSymlinkChange', + 'discardLines', 'selectAndDiscardHunk', ); this.state = { + lastFilePatch: this.props.filePatch, selection: new FilePatchSelection(this.props.filePatch.getHunks()), }; this.mouseSelectionInProgress = false; + this.stagingOperationInProgress = false; + + this.patchChangePromise = new Promise(resolve => { + this.resolvePatchChangePromise = resolve; + }); + } + + static getDerivedStateFromProps(props, state) { + if (props.filePatch !== state.lastFilePatch) { + return { + selection: state.selection.updateHunks(props.filePatch.getHunks()), + lastFilePatch: props.filePatch, + }; + } + + return null; + } + + componentDidUpdate(prevProps) { + if (prevProps.filePatch !== this.props.filePatch) { + this.resolvePatchChangePromise(); + this.patchChangePromise = new Promise(resolve => { + this.resolvePatchChangePromise = resolve; + }); + } } render() { @@ -49,12 +78,16 @@ export default class FilePatchController extends React.Component { mouseMoveOnLineNumber={this.mouseMoveOnLineNumber} mouseUp={this.mouseUp} - undoLastDiscard={this.undoLastDiscard} diveIntoMirrorPatch={this.diveIntoMirrorPatch} openFile={this.openFile} toggleFile={this.toggleFile} + selectAndToggleHunk={this.selectAndToggleHunk} + toggleLines={this.toggleLines} toggleModeChange={this.toggleModeChange} toggleSymlinkChange={this.toggleSymlinkChange} + undoLastDiscard={this.undoLastDiscard} + discardLines={this.discardLines} + selectAndDiscardHunk={this.selectAndDiscardHunk} /> ); } @@ -163,7 +196,7 @@ export default class FilePatchController extends React.Component { const workingDirectory = this.props.repository.getWorkingDirectoryPath(); const uri = FilePatchItem.buildURI(this.props.relPath, workingDirectory, mirrorStatus); - this.destroy(); + this.props.destroy(); await this.props.workspace.open(uri); } @@ -176,35 +209,78 @@ export default class FilePatchController extends React.Component { } toggleFile() { - const methodName = this.withStagingStatus({staged: 'unstageFile', unstaged: 'stageFile'}); - return this.props.repository[methodName]([this.props.relPath]); + return this.stagingOperation(() => { + const methodName = this.withStagingStatus({staged: 'unstageFile', unstaged: 'stageFile'}); + return this.props.repository[methodName]([this.props.relPath]); + }); + } + + async selectAndToggleHunk(hunk) { + await this.selectHunk(hunk); + + return this.stagingOperation(() => { + const patch = this.withStagingStatus({ + staged: () => this.props.filePatch.getUnstagePatchForHunk(hunk), + unstaged: () => this.props.filePatch.getStagePatchForHunk(hunk), + }); + return this.props.repository.applyPatchToIndex(patch); + }); + } + + toggleLines(lines) { + return this.stagingOperation(() => { + const patch = this.withStagingStatus({ + staged: () => this.props.filePatch.getUnstagePatchForLines(lines), + unstaged: () => this.props.filePatch.getStagePatchForLines(lines), + }); + return this.props.repository.applyPatchToIndex(patch); + }); } toggleModeChange() { - const targetMode = this.withStagingStatus({ - unstaged: this.props.filePatch.getNewMode(), - staged: this.props.filePatch.getOldMode(), + return this.stagingOperation(() => { + const targetMode = this.withStagingStatus({ + unstaged: this.props.filePatch.getNewMode(), + staged: this.props.filePatch.getOldMode(), + }); + return this.props.repository.stageFileModeChange(this.props.relPath, targetMode); }); - return this.props.repository.stageFileModeChange(this.props.relPath, targetMode); } toggleSymlinkChange() { - const {filePatch, relPath, repository} = this.props; - return this.withStagingStatus({ - unstaged: () => { - if (filePatch.hasTypechange() && filePatch.getStatus() === 'added') { - return repository.stageFileSymlinkChange(relPath); - } + return this.stagingOperation(() => { + const {filePatch, relPath, repository} = this.props; + return this.withStagingStatus({ + unstaged: () => { + if (filePatch.hasTypechange() && filePatch.getStatus() === 'added') { + return repository.stageFileSymlinkChange(relPath); + } - return repository.stageFiles([relPath]); - }, - staged: () => { - if (filePatch.hasTypechange() && filePatch.getStatus() === 'deleted') { - return repository.stageFileSymlinkChange(relPath); - } + return repository.stageFiles([relPath]); + }, + staged: () => { + if (filePatch.hasTypechange() && filePatch.getStatus() === 'deleted') { + return repository.stageFileSymlinkChange(relPath); + } - return repository.unstageFiles([relPath]); - }, + return repository.unstageFiles([relPath]); + }, + }); + }); + } + + discardLines(lines) { + return this.props.discardLines(this.props.filePatch, lines, this.props.repository); + } + + async selectAndDiscardHunk(hunk) { + await this.selectHunk(hunk); + return this.discardLines(hunk.getLines()); + } + + selectHunk(hunk) { + return new Promise(resolve => { + this.setState(prevState => ({selection: prevState.selection.selectHunk(hunk)}), resolve); }); } @@ -215,4 +291,16 @@ export default class FilePatchController extends React.Component { } return callback instanceof Function ? callback() : callback; } + + stagingOperation(fn) { + if (this.stagingOperationInProgress) { + return null; + } + this.stagingOperationInProgress = true; + this.patchChangePromise.then(() => { + this.stagingOperationInProgress = false; + }); + + return fn(); + } } diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index a0a373e63b..85cb421385 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -317,6 +317,7 @@ export default class RootController extends React.Component { tooltips={this.props.tooltips} workspace={this.props.workspace} + discardLines={this.discardLines} undoLastDiscard={this.undoLastDiscard} /> )} diff --git a/lib/items/file-patch-item.js b/lib/items/file-patch-item.js index b8b108e354..65e23e70ce 100644 --- a/lib/items/file-patch-item.js +++ b/lib/items/file-patch-item.js @@ -17,6 +17,7 @@ export default class FilePatchItem extends React.Component { workspace: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, + discardLines: PropTypes.func.isRequired, undoLastDiscard: PropTypes.func.isRequired, } diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 3c271e6858..2a879728aa 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -35,19 +35,24 @@ export default class FilePatchView extends React.Component { mouseMoveOnLineNumber: PropTypes.func.isRequired, mouseUp: PropTypes.func.isRequired, - undoLastDiscard: PropTypes.func.isRequired, diveIntoMirrorPatch: PropTypes.func.isRequired, openFile: PropTypes.func.isRequired, toggleFile: PropTypes.func.isRequired, + selectAndToggleHunk: PropTypes.func.isRequired, + toggleLines: PropTypes.func.isRequired, toggleModeChange: PropTypes.func.isRequired, toggleSymlinkChange: PropTypes.func.isRequired, + undoLastDiscard: PropTypes.func.isRequired, + discardLines: PropTypes.func.isRequired, + selectAndDiscardHunk: PropTypes.func.isRequired, } constructor(props) { super(props); autobind( this, - 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', 'oldLineNumberLabel', 'newLineNumberLabel', + 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', + 'oldLineNumberLabel', 'newLineNumberLabel', ); const presentedFilePatch = this.props.filePatch.present(); @@ -322,8 +327,8 @@ export default class FilePatchView extends React.Component { tooltips={this.props.tooltips} - toggleSelection={() => {}} - discardSelection={() => {}} + toggleSelection={() => this.toggleSelection(hunk)} + discardSelection={() => this.discardSelection(hunk)} mouseDown={this.props.mouseDownOnHeader} /> @@ -388,6 +393,22 @@ export default class FilePatchView extends React.Component { return this.pad(this.state.presentedFilePatch.getNewLineNumberAt(bufferRow)); } + toggleSelection(hunk) { + if (this.state.selectedHunks.has(hunk)) { + return this.props.toggleLines(this.state.selectedLines); + } else { + return this.props.selectAndToggleHunk(hunk); + } + } + + discardSelection(hunk) { + if (this.state.selectedHunks.has(hunk)) { + return this.props.discardLines(this.state.selectedLines); + } else { + return this.props.selectAndDiscardHunk(hunk); + } + } + pad(num) { const maxDigits = this.state.presentedFilePatch.getMaxLineNumberWidth(); if (num === undefined || num === -1) { diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index f943c048e6..ec92b92c96 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -6,6 +6,10 @@ import {autobind} from '../helpers'; import RefHolder from '../models/ref-holder'; import Tooltip from '../atom/tooltip'; +function theBuckStopsHere(event) { + event.stopPropagation(); +} + export default class HunkHeaderView extends React.Component { static propTypes = { hunk: PropTypes.object.isRequired, @@ -40,7 +44,10 @@ export default class HunkHeaderView extends React.Component { {this.props.hunk.getHeader().trim()} {this.props.hunk.getSectionHeading().trim()} - {this.props.stagingStatus === 'unstaged' && ( @@ -49,6 +56,7 @@ export default class HunkHeaderView extends React.Component { ref={this.refDiscardButton.setter} className="icon-trashcan github-HunkHeaderView-discardButton" onClick={this.props.discardSelection} + onMouseDown={theBuckStopsHere} /> Date: Wed, 25 Jul 2018 13:54:20 -0400 Subject: [PATCH 0034/4053] Let's put HunkView to the side --- lib/views/{hunk-view.js => hunk-view.old.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/views/{hunk-view.js => hunk-view.old.js} (100%) diff --git a/lib/views/hunk-view.js b/lib/views/hunk-view.old.js similarity index 100% rename from lib/views/hunk-view.js rename to lib/views/hunk-view.old.js From 4d71aed29a1cbde92f4ff6c38915aa3d18fabd97 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 25 Jul 2018 14:04:21 -0400 Subject: [PATCH 0035/4053] Use diff to... generate diffs --- package-lock.json | 5 ++--- package.json | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 210339e328..191f531911 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2360,8 +2360,7 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" }, "discontinuous-range": { "version": "1.0.0", @@ -5488,7 +5487,7 @@ }, "lru-cache": { "version": "4.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "requires": { "pseudomap": "^1.0.2", diff --git a/package.json b/package.json index d6737e4946..8e3f3fb420 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "bytes": "^3.0.0", "classnames": "2.2.6", "compare-sets": "1.0.1", + "diff": "3.5.0", "dugite": "^1.66.0", "event-kit": "2.5.0", "fs-extra": "4.0.3", From fe8d0f7eb11575ede0c3d0bf47a6108dedeac9e2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 25 Jul 2018 14:18:25 -0400 Subject: [PATCH 0036/4053] On second thought don't --- package-lock.json | 3 ++- package.json | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 191f531911..167e2c0681 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2360,7 +2360,8 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true }, "discontinuous-range": { "version": "1.0.0", diff --git a/package.json b/package.json index 8e3f3fb420..d6737e4946 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "bytes": "^3.0.0", "classnames": "2.2.6", "compare-sets": "1.0.1", - "diff": "3.5.0", "dugite": "^1.66.0", "event-kit": "2.5.0", "fs-extra": "4.0.3", From 40b225e1a4b96fbc56e815528a760f22b449778a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 26 Jul 2018 08:39:58 -0400 Subject: [PATCH 0037/4053] Allow TextEditor text to be set with setTextViaDiff to preserve markers --- lib/atom/atom-text-editor.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index b2b77c276e..6f2665a032 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -43,6 +43,7 @@ export default class AtomTextEditor extends React.PureComponent { text: PropTypes.string, didChange: PropTypes.func, didChangeCursorPosition: PropTypes.func, + preserveMarkers: PropTypes.bool, children: PropTypes.node, } @@ -103,7 +104,12 @@ export default class AtomTextEditor extends React.PureComponent { quietlySetText(text) { this.suppressChange = true; try { - this.getModel().setText(text); + const editor = this.getModel(); + if (this.props.preserveMarkers) { + editor.getBuffer().setTextViaDiff(text); + } else { + editor.setText(text); + } } finally { this.suppressChange = false; } From 13950a933e5401d4f37dd19ccffc3aca22e195a9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 26 Jul 2018 08:40:17 -0400 Subject: [PATCH 0038/4053] Accept a nameMap in extractProps to modify names --- lib/helpers.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/helpers.js b/lib/helpers.js index 5e7c020f11..cc572630ed 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -37,10 +37,11 @@ export function autobind(self, ...methods) { // } // } // ``` -export function extractProps(props, propTypes) { +export function extractProps(props, propTypes, nameMap = {}) { return Object.keys(propTypes).reduce((opts, propName) => { if (props[propName] !== undefined) { - opts[propName] = props[propName]; + const destPropName = nameMap[propName] || propName; + opts[destPropName] = props[propName]; } return opts; }, {}); From 3960cab7fba3fdb867af2636223c82052e57693e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 26 Jul 2018 08:40:28 -0400 Subject: [PATCH 0039/4053] Remove unused state --- lib/atom/gutter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/atom/gutter.js b/lib/atom/gutter.js index 34a29873cc..713a337d92 100644 --- a/lib/atom/gutter.js +++ b/lib/atom/gutter.js @@ -36,7 +36,6 @@ class BareGutter extends React.Component { this.state = { gutter: null, - editorComponent: null, }; this.sub = new Disposable(); From c7186fe777506698a88657ed1bb3a161fb20b1d1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 26 Jul 2018 08:40:44 -0400 Subject: [PATCH 0040/4053] Update Decorations and Markers in place --- lib/atom/decoration.js | 59 ++++++++++++++++++++++++++---------------- lib/atom/marker.js | 55 ++++++++++++++++++++++++++++++++++----- 2 files changed, 85 insertions(+), 29 deletions(-) diff --git a/lib/atom/decoration.js b/lib/atom/decoration.js index 08ec2b901a..c9bf333a87 100644 --- a/lib/atom/decoration.js +++ b/lib/atom/decoration.js @@ -3,29 +3,37 @@ import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import {Disposable} from 'event-kit'; -import {createItem, autobind} from '../helpers'; +import {createItem, autobind, extractProps} from '../helpers'; import {RefHolderPropType} from '../prop-types'; import {TextEditorContext} from './atom-text-editor'; import {DecorableContext} from './marker'; import RefHolder from '../models/ref-holder'; -class WrappedDecoration extends React.Component { +const decorationPropTypes = { + type: PropTypes.oneOf(['line', 'line-number', 'highlight', 'overlay', 'gutter', 'block']).isRequired, + className: PropTypes.string, + style: PropTypes.string, + onlyHead: PropTypes.bool, + onlyEmpty: PropTypes.bool, + onlyNonEmpty: PropTypes.bool, + omitEmptyLastRow: PropTypes.bool, + position: PropTypes.oneOf(['head', 'tail', 'before', 'after']), + avoidOverflow: PropTypes.bool, + gutterName: PropTypes.string, +}; + +class BareDecoration extends React.Component { static propTypes = { editorHolder: RefHolderPropType.isRequired, markerHolder: RefHolderPropType.isRequired, decorateMethod: PropTypes.oneOf(['decorateMarker', 'decorateMarkerLayer']), - type: PropTypes.oneOf(['line', 'line-number', 'highlight', 'overlay', 'gutter', 'block']).isRequired, - position: PropTypes.oneOf(['head', 'tail', 'before', 'after']), - className: PropTypes.string, - children: PropTypes.node, itemHolder: RefHolderPropType, - options: PropTypes.object, + children: PropTypes.node, + ...decorationPropTypes, } static defaultProps = { decorateMethod: 'decorateMarker', - options: {}, - position: 'head', } constructor(props, context) { @@ -64,6 +72,13 @@ class WrappedDecoration extends React.Component { this.markerSub.dispose(); this.markerSub = this.state.markerHolder.observe(this.observeParents); } + + if ( + Object.keys(decorationPropTypes).some(key => this.props[key] !== prevProps[key]) + ) { + const opts = this.getDecorationOpts(this.props); + this.decorationHolder.map(decoration => decoration.setProperties(opts)); + } } render() { @@ -78,6 +93,8 @@ class WrappedDecoration extends React.Component { } observeParents() { + this.decorationHolder.map(decoration => decoration.destroy()); + if (this.props.editorHolder.isEmpty() || this.props.markerHolder.isEmpty()) { return; } @@ -86,25 +103,16 @@ class WrappedDecoration extends React.Component { } createDecoration() { - this.decorationHolder.map(decoration => decoration.destroy()); - if (!this.item) { this.item = createItem(this.domNode, this.props.itemHolder); } - const options = { - ...this.props.options, - type: this.props.type, - position: this.props.position, - class: this.props.className, - item: this.item, - }; - + const opts = this.getDecorationOpts(this.props); const editor = this.props.editorHolder.get(); const marker = this.props.markerHolder.get(); this.decorationHolder.setter( - editor[this.props.decorateMethod](marker, options), + editor[this.props.decorateMethod](marker, opts), ); } @@ -113,6 +121,13 @@ class WrappedDecoration extends React.Component { this.editorSub.dispose(); this.markerSub.dispose(); } + + getDecorationOpts(props) { + return { + ...extractProps(props, decorationPropTypes, {className: 'class'}), + item: this.item, + }; + } } export default class Decoration extends React.Component { @@ -151,7 +166,7 @@ export default class Decoration extends React.Component { render() { if (!this.state.editorHolder.isEmpty() && !this.state.markerHolder.isEmpty()) { return ( - ( {({holder, decorateMethod}) => ( - prevProps[key] !== this.props[key])) { + this.markerHolder.map(marker => marker.setProperties(extractProps(this.props, markerProps))); } } @@ -124,6 +145,28 @@ class WrappedMarker extends React.Component { this.markerHolder.map(marker => this.props.handleID(marker.id)); } + + updateMarkerPosition() { + this.markerHolder.map(marker => { + if (this.props.bufferRange) { + return marker.setBufferRange(this.props.bufferRange); + } + + if (this.props.screenRange) { + return marker.setScreenRange(this.props.screenRange); + } + + if (this.props.bufferPosition) { + return marker.setBufferRange(new Range(this.props.bufferPosition, this.props.bufferPosition)); + } + + if (this.props.screenPosition) { + return marker.setScreenRange(new Range(this.props.screenPosition, this.props.screenPosition)); + } + + throw new Error('Expected one of bufferRange, screenRange, bufferPosition, or screenPosition to be set'); + }); + } } export default class Marker extends React.Component { @@ -154,25 +197,23 @@ export default class Marker extends React.Component { render() { if (!this.state.markableHolder.isEmpty()) { - return ; + return ; } - /* eslint-disable react/jsx-key */ return ( {layerHolder => { if (layerHolder) { - return ; + return ; } else { return ( - {editorHolder => } + {editorHolder => } ); } }} ); - /* eslint-enable react/jsx-key */ } } From 401d662114d577addb6227c4b40b9a61bc7b9d75 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 26 Jul 2018 08:41:33 -0400 Subject: [PATCH 0041/4053] Update FilePatchView uses of Marker, AtomTextEditor, and Decoration --- lib/views/file-patch-view.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 2a879728aa..fa9bdfeae4 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -122,9 +122,11 @@ export default class FilePatchView extends React.Component {
+ + + {positions.map(position => { return ; })} @@ -347,8 +349,8 @@ export default class FilePatchView extends React.Component { {line && } {gutter && ( - - + + )} From 3ead0884c127d7cbec7673eaa3122a3a8ad7bab2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 26 Jul 2018 11:29:21 -0400 Subject: [PATCH 0042/4053] Update AtomTextEditor's text before children render --- lib/atom/atom-text-editor.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index 6f2665a032..b2f07a5221 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -65,6 +65,8 @@ export default class AtomTextEditor extends React.PureComponent { } render() { + this.refModel.map(() => this.quietlySetText(this.props.text)); + return ( @@ -80,6 +82,7 @@ export default class AtomTextEditor extends React.PureComponent { this.refElement.map(element => { const editor = element.getModel(); + editor.setText(this.props.text); this.refModel.setter(editor); @@ -105,6 +108,10 @@ export default class AtomTextEditor extends React.PureComponent { this.suppressChange = true; try { const editor = this.getModel(); + if (editor.getText() === text) { + return; + } + if (this.props.preserveMarkers) { editor.getBuffer().setTextViaDiff(text); } else { @@ -117,13 +124,7 @@ export default class AtomTextEditor extends React.PureComponent { setAttributesOnElement(theProps) { const modelProps = extractProps(this.props, editorProps); - - const editor = this.getModel(); - editor.update(modelProps); - - if (editor.getText() !== theProps.text) { - this.quietlySetText(theProps.text); - } + this.getModel().update(modelProps); } didChange() { From 54010cda45d1794a2f1eb6826d8756e30e5d9a62 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 26 Jul 2018 11:30:53 -0400 Subject: [PATCH 0043/4053] Update component naming for consistency --- lib/atom/marker-layer.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/atom/marker-layer.js b/lib/atom/marker-layer.js index 8793c3ee50..9c483d7582 100644 --- a/lib/atom/marker-layer.js +++ b/lib/atom/marker-layer.js @@ -14,7 +14,7 @@ const markerLayerProps = { export const MarkerLayerContext = React.createContext(); -class WrappedMarkerLayer extends React.Component { +class BareMarkerLayer extends React.Component { static propTypes = { ...markerLayerProps, editor: PropTypes.object, @@ -88,7 +88,6 @@ class WrappedMarkerLayer extends React.Component { const options = extractProps(this.props, markerLayerProps); - this.layerHolder.setter( this.state.editorHolder.map(editor => editor.addMarkerLayer(options)).getOr(null), ); @@ -104,7 +103,7 @@ export default class MarkerLayer extends React.Component { render() { return ( - {editor => } + {editor => } ); } From 00bb1fc6a4426e97667a2e04a6759b303233c033 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 26 Jul 2018 11:31:11 -0400 Subject: [PATCH 0044/4053] Destroy empty MarkerLayers --- lib/views/file-patch-view.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index fa9bdfeae4..8bf69343e7 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -340,6 +340,10 @@ export default class FilePatchView extends React.Component { } renderLineDecorations(positions, lineClass, {line, gutter}) { + if (positions.length === 0) { + return null; + } + return ( {positions.map(position => { From 5b9cd7d3f7f25cab5d7a54ff7c1e593e4654ad58 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 26 Jul 2018 11:31:37 -0400 Subject: [PATCH 0045/4053] Use Constructor.fromObject instead of new Constructor --- lib/atom/marker.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/atom/marker.js b/lib/atom/marker.js index 1e1fcad17a..1324cb1b06 100644 --- a/lib/atom/marker.js +++ b/lib/atom/marker.js @@ -35,6 +35,17 @@ export const MarkerContext = React.createContext(); export const DecorableContext = React.createContext(); +// Compare Range or Point compatible props +function changed(Constructor, from, to) { + if (from == null && to == null) { + return false; + } else if (from == null || to == null) { + return true; + } else { + return !Constructor.fromObject(from).isEqual(to); + } +} + class BareMarker extends React.Component { static propTypes = { ...markerProps, @@ -80,16 +91,6 @@ class BareMarker extends React.Component { } componentDidUpdate(prevProps) { - function changed(Constructor, from, to) { - if (!from && !to) { - return false; - } else if (!from || !to) { - return true; - } else { - return !(new Constructor(from).isEqual(to)); - } - } - if (prevProps.markableHolder !== this.props.markableHolder) { this.observeMarkable(); } else if ( From 2bfe0d6f2c3b5a8f9110394d869bf186d2f6c4c1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 09:42:33 -0400 Subject: [PATCH 0046/4053] Style the selected cursor line --- styles/file-patch-view.less | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 193a97d759..589dae1269 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -152,6 +152,11 @@ .hunk-line-mixin(@fg; @bg) { color: saturate( mix(@fg, @text-color-highlight, 20%), 20%); background-color: saturate( mix(@bg, @hunk-bg-color, 15%), 20%); + + &.line.cursor-line { + color: saturate( mix(@fg, @text-color-highlight, 20%), 80%); + background-color: saturate( mix(@bg, @hunk-bg-color, 15%), 80%); + } } &--deleted { From a1e76166bc39b7ec0884262fd6259d0190cf8bf5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 09:47:09 -0400 Subject: [PATCH 0047/4053] MarkerPosition model for different ways markers can be positioned --- lib/models/marker-position.js | 155 ++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 lib/models/marker-position.js diff --git a/lib/models/marker-position.js b/lib/models/marker-position.js new file mode 100644 index 0000000000..7e9989f1ca --- /dev/null +++ b/lib/models/marker-position.js @@ -0,0 +1,155 @@ +import {Range} from 'atom'; + +class Position { + markOn(markable, options) { + throw new Error('markOn not overridden'); + } + + setIn(marker) { + throw new Error('setIn not overridden'); + } + + matches(other) { + throw new Error('matches not overridden'); + } + + matchFromBufferRange(other) { + return false; + } + + matchFromScreenRange(other) { + return false; + } +} + +class BufferRangePosition extends Position { + constructor(bufferRange) { + super(); + this.bufferRange = Range.fromObject(bufferRange); + } + + markOn(markable, options) { + return markable.markBufferRange(this.bufferRange, options); + } + + setIn(marker) { + return marker.setBufferRange(this.bufferRange); + } + + matches(other) { + return other.matchFromBufferRange(this); + } + + matchFromBufferRange(other) { + return other.bufferRange.isEqual(this.bufferRange); + } + + toString() { + return `buffer(${this.bufferRange.toString()})`; + } +} + +class ScreenRangePosition extends Position { + constructor(screenRange) { + super(); + this.screenRange = screenRange; + } + + markOn(markable, options) { + return markable.markScreenRange(this.screenRange, options); + } + + setIn(marker) { + return marker.setScreenRange(this.screenRange); + } + + matches(other) { + return other.matchFromScreenRange(this); + } + + matchFromScreenRange(other) { + return other.screenRange.isEqual(this.screenRange); + } + + toString() { + return `screen(${this.screenRange.toString()})`; + } +} + +class BufferOrScreenRangePosition extends Position { + constructor(bufferRange, screenRange) { + super(); + this.bufferRange = bufferRange; + this.screenRange = screenRange; + } + + markOn(markable, options) { + return markable.markBufferRange(this.bufferRange); + } + + setIn(marker) { + return marker.setBufferRange(this.bufferRange); + } + + matches(other) { + return other.matchFromBufferRange(this) || other.matchFromScreenRange(this); + } + + matchFromBufferRange(other) { + return other.bufferRange.isEqual(this.bufferRange); + } + + matchFromScreenRange(other) { + return other.screenRange.isEqual(this.screenRange); + } + + toString() { + return `either(b${this.bufferRange.toString()}/s${this.screenRange.toString()})`; + } +} + +export function fromBufferRange(bufferRange) { + return new BufferRangePosition(Range.fromObject(bufferRange)); +} + +export function fromBufferPosition(bufferPoint) { + return new BufferRangePosition(new Range(bufferPoint, bufferPoint)); +} + +export function fromScreenRange(screenRange) { + return new ScreenRangePosition(Range.fromObject(screenRange)); +} + +export function fromScreenPosition(screenPoint) { + return new ScreenRangePosition(new Range(screenPoint, screenPoint)); +} + +export function fromMarker(marker) { + return new BufferOrScreenRangePosition( + marker.getBufferRange(), + marker.getScreenRange(), + ); +} + +export function fromChangeEvent(event, reversed = false) { + const oldBufferStartPosition = reversed ? event.oldHeadBufferPosition : event.oldTailBufferPosition; + const oldBufferEndPosition = reversed ? event.oldTailBufferPosition : event.oldHeadBufferPosition; + const oldScreenStartPosition = reversed ? event.oldHeadScreenPosition : event.oldTailScreenPosition; + const oldScreenEndPosition = reversed ? event.oldTailScreenPosition : event.oldHeadScreenPosition; + + const newBufferStartPosition = reversed ? event.newHeadBufferPosition : event.newTailBufferPosition; + const newBufferEndPosition = reversed ? event.newTailBufferPosition : event.newHeadBufferPosition; + const newScreenStartPosition = reversed ? event.newHeadScreenPosition : event.newTailScreenPosition; + const newScreenEndPosition = reversed ? event.newTailScreenPosition : event.newHeadScreenPosition; + + return { + oldPosition: new BufferOrScreenRangePosition( + new Range(oldBufferStartPosition, oldBufferEndPosition), + new Range(oldScreenStartPosition, oldScreenEndPosition), + ), + newPosition: new BufferOrScreenRangePosition( + new Range(newBufferStartPosition, newBufferEndPosition), + new Range(newScreenStartPosition, newScreenEndPosition), + ), + }; +} From d760888c3027f9cc714725a74274a50d033397fc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 09:47:30 -0400 Subject: [PATCH 0048/4053] Consume MarkerPosition in --- lib/atom/marker.js | 124 +++++++++++++++++---------------------------- lib/prop-types.js | 6 +++ 2 files changed, 52 insertions(+), 78 deletions(-) diff --git a/lib/atom/marker.js b/lib/atom/marker.js index 1324cb1b06..f6ba3819fe 100644 --- a/lib/atom/marker.js +++ b/lib/atom/marker.js @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Disposable} from 'event-kit'; -import {Range, Point} from 'atom'; +import {CompositeDisposable} from 'event-kit'; import {autobind, extractProps} from '../helpers'; -import {RefHolderPropType} from '../prop-types'; +import {RefHolderPropType, MarkerPositionPropType} from '../prop-types'; import RefHolder from '../models/ref-holder'; +import {fromChangeEvent, fromMarker} from '../models/marker-position'; import {TextEditorContext} from './atom-text-editor'; import {MarkerLayerContext} from './marker-layer'; @@ -16,16 +16,6 @@ const MarkablePropType = PropTypes.shape({ markScreenPosition: PropTypes.func.isRequired, }); -const RangePropType = PropTypes.oneOfType([ - PropTypes.array, - PropTypes.instanceOf(Range), -]); - -const PointPropType = PropTypes.oneOfType([ - PropTypes.array, - PropTypes.instanceOf(Point), -]); - const markerProps = { reversed: PropTypes.bool, invalidate: PropTypes.oneOf(['never', 'surround', 'overlap', 'inside', 'touch']), @@ -35,39 +25,27 @@ export const MarkerContext = React.createContext(); export const DecorableContext = React.createContext(); -// Compare Range or Point compatible props -function changed(Constructor, from, to) { - if (from == null && to == null) { - return false; - } else if (from == null || to == null) { - return true; - } else { - return !Constructor.fromObject(from).isEqual(to); - } -} - class BareMarker extends React.Component { static propTypes = { ...markerProps, - bufferRange: RangePropType, - bufferPosition: PointPropType, - screenRange: RangePropType, - screenPosition: PointPropType, + position: MarkerPositionPropType.isRequired, markableHolder: RefHolderPropType, children: PropTypes.node, + onDidChange: PropTypes.func, handleID: PropTypes.func, } static defaultProps = { + onDidChange: () => {}, handleID: () => {}, } constructor(props) { super(props); - autobind(this, 'createMarker'); + autobind(this, 'createMarker', 'didChange'); - this.sub = new Disposable(); + this.subs = new CompositeDisposable(); this.markerHolder = new RefHolder(); this.decorable = { @@ -81,6 +59,7 @@ class BareMarker extends React.Component { } render() { + this.updateMarkerPosition(); return ( @@ -93,13 +72,6 @@ class BareMarker extends React.Component { componentDidUpdate(prevProps) { if (prevProps.markableHolder !== this.props.markableHolder) { this.observeMarkable(); - } else if ( - changed(Range, prevProps.bufferRange, this.props.bufferRange) || - changed(Point, prevProps.bufferPosition, this.props.bufferPosition) || - changed(Range, prevProps.screenRange, this.props.screenRange) || - changed(Point, prevProps.screenPosition, this.props.screenPosition) - ) { - this.updateMarkerPosition(); } if (Object.keys(markerProps).some(key => prevProps[key] !== this.props[key])) { @@ -108,64 +80,60 @@ class BareMarker extends React.Component { } componentWillUnmount() { + this.markerHolder.map(marker => console.log(`destroying marker ${marker.id} due to unmount`)); this.markerHolder.map(marker => marker.destroy()); - this.sub.dispose(); + this.subs.dispose(); } observeMarkable() { - this.sub.dispose(); - this.sub = this.props.markableHolder.observe(this.createMarker); + this.subs.dispose(); + this.subs = new CompositeDisposable( + this.props.markableHolder.observe(this.createMarker), + ); } createMarker() { + this.markerHolder.map(marker => console.log(`destroying marker ${marker.id} to create a new one`)); this.markerHolder.map(marker => marker.destroy()); const options = extractProps(this.props, markerProps); - this.markerHolder.setter( - this.props.markableHolder.map(markable => { - if (this.props.bufferRange) { - return markable.markBufferRange(this.props.bufferRange, options); - } - - if (this.props.screenRange) { - return markable.markScreenRange(this.props.screenRange, options); - } - - if (this.props.bufferPosition) { - return markable.markBufferPosition(this.props.bufferPosition, options); - } - - if (this.props.screenPosition) { - return markable.markScreenPosition(this.props.screenPosition, options); - } - - throw new Error('Expected one of bufferRange, screenRange, bufferPosition, or screenPosition to be set'); - }).getOr(null), - ); - - this.markerHolder.map(marker => this.props.handleID(marker.id)); + this.props.markableHolder.map(markable => { + const marker = this.props.position.markOn(markable, options); + const markableKind = markable.constructor.name.toLowerCase().replace(/^display/, ''); + console.log(`created marker ${marker.id} on ${markableKind} ${markable.id}`); + + this.subs.add( + marker.onDidChange(this.didChange), + marker.onDidChange(() => { + console.log(`marker ${marker.id} was changed to ${marker.getBufferRange().toString()}`); + return null; + }), + ); + this.markerHolder.setter(marker); + this.props.handleID(marker.id); + return null; + }); } updateMarkerPosition() { this.markerHolder.map(marker => { - if (this.props.bufferRange) { - return marker.setBufferRange(this.props.bufferRange); - } - - if (this.props.screenRange) { - return marker.setScreenRange(this.props.screenRange); - } - - if (this.props.bufferPosition) { - return marker.setBufferRange(new Range(this.props.bufferPosition, this.props.bufferPosition)); - } - - if (this.props.screenPosition) { - return marker.setScreenRange(new Range(this.props.screenPosition, this.props.screenPosition)); + const before = fromMarker(marker); + const after = this.props.position; + if (!before.matches(after)) { + console.log( + `updating marker ${marker.id} from ${before.toString()} to ${after.toString()}`, + ); } + this.props.position.setIn(marker); + return null; + }); + } - throw new Error('Expected one of bufferRange, screenRange, bufferPosition, or screenPosition to be set'); + didChange(event) { + this.props.onDidChange({ + ...fromChangeEvent(event), + ...event, }); } } diff --git a/lib/prop-types.js b/lib/prop-types.js index 5ea4e20523..d8fa6c1ca6 100644 --- a/lib/prop-types.js +++ b/lib/prop-types.js @@ -89,6 +89,12 @@ export const RefHolderPropType = PropTypes.shape({ observe: PropTypes.func.isRequired, }); +export const MarkerPositionPropType = PropTypes.shape({ + markOn: PropTypes.func.isRequired, + setIn: PropTypes.func.isRequired, + matches: PropTypes.func.isRequired, +}); + export const EnableableOperationPropType = PropTypes.shape({ isEnabled: PropTypes.func.isRequired, run: PropTypes.func.isRequired, From b8b66937c1ad7a16d0a2de1f2898dab39d4e225d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 09:47:56 -0400 Subject: [PATCH 0049/4053] Update callsites --- lib/models/presented-file-patch.js | 7 ++++--- test/atom/marker.test.js | 13 +++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/models/presented-file-patch.js b/lib/models/presented-file-patch.js index 02a5b2ba09..871190dfc2 100644 --- a/lib/models/presented-file-patch.js +++ b/lib/models/presented-file-patch.js @@ -1,4 +1,5 @@ import {Point} from 'atom'; +import {fromBufferPosition} from './marker-position'; export default class PresentedFilePatch { constructor(filePatch) { @@ -18,7 +19,7 @@ export default class PresentedFilePatch { let bufferLine = 0; this.text = filePatch.getHunks().reduce((str, hunk) => { - this.hunkStartPositions.push(new Point(bufferLine, 0)); + this.hunkStartPositions.push(fromBufferPosition(new Point(bufferLine, 0))); return hunk.getLines().reduce((hunkStr, line) => { hunkStr += line.getText() + '\n'; @@ -34,7 +35,7 @@ export default class PresentedFilePatch { } this.bufferPositions[line.getStatus()].push( - new Point(bufferLine, 0), + fromBufferPosition(new Point(bufferLine, 0)), ); bufferLine++; @@ -94,7 +95,7 @@ export default class PresentedFilePatch { if (bufferRow === undefined) { return null; } - return new Point(bufferRow, 0); + return new fromBufferPosition(new Point(bufferRow, 0)); } getMaxLineNumberWidth() { diff --git a/test/atom/marker.test.js b/test/atom/marker.test.js index b965b0018d..5ca555f438 100644 --- a/test/atom/marker.test.js +++ b/test/atom/marker.test.js @@ -4,6 +4,7 @@ import {mount} from 'enzyme'; import Marker from '../../lib/atom/marker'; import AtomTextEditor from '../../lib/atom/atom-text-editor'; import MarkerLayer from '../../lib/atom/marker-layer'; +import {fromBufferRange, fromScreenRange, fromBufferPosition} from '../../lib/models/marker-position'; describe('Marker', function() { let atomEnv, editor, markerID; @@ -23,7 +24,7 @@ describe('Marker', function() { it('adds its marker on mount with default properties', function() { mount( - , + , ); const marker = editor.getMarker(markerID); @@ -37,7 +38,7 @@ describe('Marker', function() { , ); @@ -67,7 +68,7 @@ describe('Marker', function() { }); it('destroys its marker on unmount', function() { - const wrapper = mount(); + const wrapper = mount(); assert.isDefined(editor.getMarker(markerID)); wrapper.unmount(); @@ -77,7 +78,7 @@ describe('Marker', function() { it('marks an editor from a parent node', function() { const wrapper = mount( - + , ); @@ -91,7 +92,7 @@ describe('Marker', function() { const wrapper = mount( { layerID = id; }}> - + , ); From 44a650133c7c8427478941005f7c0a4f03ef8fa4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 09:52:16 -0400 Subject: [PATCH 0050/4053] In which invalidate="never" solves all of my problems forever --- lib/views/file-patch-view.js | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 8bf69343e7..9ba6dd3bd2 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -11,6 +11,7 @@ import Gutter from '../atom/gutter'; import FilePatchHeaderView from './file-patch-header-view'; import FilePatchMetaView from './file-patch-meta-view'; import HunkHeaderView from './hunk-header-view'; +import {fromBufferPosition} from '../models/marker-position'; const executableText = { 100644: 'non executable', @@ -146,7 +147,7 @@ export default class FilePatchView extends React.Component { onMouseMove={this.didMouseMoveOnLineNumber} /> - + {this.renderExecutableModeChangeMeta()} @@ -305,6 +306,7 @@ export default class FilePatchView extends React.Component { const selectedHunks = this.state.selectedHunks; const isHunkSelectionMode = this.props.selection.getMode() === 'hunk'; const toggleVerb = this.props.stagingStatus === 'unstaged' ? 'Stage' : 'Unstage'; + const hunkStartPositions = this.state.presentedFilePatch.getHunkStartPositions(); return this.props.filePatch.getHunks().map((hunk, index) => { const isSelected = selectedHunks.has(hunk); @@ -314,10 +316,18 @@ export default class FilePatchView extends React.Component { } const toggleSelectionLabel = `${toggleVerb}${buttonSuffix}`; const discardSelectionLabel = `Discard${buttonSuffix}`; - const bufferPosition = this.state.presentedFilePatch.getHunkStartPositions()[index]; + + const onDidChange = event => { + hunkStartPositions[index] = event.newPosition; + }; return ( - + + + ); }); @@ -345,9 +356,20 @@ export default class FilePatchView extends React.Component { } return ( - - {positions.map(position => { - return ; + + {positions.map((position, index) => { + const onDidChange = event => { + positions[index] = event.newPosition; + }; + + return ( + + ); })} {line && } From bde08098d2089769c4206363007a43d17ad57410 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 09:52:26 -0400 Subject: [PATCH 0051/4053] Use MarkerPositions in Decoration tests --- test/atom/decoration.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/atom/decoration.test.js b/test/atom/decoration.test.js index 441c5da836..6be94ab9ff 100644 --- a/test/atom/decoration.test.js +++ b/test/atom/decoration.test.js @@ -6,6 +6,7 @@ import Decoration from '../../lib/atom/decoration'; import AtomTextEditor from '../../lib/atom/atom-text-editor'; import Marker from '../../lib/atom/marker'; import MarkerLayer from '../../lib/atom/marker-layer'; +import {fromBufferRange, fromBufferPosition} from '../../lib/models/marker-position'; describe('Decoration', function() { let atomEnv, editor, marker; @@ -118,7 +119,7 @@ describe('Decoration', function() { it('decorates a parent Marker', function() { const wrapper = mount( - + , @@ -132,7 +133,7 @@ describe('Decoration', function() { mount( - + , From 7a5fe5a1125a549293a346381fac19f85ab628a0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 10:23:01 -0400 Subject: [PATCH 0052/4053] :fire: logging in --- lib/atom/marker.js | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/lib/atom/marker.js b/lib/atom/marker.js index f6ba3819fe..5bf575ba8f 100644 --- a/lib/atom/marker.js +++ b/lib/atom/marker.js @@ -5,7 +5,7 @@ import {CompositeDisposable} from 'event-kit'; import {autobind, extractProps} from '../helpers'; import {RefHolderPropType, MarkerPositionPropType} from '../prop-types'; import RefHolder from '../models/ref-holder'; -import {fromChangeEvent, fromMarker} from '../models/marker-position'; +import {fromChangeEvent} from '../models/marker-position'; import {TextEditorContext} from './atom-text-editor'; import {MarkerLayerContext} from './marker-layer'; @@ -80,7 +80,6 @@ class BareMarker extends React.Component { } componentWillUnmount() { - this.markerHolder.map(marker => console.log(`destroying marker ${marker.id} due to unmount`)); this.markerHolder.map(marker => marker.destroy()); this.subs.dispose(); } @@ -93,23 +92,14 @@ class BareMarker extends React.Component { } createMarker() { - this.markerHolder.map(marker => console.log(`destroying marker ${marker.id} to create a new one`)); this.markerHolder.map(marker => marker.destroy()); const options = extractProps(this.props, markerProps); this.props.markableHolder.map(markable => { const marker = this.props.position.markOn(markable, options); - const markableKind = markable.constructor.name.toLowerCase().replace(/^display/, ''); - console.log(`created marker ${marker.id} on ${markableKind} ${markable.id}`); - - this.subs.add( - marker.onDidChange(this.didChange), - marker.onDidChange(() => { - console.log(`marker ${marker.id} was changed to ${marker.getBufferRange().toString()}`); - return null; - }), - ); + + this.subs.add(marker.onDidChange(this.didChange)); this.markerHolder.setter(marker); this.props.handleID(marker.id); return null; @@ -117,17 +107,7 @@ class BareMarker extends React.Component { } updateMarkerPosition() { - this.markerHolder.map(marker => { - const before = fromMarker(marker); - const after = this.props.position; - if (!before.matches(after)) { - console.log( - `updating marker ${marker.id} from ${before.toString()} to ${after.toString()}`, - ); - } - this.props.position.setIn(marker); - return null; - }); + this.markerHolder.map(marker => this.props.position.setIn(marker)); } didChange(event) { From 751f2d8b9e44f830d6859e76b8be0039e8e89071 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 11:14:30 -0400 Subject: [PATCH 0053/4053] Propagate getRealItemPromise through StubItems and createItems --- lib/items/stub-item.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/items/stub-item.js b/lib/items/stub-item.js index 7876428134..8dd42e1a89 100644 --- a/lib/items/stub-item.js +++ b/lib/items/stub-item.js @@ -51,7 +51,13 @@ export default class StubItem { setRealItem(item) { this.realItem = item; - this.resolveRealItemPromise(); + + if (this.realItem.getRealItemPromise) { + this.realItem.getRealItemPromise().then(this.resolveRealItemPromise); + } else { + this.resolveRealItemPromise(this.realItem); + } + this.emitter.emit('did-change-title'); this.emitter.emit('did-change-icon'); @@ -108,6 +114,7 @@ export default class StubItem { } destroy() { + this.resolveRealItemPromise(null); this.subscriptions.dispose(); this.emitter.dispose(); if (this.realItem) { From 020bff318778f77ee946838dcc45a7a7f4812409 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 11:14:58 -0400 Subject: [PATCH 0054/4053] Default workingDirectoryPath to null --- lib/containers/git-tab-container.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/containers/git-tab-container.js b/lib/containers/git-tab-container.js index b4414813e1..e8ddef3cf7 100644 --- a/lib/containers/git-tab-container.js +++ b/lib/containers/git-tab-container.js @@ -18,7 +18,7 @@ const DEFAULT_REPO_DATA = { unstagedChanges: [], stagedChanges: [], mergeConflicts: [], - workingDirectoryPath: '', + workingDirectoryPath: null, mergeMessage: null, fetchInProgress: true, }; From 157d19837c7fdc04f45f3b230c1f9f217747a48c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 11:15:47 -0400 Subject: [PATCH 0055/4053] Detect activated patch items through stubs and proxies --- lib/items/file-patch-item.js | 6 +++++- lib/views/staging-view.js | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/items/file-patch-item.js b/lib/items/file-patch-item.js index 65e23e70ce..d7d735500a 100644 --- a/lib/items/file-patch-item.js +++ b/lib/items/file-patch-item.js @@ -83,7 +83,7 @@ export default class FilePatchItem extends React.Component { serialize() { return { deserializer: 'FilePatchControllerStub', - uri: this.getURI(), + uri: FilePatchItem.buildURI(this.props.relPath, this.props.workingDirectory, this.props.stagingStatus), }; } @@ -98,4 +98,8 @@ export default class FilePatchItem extends React.Component { getWorkingDirectory() { return this.props.workingDirectory; } + + isFilePatchItem() { + return true; + } } diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 1b563d77d6..f40407c04d 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -530,21 +530,22 @@ export default class StagingView extends React.Component { } } - syncWithWorkspace() { + async syncWithWorkspace() { const item = this.props.workspace.getActivePaneItem(); if (!item) { return; } - const realItem = item.getRealItem && item.getRealItem(); + const realItemPromise = item.getRealItemPromise && item.getRealItemPromise(); + const realItem = await realItemPromise; if (!realItem) { return; } - const isFilePatchController = realItem instanceof FilePatchItem; - const isMatch = realItem.getWorkingDirectory && item.getWorkingDirectory() === this.props.workingDirectoryPath; + const isFilePatchItem = realItem.isFilePatchItem && realItem.isFilePatchItem(); + const isMatch = realItem.getWorkingDirectory && realItem.getWorkingDirectory() === this.props.workingDirectoryPath; - if (isFilePatchController && isMatch) { + if (isFilePatchItem && isMatch) { this.quietlySelectItem(realItem.getFilePath(), realItem.getStagingStatus()); } } From dc11bcf6cdbd65e75468f03f79efa8688e4b6cbc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 11:16:22 -0400 Subject: [PATCH 0056/4053] Sync StagingView with workspace the first time it is populated --- lib/views/staging-view.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index f40407c04d..b3081339a0 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -150,6 +150,10 @@ export default class StagingView extends React.Component { this.syncWithWorkspace(); }), ); + + if (this.isPopulated(this.props)) { + this.syncWithWorkspace(); + } } componentDidUpdate(prevProps, prevState) { @@ -170,6 +174,10 @@ export default class StagingView extends React.Component { element.scrollIntoViewIfNeeded(); } } + + if (!this.isPopulated(prevProps) && this.isPopulated(this.props)) { + this.syncWithWorkspace(); + } } render() { @@ -600,7 +608,7 @@ export default class StagingView extends React.Component { quietlySelectItem(filePath, stagingStatus) { return new Promise(resolve => { this.setState(prevState => { - const item = this.state.selection.findItem((each, key) => each.filePath === filePath && key === stagingStatus); + const item = prevState.selection.findItem((each, key) => each.filePath === filePath && key === stagingStatus); if (!item) { // FIXME: make staging view display no selected item // eslint-disable-next-line no-console @@ -821,4 +829,12 @@ export default class StagingView extends React.Component { hasFocus() { return this.refRoot.contains(document.activeElement); } + + isPopulated(props) { + return props.workingDirectoryPath != null && ( + props.unstagedChanges.length > 0 || + props.mergeConflicts.length > 0 || + props.stagedChanges.length > 0 + ); + } } From 537753a48f379280b0c30f39fa8c4a9509cb4d3e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 27 Jul 2018 13:36:43 -0400 Subject: [PATCH 0057/4053] Center the "no content" message --- lib/containers/file-patch-container.js | 4 ++-- styles/file-patch-view.less | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index 8bb5f11645..a5040a61db 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -61,8 +61,8 @@ export default class FilePatchContainer extends React.Component { renderEmptyPatchMessage() { return ( -
- No changes to display +
+

No changes to display

); } diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 589dae1269..4e4b4ff3e6 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -12,6 +12,11 @@ min-width: 0; height: 100%; + &.is-blank { + text-align: center; + justify-content: center; + } + &-header { display: flex; justify-content: space-between; From 9fc9622b3ae6f8c9c477cb9f6214c42c2764f1a4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 31 Jul 2018 15:26:36 -0400 Subject: [PATCH 0058/4053] MarkerPosition tests --- lib/models/marker-position.js | 124 ++++++++++- test/models/marker-position.test.js | 310 ++++++++++++++++++++++++++++ 2 files changed, 423 insertions(+), 11 deletions(-) create mode 100644 test/models/marker-position.test.js diff --git a/lib/models/marker-position.js b/lib/models/marker-position.js index 7e9989f1ca..dacb9e44e7 100644 --- a/lib/models/marker-position.js +++ b/lib/models/marker-position.js @@ -1,14 +1,22 @@ import {Range} from 'atom'; class Position { + constructor(bufferRange, screenRange) { + this.bufferRange = bufferRange && Range.fromObject(bufferRange); + this.screenRange = screenRange && Range.fromObject(screenRange); + } + + /* istanbul ignore next */ markOn(markable, options) { throw new Error('markOn not overridden'); } + /* istanbul ignore next */ setIn(marker) { throw new Error('setIn not overridden'); } + /* istanbul ignore next */ matches(other) { throw new Error('matches not overridden'); } @@ -20,12 +28,57 @@ class Position { matchFromScreenRange(other) { return false; } + + bufferStartRow() { + return this.bufferRange !== null ? this.bufferRange.start.row : -1; + } + + bufferRowCount() { + return this.bufferRange !== null ? this.bufferRange.getRowCount() : 0; + } + + intersectRows(rowSet) { + if (this.bufferRange === null) { + return []; + } + + const intersections = []; + let currentRangeStart = null; + + let row = this.bufferRange.start.row; + while (row <= this.bufferRange.end.row) { + if (rowSet.has(row) && currentRangeStart === null) { + currentRangeStart = row; + } else if (!rowSet.has(row) && currentRangeStart !== null) { + intersections.push( + fromBufferRange([[currentRangeStart, 0], [row - 1, 0]]), + ); + currentRangeStart = null; + } + row++; + } + if (currentRangeStart !== null) { + intersections.push( + fromBufferRange([[currentRangeStart, 0], this.bufferRange.end]), + ); + } + + return intersections; + } + + /* istanbul ignore next */ + serialize() { + throw new Error('serialize not overridden'); + } + + isPresent() { + return true; + } } class BufferRangePosition extends Position { constructor(bufferRange) { - super(); - this.bufferRange = Range.fromObject(bufferRange); + super(bufferRange, null); } markOn(markable, options) { @@ -44,6 +97,10 @@ class BufferRangePosition extends Position { return other.bufferRange.isEqual(this.bufferRange); } + serialize() { + return this.bufferRange.serialize(); + } + toString() { return `buffer(${this.bufferRange.toString()})`; } @@ -51,8 +108,7 @@ class BufferRangePosition extends Position { class ScreenRangePosition extends Position { constructor(screenRange) { - super(); - this.screenRange = screenRange; + super(null, screenRange); } markOn(markable, options) { @@ -71,20 +127,18 @@ class ScreenRangePosition extends Position { return other.screenRange.isEqual(this.screenRange); } + serialize() { + return this.screenRange.serialize(); + } + toString() { return `screen(${this.screenRange.toString()})`; } } class BufferOrScreenRangePosition extends Position { - constructor(bufferRange, screenRange) { - super(); - this.bufferRange = bufferRange; - this.screenRange = screenRange; - } - markOn(markable, options) { - return markable.markBufferRange(this.bufferRange); + return markable.markBufferRange(this.bufferRange, options); } setIn(marker) { @@ -103,11 +157,59 @@ class BufferOrScreenRangePosition extends Position { return other.screenRange.isEqual(this.screenRange); } + serialize() { + return this.bufferRange.serialize(); + } + toString() { return `either(b${this.bufferRange.toString()}/s${this.screenRange.toString()})`; } } +export const nullPosition = { + markOn() { + return null; + }, + + setIn() {}, + + matches(other) { + return other === this; + }, + + matchFromBufferRange() { + return false; + }, + + matchFromScreenRange() { + return false; + }, + + bufferStartRow() { + return -1; + }, + + bufferRowCount() { + return 0; + }, + + intersectRows() { + return []; + }, + + serialize() { + return null; + }, + + isPresent() { + return false; + }, + + toString() { + return 'null'; + }, +}; + export function fromBufferRange(bufferRange) { return new BufferRangePosition(Range.fromObject(bufferRange)); } diff --git a/test/models/marker-position.test.js b/test/models/marker-position.test.js new file mode 100644 index 0000000000..3f5b069eec --- /dev/null +++ b/test/models/marker-position.test.js @@ -0,0 +1,310 @@ +import { + nullPosition, fromBufferRange, fromBufferPosition, fromScreenRange, fromScreenPosition, fromMarker, fromChangeEvent, +} from '../../lib/models/marker-position'; + +describe('MarkerPosition', function() { + let atomEnv, editor; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + + editor = await atomEnv.workspace.open(__filename); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + describe('markOn', function() { + it('marks a buffer range', function() { + const position = fromBufferRange([[1, 0], [4, 0]]); + const marker = position.markOn(editor, {invalidate: 'never'}); + + assert.deepEqual(marker.getBufferRange().serialize(), [[1, 0], [4, 0]]); + assert.strictEqual(marker.getInvalidationStrategy(), 'never'); + }); + + it('marks a buffer position', function() { + const position = fromBufferPosition([2, 0]); + const marker = position.markOn(editor, {invalidate: 'never'}); + + assert.deepEqual(marker.getBufferRange().serialize(), [[2, 0], [2, 0]]); + assert.strictEqual(marker.getInvalidationStrategy(), 'never'); + }); + + it('marks a screen range', function() { + const position = fromScreenRange([[2, 0], [5, 0]]); + const marker = position.markOn(editor, {invalidate: 'never'}); + + assert.deepEqual(marker.getBufferRange().serialize(), [[2, 0], [5, 0]]); + assert.strictEqual(marker.getInvalidationStrategy(), 'never'); + }); + + it('marks a screen position', function() { + const position = fromScreenPosition([3, 0]); + const marker = position.markOn(editor, {invalidate: 'never'}); + + assert.deepEqual(marker.getBufferRange().serialize(), [[3, 0], [3, 0]]); + assert.strictEqual(marker.getInvalidationStrategy(), 'never'); + }); + + it('marks a combination position', function() { + const marker0 = editor.markBufferRange([[0, 0], [2, 0]]); + const position = fromMarker(marker0); + + const marker1 = position.markOn(editor, {invalidate: 'never'}); + assert.deepEqual(marker1.getBufferRange().serialize(), [[0, 0], [2, 0]]); + assert.strictEqual(marker1.getInvalidationStrategy(), 'never'); + }); + + it('does nothing with a nullPosition', function() { + assert.isNull(nullPosition.markOn(editor, {})); + assert.lengthOf(editor.findMarkers({}), 0); + }); + }); + + describe('setIn', function() { + let marker; + + beforeEach(function() { + marker = editor.markBufferRange([[1, 0], [3, 0]]); + }); + + it('updates an existing marker by buffer range', function() { + fromBufferRange([[2, 0], [4, 0]]).setIn(marker); + assert.deepEqual(marker.getBufferRange().serialize(), [[2, 0], [4, 0]]); + }); + + it('updates an existing marker by screen range', function() { + fromScreenRange([[6, 0], [7, 0]]).setIn(marker); + assert.deepEqual(marker.getBufferRange().serialize(), [[6, 0], [7, 0]]); + }); + + it('updates with a combination position', function() { + const other = editor.markBufferRange([[2, 0], [4, 0]]); + fromMarker(other).setIn(marker); + assert.deepEqual(marker.getBufferRange().serialize(), [[2, 0], [4, 0]]); + }); + + it('does nothing with a nullPosition', function() { + nullPosition.setIn(marker); + assert.deepEqual(marker.getBufferRange().serialize(), [[1, 0], [3, 0]]); + }); + }); + + describe('matches', function() { + let marker0, marker1; + + beforeEach(function() { + marker0 = editor.markBufferRange([[2, 0], [4, 0]]); + marker1 = editor.markBufferRange([[1, 0], [3, 0]]); + }); + + it('a buffer range', function() { + const position = fromBufferRange([[2, 0], [4, 0]]); + assert.isTrue(position.matches(fromBufferRange([[2, 0], [4, 0]]))); + assert.isFalse(position.matches(fromBufferRange([[2, 0], [5, 0]]))); + assert.isFalse(position.matches(fromScreenRange([[2, 0], [4, 0]]))); + assert.isTrue(position.matches(fromMarker(marker0))); + assert.isFalse(position.matches(fromMarker(marker1))); + assert.isFalse(position.matches(nullPosition)); + }); + + it('a screen range', function() { + const position = fromScreenRange([[1, 0], [3, 0]]); + assert.isTrue(position.matches(fromScreenRange([[1, 0], [3, 0]]))); + assert.isFalse(position.matches(fromScreenRange([[2, 0], [4, 0]]))); + assert.isFalse(position.matches(fromBufferRange([[1, 0], [3, 0]]))); + assert.isFalse(position.matches(fromMarker(marker0))); + assert.isTrue(position.matches(fromMarker(marker1))); + assert.isFalse(position.matches(nullPosition)); + }); + + it('a combination range', function() { + const position = fromMarker(marker0); + assert.isTrue(position.matches(fromMarker(marker0))); + assert.isFalse(position.matches(fromMarker(marker1))); + assert.isTrue(position.matches(fromBufferRange([[2, 0], [4, 0]]))); + assert.isFalse(position.matches(fromBufferRange([[1, 0], [3, 0]]))); + assert.isTrue(position.matches(fromScreenRange([[2, 0], [4, 0]]))); + assert.isFalse(position.matches(fromScreenRange([[1, 0], [3, 0]]))); + assert.isFalse(position.matches(nullPosition)); + }); + + it('a null position', function() { + assert.isTrue(nullPosition.matches(nullPosition)); + assert.isFalse(nullPosition.matches(fromBufferRange([[1, 0], [2, 0]]))); + assert.isFalse(nullPosition.matches(fromScreenRange([[1, 0], [2, 0]]))); + assert.isFalse(nullPosition.matches(fromMarker(marker0))); + }); + }); + + describe('bufferStartRow()', function() { + it('retrieves the first row from a buffer range', function() { + assert.strictEqual(fromBufferRange([[2, 0], [3, 4]]).bufferStartRow(), 2); + }); + + it('retrieves the first row from a combination range', function() { + const marker = editor.markBufferRange([[3, 0], [5, 0]]); + assert.strictEqual(fromMarker(marker).bufferStartRow(), 3); + }); + + it('returns -1 from a screen range', function() { + assert.strictEqual(fromScreenRange([[1, 0], [2, 0]]).bufferStartRow(), -1); + }); + + it('returns -1 from a null position', function() { + assert.strictEqual(nullPosition.bufferStartRow(), -1); + }); + }); + + describe('bufferRowCount()', function() { + it('counts the rows in a buffer range', function() { + assert.strictEqual(fromBufferRange([[1, 0], [4, 0]]).bufferRowCount(), 4); + assert.strictEqual(fromBufferPosition([2, 0]).bufferRowCount(), 1); + }); + + it('counts the rows in a combination range', function() { + const marker = editor.markBufferRange([[2, 0], [6, 0]]); + assert.strictEqual(fromMarker(marker).bufferRowCount(), 5); + }); + + it('returns 0 for screen or null ranges', function() { + assert.strictEqual(fromScreenRange([[1, 0], [2, 0]]).bufferRowCount(), 0); + assert.strictEqual(nullPosition.bufferRowCount(), 0); + }); + }); + + describe('intersectRows()', function() { + it('returns an empty array with no intersection rows', function() { + assert.deepEqual(fromBufferRange([[1, 0], [3, 0]]).intersectRows(new Set([0, 5, 6])), []); + }); + + it('detects an intersection at the beginning of the range', function() { + const position = fromBufferRange([[2, 0], [6, 0]]); + const rowSet = new Set([0, 1, 2, 3]); + + assert.deepEqual(position.intersectRows(rowSet).map(i => i.serialize()), [ + [[2, 0], [3, 0]], + ]); + }); + + it('detects an intersection in the middle of the range', function() { + const position = fromBufferRange([[2, 0], [6, 0]]); + const rowSet = new Set([0, 3, 4, 8, 9]); + + assert.deepEqual(position.intersectRows(rowSet).map(i => i.serialize()), [ + [[3, 0], [4, 0]], + ]); + }); + + it('detects an intersection at the end of the range', function() { + const position = fromBufferRange([[2, 0], [6, 0]]); + const rowSet = new Set([4, 5, 6, 7, 10, 11]); + + assert.deepEqual(position.intersectRows(rowSet).map(i => i.serialize()), [ + [[4, 0], [6, 0]], + ]); + }); + + it('detects multiple intersections', function() { + const position = fromBufferRange([[2, 0], [8, 0]]); + const rowSet = new Set([0, 3, 4, 6, 7, 10]); + + assert.deepEqual(position.intersectRows(rowSet).map(i => i.serialize()), [ + [[3, 0], [4, 0]], + [[6, 0], [7, 0]], + ]); + }); + + it('returns an empty array for screen or null ranges', function() { + assert.deepEqual(fromScreenRange([[1, 0], [4, 0]]).intersectRows(new Set()), []); + assert.deepEqual(nullPosition.intersectRows(new Set()), []); + }); + }); + + describe('isPresent()', function() { + it('returns true on non-null positions', function() { + assert.isTrue(fromBufferRange([[1, 0], [2, 0]]).isPresent()); + assert.isTrue(fromScreenRange([[1, 0], [2, 0]]).isPresent()); + + const marker = editor.markBufferRange([[1, 0], [2, 0]]); + assert.isTrue(fromMarker(marker).isPresent()); + }); + + it('returns false on null positions', function() { + assert.isFalse(nullPosition.isPresent()); + }); + }); + + describe('serialize()', function() { + it('produces an array', function() { + assert.deepEqual(fromBufferRange([[0, 0], [1, 1]]).serialize(), [[0, 0], [1, 1]]); + assert.deepEqual(fromScreenRange([[0, 0], [1, 1]]).serialize(), [[0, 0], [1, 1]]); + + const marker = editor.markBufferRange([[2, 2], [3, 0]]); + assert.deepEqual(fromMarker(marker).serialize(), [[2, 2], [3, 0]]); + }); + + it('serializes a null position as null', function() { + assert.isNull(nullPosition.serialize()); + }); + }); + + describe('toString()', function() { + it('pretty-prints buffer ranges', function() { + assert.strictEqual(fromBufferRange([[0, 0], [2, 0]]).toString(), 'buffer([(0, 0) - (2, 0)])'); + }); + + it('pretty-prints screen ranges', function() { + assert.strictEqual(fromScreenRange([[3, 0], [7, 0]]).toString(), 'screen([(3, 0) - (7, 0)])'); + }); + + it('pretty-prints combination ranges', function() { + const marker = editor.markBufferRange([[1, 0], [3, 0]]); + assert.strictEqual(fromMarker(marker).toString(), 'either(b[(1, 0) - (3, 0)]/s[(1, 0) - (3, 0)])'); + }); + + it('pretty-prints a null position', function() { + assert.strictEqual(nullPosition.toString(), 'null'); + }); + }); + + describe('fromChangeEvent()', function() { + it('produces positions from a non-reversed marker change', function() { + const {oldPosition, newPosition} = fromChangeEvent({ + oldTailBufferPosition: [0, 0], + oldHeadBufferPosition: [1, 1], + oldTailScreenPosition: [2, 2], + oldHeadScreenPosition: [3, 3], + newTailBufferPosition: [4, 4], + newHeadBufferPosition: [5, 5], + newTailScreenPosition: [6, 6], + newHeadScreenPosition: [7, 7], + }); + + assert.isTrue(oldPosition.matches(fromBufferRange([[0, 0], [1, 1]]))); + assert.isTrue(oldPosition.matches(fromScreenRange([[2, 2], [3, 3]]))); + assert.isTrue(newPosition.matches(fromBufferRange([[4, 4], [5, 5]]))); + assert.isTrue(newPosition.matches(fromScreenRange([[6, 6], [7, 7]]))); + }); + + it('produces positions from a reversed marker change', function() { + const {oldPosition, newPosition} = fromChangeEvent({ + oldTailBufferPosition: [0, 0], + oldHeadBufferPosition: [1, 1], + oldTailScreenPosition: [2, 2], + oldHeadScreenPosition: [3, 3], + newTailBufferPosition: [4, 4], + newHeadBufferPosition: [5, 5], + newTailScreenPosition: [6, 6], + newHeadScreenPosition: [7, 7], + }, true); + + assert.isTrue(oldPosition.matches(fromBufferRange([[1, 1], [0, 0]]))); + assert.isTrue(oldPosition.matches(fromScreenRange([[3, 3], [2, 2]]))); + assert.isTrue(newPosition.matches(fromBufferRange([[5, 5], [4, 4]]))); + assert.isTrue(newPosition.matches(fromScreenRange([[7, 7], [6, 6]]))); + }); + }); +}); From 6e8ae370323c998020548ccc5597b81b0f90eedb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 1 Aug 2018 09:10:25 -0400 Subject: [PATCH 0059/4053] Model a MarkerPosition plus an offset range as a Change --- lib/models/patch/change.js | 77 ++++++++++++++++++++++++++++++++ test/models/patch/change.test.js | 66 +++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 lib/models/patch/change.js create mode 100644 test/models/patch/change.test.js diff --git a/lib/models/patch/change.js b/lib/models/patch/change.js new file mode 100644 index 0000000000..f1eb504e39 --- /dev/null +++ b/lib/models/patch/change.js @@ -0,0 +1,77 @@ +// A contiguous region of additions or deletions within a {Hunk}. +// +// The Change's position is a {MarkerPosition} containing a {Range} of the change rows within the diff buffer. The +// calculated offsets delimit a half-open interval of {String} code point offsets within the diff buffer, such that +// `buffer.slice(startOffset, endOffset)` returns the exact contents of the changed lines, including the last line +// and its line-end character. +export default class Change { + constructor({position, startOffset, endOffset}) { + this.position = position; + this.startOffset = startOffset; + this.endOffset = endOffset; + } + + markOn(markable, options) { + return this.position.markOn(markable, options); + } + + setIn(marker) { + return this.position.setIn(marker); + } + + toStringIn(buffer, origin) { + let str = ''; + for (let offset = this.startOffset; offset < this.endOffset; offset++) { + const ch = buffer[offset]; + if (offset === this.startOffset) { + str += origin; + } + str += ch; + if (ch === '\n' && offset !== this.endOffset - 1) { + str += origin; + } + } + return str; + } + + intersectRowsIn(rowSet, buffer) { + const intPositions = this.position.intersectRows(rowSet); + const intChanges = []; + let intIndex = 0; + let currentRow = this.position.bufferStartRow(); + let currentOffset = this.startOffset; + let nextStartOffset = null; + + while (intIndex < intPositions.length && currentOffset < this.endOffset) { + const currentInt = intPositions[intIndex]; + + if (currentRow === currentInt.bufferStartRow()) { + nextStartOffset = currentOffset; + } + + if (currentRow === currentInt.bufferEndRow() + 1) { + intChanges.push(new this.constructor({ + position: currentInt, + startOffset: nextStartOffset, + endOffset: currentOffset, + })); + + intIndex++; + nextStartOffset = null; + } + + currentOffset = buffer.indexOf('\n', currentOffset) + 1; + currentRow++; + } + + if (intIndex < intPositions.length && nextStartOffset !== null) { + intChanges.push(new this.constructor({ + position: intPositions[intIndex], + startOffset: nextStartOffset, + endOffset: currentOffset, + })); + } + + return intChanges; + } +} diff --git a/test/models/patch/change.test.js b/test/models/patch/change.test.js new file mode 100644 index 0000000000..29dde58e80 --- /dev/null +++ b/test/models/patch/change.test.js @@ -0,0 +1,66 @@ +import Change from '../../../lib/models/patch/change'; +import {fromBufferRange} from '../../../lib/models/marker-position'; + +describe('Change', function() { + it('delegates methods to its MarkerPosition', function() { + const ch = new Change({ + position: fromBufferRange([[0, 0], [1, 0]]), + startOffset: 0, + endOffset: 10, + }); + + const markable = {markBufferRange: sinon.stub().returns(0)}; + assert.strictEqual(ch.markOn(markable, {}), 0); + assert.deepEqual(markable.markBufferRange.firstCall.args[0].serialize(), [[0, 0], [1, 0]]); + + const marker = {setBufferRange: sinon.stub().returns(1)}; + assert.strictEqual(ch.setIn(marker), 1); + assert.deepEqual(marker.setBufferRange.firstCall.args[0].serialize(), [[0, 0], [1, 0]]); + }); + + it('extracts its offset range from buffer text with toStringIn()', function() { + const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n'; + const ch = new Change({ + position: fromBufferRange([[1, 0], [2, 0]]), + startOffset: 5, + endOffset: 25, + }); + + assert.strictEqual(ch.toStringIn(buffer, '+'), '+1111\n+2222\n+3333\n+4444\n'); + assert.strictEqual(ch.toStringIn(buffer, '-'), '-1111\n-2222\n-3333\n-4444\n'); + }); + + it('returns Changes corresponding to intersecting buffer rows', function() { + const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999'; + const ch = new Change({ + position: fromBufferRange([[1, 0], [8, 0]]), + startOffset: 5, + endOffset: 45, + }); + + const intersections = ch.intersectRowsIn(new Set([4, 5]), buffer); + assert.lengthOf(intersections, 1); + assert.strictEqual(intersections[0].toStringIn(buffer, '-'), '-4444\n-5555\n'); + }); + + it('includes a Change corresponding to an intersection at the end of the range', function() { + const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999'; + const ch = new Change({ + position: fromBufferRange([[1, 0], [8, 0]]), + startOffset: 5, + endOffset: 45, + }); + + const intersections = ch.intersectRowsIn(new Set([1, 2, 4, 8]), buffer); + assert.lengthOf(intersections, 3); + + const int0 = intersections[0]; + assert.strictEqual(int0.toStringIn(buffer, '+'), '+1111\n+2222\n'); + + const int1 = intersections[1]; + assert.strictEqual(int1.toStringIn(buffer, '-'), '-4444\n'); + + const int2 = intersections[2]; + assert.strictEqual(int2.toStringIn(buffer, '+'), '+8888\n'); + }); +}); From 7413d6fbabc5239b21d3d467c1f7ecbbf4fb64bd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 1 Aug 2018 09:27:17 -0400 Subject: [PATCH 0060/4053] MarkerPosition needs a bufferEndRow() accessor --- lib/models/marker-position.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/models/marker-position.js b/lib/models/marker-position.js index dacb9e44e7..f60d9987ab 100644 --- a/lib/models/marker-position.js +++ b/lib/models/marker-position.js @@ -33,6 +33,10 @@ class Position { return this.bufferRange !== null ? this.bufferRange.start.row : -1; } + bufferEndRow() { + return this.bufferRange !== null ? this.bufferRange.end.row : -1; + } + bufferRowCount() { return this.bufferRange !== null ? this.bufferRange.getRowCount() : 0; } From 6778afff7d9ff9da33774a666e8fc011c5d00978 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 1 Aug 2018 09:27:36 -0400 Subject: [PATCH 0061/4053] Delegate Change.bufferRowCount() --- lib/models/patch/change.js | 4 ++++ test/models/patch/change.test.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/lib/models/patch/change.js b/lib/models/patch/change.js index f1eb504e39..2f389bb362 100644 --- a/lib/models/patch/change.js +++ b/lib/models/patch/change.js @@ -19,6 +19,10 @@ export default class Change { return this.position.setIn(marker); } + bufferRowCount() { + return this.position.bufferRowCount(); + } + toStringIn(buffer, origin) { let str = ''; for (let offset = this.startOffset; offset < this.endOffset; offset++) { diff --git a/test/models/patch/change.test.js b/test/models/patch/change.test.js index 29dde58e80..8a34706f52 100644 --- a/test/models/patch/change.test.js +++ b/test/models/patch/change.test.js @@ -16,6 +16,8 @@ describe('Change', function() { const marker = {setBufferRange: sinon.stub().returns(1)}; assert.strictEqual(ch.setIn(marker), 1); assert.deepEqual(marker.setBufferRange.firstCall.args[0].serialize(), [[0, 0], [1, 0]]); + + assert.deepEqual(ch.bufferRowCount(), 2); }); it('extracts its offset range from buffer text with toStringIn()', function() { From a5bfa0bfe15acca84a8500059083082ee782d38e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 1 Aug 2018 09:27:57 -0400 Subject: [PATCH 0062/4053] nullChange --- lib/models/patch/change.js | 24 ++++++++++++++++++++++++ test/models/patch/change.test.js | 12 ++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/change.js b/lib/models/patch/change.js index 2f389bb362..f0ee9f8723 100644 --- a/lib/models/patch/change.js +++ b/lib/models/patch/change.js @@ -1,3 +1,5 @@ +import {nullPosition} from '../marker-position'; + // A contiguous region of additions or deletions within a {Hunk}. // // The Change's position is a {MarkerPosition} containing a {Range} of the change rows within the diff buffer. The @@ -79,3 +81,25 @@ export default class Change { return intChanges; } } + +export const nullChange = { + markOn(...args) { + return nullPosition.markOn(...args); + }, + + setIn(...args) { + return nullPosition.setIn(...args); + }, + + bufferRowCount() { + return nullPosition.bufferRowCount(); + }, + + toStringIn() { + return ''; + }, + + intersectRowsIn() { + return []; + }, +}; diff --git a/test/models/patch/change.test.js b/test/models/patch/change.test.js index 8a34706f52..976f9d5202 100644 --- a/test/models/patch/change.test.js +++ b/test/models/patch/change.test.js @@ -1,5 +1,5 @@ -import Change from '../../../lib/models/patch/change'; -import {fromBufferRange} from '../../../lib/models/marker-position'; +import Change, {nullChange} from '../../../lib/models/patch/change'; +import {fromBufferRange, nullPosition} from '../../../lib/models/marker-position'; describe('Change', function() { it('delegates methods to its MarkerPosition', function() { @@ -65,4 +65,12 @@ describe('Change', function() { const int2 = intersections[2]; assert.strictEqual(int2.toStringIn(buffer, '+'), '+8888\n'); }); + + it('returns appropriate values from nullChange methods', function() { + assert.deepEqual(nullChange.intersectRowsIn(new Set([0, 1, 2]), ''), []); + assert.strictEqual(nullChange.toStringIn('', '+'), ''); + assert.strictEqual(nullChange.markOn(), nullPosition.markOn()); + assert.strictEqual(nullChange.setIn(), nullPosition.setIn()); + assert.strictEqual(nullChange.bufferRowCount(), 0); + }); }); From aee4a12663eb3f5a58e2302fedad97233d1ac05e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 1 Aug 2018 10:11:27 -0400 Subject: [PATCH 0063/4053] isPresent() methods on Changes --- lib/models/patch/change.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/models/patch/change.js b/lib/models/patch/change.js index f0ee9f8723..4cea4461d8 100644 --- a/lib/models/patch/change.js +++ b/lib/models/patch/change.js @@ -80,6 +80,10 @@ export default class Change { return intChanges; } + + isPresent() { + return true; + } } export const nullChange = { @@ -102,4 +106,8 @@ export const nullChange = { intersectRowsIn() { return []; }, + + isPresent() { + return false; + }, }; From 8756786a9272a6131445630265b5592c92d1bbd6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 1 Aug 2018 14:16:32 -0400 Subject: [PATCH 0064/4053] Diff builder --- lib/models/patch/builder.js | 172 +++++++++++ test/models/patch/builder.test.js | 480 ++++++++++++++++++++++++++++++ 2 files changed, 652 insertions(+) create mode 100644 lib/models/patch/builder.js create mode 100644 test/models/patch/builder.test.js diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js new file mode 100644 index 0000000000..a0808a1971 --- /dev/null +++ b/lib/models/patch/builder.js @@ -0,0 +1,172 @@ +import Hunk from './hunk'; +import File, {nullFile} from './file'; +import Patch, {nullPatch} from './patch'; +import Change, {nullChange} from './change'; +import FilePatch from './file-patch'; +import {fromBufferRange, fromBufferPosition} from '../marker-position'; + +export default function buildFilePatch(diffs) { + if (diffs.length === 0) { + return emptyDiffFilePatch(); + } else if (diffs.length === 1) { + return singleDiffFilePatch(diffs[0]); + } else if (diffs.length === 2) { + return dualDiffFilePatch(...diffs); + } else { + throw new Error(`Unexpected number of diffs: ${diffs.length}`); + } +} + +function emptyDiffFilePatch() { + return new FilePatch(nullFile, nullFile, nullPatch); +} + +function singleDiffFilePatch(diff) { + const wasSymlink = diff.oldMode === '120000'; + const isSymlink = diff.newMode === '120000'; + const [hunks, bufferText] = buildHunks(diff); + + let oldSymlink = null; + let newSymlink = null; + if (wasSymlink && !isSymlink) { + oldSymlink = diff.hunks[0].lines[0].slice(1); + } else if (!wasSymlink && isSymlink) { + newSymlink = diff.hunks[0].lines[0].slice(1); + } else if (wasSymlink && isSymlink) { + oldSymlink = diff.hunks[0].lines[0].slice(1); + newSymlink = diff.hunks[0].lines[2].slice(1); + } + + const oldFile = new File({path: diff.oldPath, mode: diff.oldMode, symlink: oldSymlink}); + const newFile = new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink}); + const patch = new Patch({status: diff.status, hunks, bufferText}); + + return new FilePatch(oldFile, newFile, patch); +} + +function dualDiffFilePatch(diff1, diff2) { + let modeChangeDiff, contentChangeDiff; + if (diff1.oldMode === '120000' || diff1.newMode === '120000') { + modeChangeDiff = diff1; + contentChangeDiff = diff2; + } else { + modeChangeDiff = diff2; + contentChangeDiff = diff1; + } + + const [hunks, bufferText] = buildHunks(contentChangeDiff); + const filePath = contentChangeDiff.oldPath || contentChangeDiff.newPath; + const symlink = modeChangeDiff.hunks[0].lines[0].slice(1); + + let status; + let oldMode, newMode; + let oldSymlink = null; + let newSymlink = null; + if (modeChangeDiff.status === 'added') { + // contents were deleted and replaced with symlink + status = 'deleted'; + oldMode = contentChangeDiff.oldMode; + newMode = modeChangeDiff.newMode; + newSymlink = symlink; + } else if (modeChangeDiff.status === 'deleted') { + // contents were added after symlink was deleted + status = 'added'; + oldMode = modeChangeDiff.oldMode; + oldSymlink = symlink; + newMode = contentChangeDiff.newMode; + } else { + throw new Error(`Invalid mode change diff status: ${modeChangeDiff.status}`); + } + + const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink}); + const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink}); + const patch = new Patch({status, hunks, bufferText}); + + return new FilePatch(oldFile, newFile, patch); +} + +const STATUS = { + '+': 'added', + '-': 'deleted', + ' ': 'unchanged', + '\\': 'nonewline', +}; + +function buildHunks(diff) { + let bufferText = ''; + const hunks = []; + + let bufferRow = 0; + let bufferOffset = 0; + let startOffset = 0; + + for (const hunkData of diff.hunks) { + const bufferStartRow = bufferRow; + const bufferStartOffset = bufferOffset; + const additions = []; + const deletions = []; + const noNewlines = []; + + let lastStatus = null; + let currentRangeStart = bufferRow; + + const finishCurrentChange = () => { + const changes = { + added: additions, + deleted: deletions, + nonewline: noNewlines, + }[lastStatus]; + if (changes !== undefined) { + changes.push(new Change({ + position: fromBufferRange([[currentRangeStart, 0], [bufferRow - 1, 0]]), + startOffset, + endOffset: bufferOffset, + })); + } + startOffset = bufferOffset; + currentRangeStart = bufferRow; + }; + + for (const lineText of hunkData.lines) { + const bufferLine = lineText.slice(1) + '\n'; + bufferText += bufferLine; + + const status = STATUS[lineText[0]]; + if (status === undefined) { + throw new Error(`Unknown diff status character: "${lineText[0]}"`); + } + + if (status !== lastStatus && lastStatus !== null) { + finishCurrentChange(); + } + + lastStatus = status; + bufferOffset += bufferLine.length; + bufferRow++; + } + finishCurrentChange(); + + let noNewline = nullChange; + if (noNewlines.length === 1) { + noNewline = noNewlines[0]; + } else if (noNewlines.length > 1) { + throw new Error('Multiple nonewline lines encountered in diff'); + } + + hunks.push(new Hunk({ + oldStartRow: hunkData.oldStartLine, + newStartRow: hunkData.newStartLine, + oldRowCount: hunkData.oldLineCount, + newRowCount: hunkData.newLineCount, + sectionHeading: hunkData.heading, + bufferStartPosition: fromBufferPosition([bufferStartRow, 0]), + bufferStartOffset, + bufferEndRow: bufferRow, + additions, + deletions, + noNewline, + })); + } + + return [hunks, bufferText]; +} diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js new file mode 100644 index 0000000000..0c8628b71c --- /dev/null +++ b/test/models/patch/builder.test.js @@ -0,0 +1,480 @@ +import {buildFilePatch} from '../../../lib/models/patch'; + +describe('buildFilePatch', function() { + let buffer; + + function assertHunkChanges(changes, expectedStrings, expectedRanges) { + const actualStrings = changes.map(change => change.toStringIn(buffer, '*')); + const actualRanges = changes.map(change => change.position.serialize()); + + assert.deepEqual( + {strings: actualStrings, ranges: actualRanges}, + {strings: expectedStrings, ranges: expectedRanges}, + ); + } + + function assertHunk(hunk, {startPosition, startOffset, header, deletions, additions, noNewline}) { + assert.deepEqual(hunk.getBufferStartPosition().serialize(), startPosition); + assert.strictEqual(hunk.getBufferStartOffset(), startOffset); + assert.strictEqual(hunk.getHeader(), header); + + assertHunkChanges(hunk.getDeletions(), deletions.strings, deletions.ranges); + assertHunkChanges(hunk.getAdditions(), additions.strings, additions.ranges); + + const noNewlineChange = hunk.getNoNewline(); + if (noNewlineChange.isPresent()) { + assertHunkChanges([noNewlineChange], [noNewline.string], [noNewline.range]); + } else { + assert.isUndefined(noNewline); + } + } + + it('returns a null patch for an empty diff list', function() { + const p = buildFilePatch([]); + assert.isFalse(p.getOldFile().isPresent()); + assert.isFalse(p.getNewFile().isPresent()); + assert.isFalse(p.getPatch().isPresent()); + }); + + describe('with a single diff', function() { + it('assembles a patch from non-symlink sides', function() { + const p = buildFilePatch([{ + oldPath: 'old/path', + oldMode: '100644', + newPath: 'new/path', + newMode: '100755', + status: 'modified', + hunks: [ + { + oldStartLine: 0, + newStartLine: 0, + oldLineCount: 7, + newLineCount: 6, + lines: [ + ' line-0', + '-line-1', + '-line-2', + '-line-3', + ' line-4', + '+line-5', + '+line-6', + ' line-7', + ' line-8', + ], + }, + { + oldStartLine: 10, + newStartLine: 11, + oldLineCount: 3, + newLineCount: 3, + lines: [ + '-line-9', + ' line-10', + ' line-11', + '+line-12', + ], + }, + { + oldStartLine: 20, + newStartLine: 21, + oldLineCount: 4, + newLineCount: 4, + lines: [ + ' line-13', + '-line-14', + '-line-15', + '+line-16', + '+line-17', + ' line-18', + ], + }, + ], + }]); + + assert.strictEqual(p.getOldPath(), 'old/path'); + assert.strictEqual(p.getOldMode(), '100644'); + assert.strictEqual(p.getNewPath(), 'new/path'); + assert.strictEqual(p.getNewMode(), '100755'); + assert.strictEqual(p.getPatch().getStatus(), 'modified'); + + buffer = + 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\nline-6\nline-7\nline-8\nline-9\nline-10\n' + + 'line-11\nline-12\nline-13\nline-14\nline-15\nline-16\nline-17\nline-18\n'; + assert.strictEqual(p.getBufferText(), buffer); + + assert.lengthOf(p.getHunks(), 3); + assertHunk(p.getHunks()[0], { + startPosition: [[0, 0], [0, 0]], + startOffset: 0, + header: '@@ -0,7 +0,6 @@\n', + deletions: { + strings: ['*line-1\n*line-2\n*line-3\n'], + ranges: [[[1, 0], [3, 0]]], + }, + additions: { + strings: ['*line-5\n*line-6\n'], + ranges: [[[5, 0], [6, 0]]], + }, + }); + + assertHunk(p.getHunks()[1], { + startPosition: [[9, 0], [9, 0]], + startOffset: 63, + header: '@@ -10,3 +11,3 @@\n', + deletions: { + strings: ['*line-9\n'], + ranges: [[[9, 0], [9, 0]]], + }, + additions: { + strings: ['*line-12\n'], + ranges: [[[12, 0], [12, 0]]], + }, + }); + + assertHunk(p.getHunks()[2], { + startPosition: [[13, 0], [13, 0]], + startOffset: 94, + header: '@@ -20,4 +21,4 @@\n', + deletions: { + strings: ['*line-14\n*line-15\n'], + ranges: [[[14, 0], [15, 0]]], + }, + additions: { + strings: ['*line-16\n*line-17\n'], + ranges: [[[16, 0], [17, 0]]], + }, + }); + }); + + it("sets the old file's symlink destination", function() { + const p = buildFilePatch([{ + oldPath: 'old/path', + oldMode: '120000', + newPath: 'new/path', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 0, + newStartLine: 0, + oldLineCount: 0, + newLineCount: 0, + lines: [' old/destination'], + }, + ], + }]); + + assert.strictEqual(p.getOldSymlink(), 'old/destination'); + assert.isNull(p.getNewSymlink()); + }); + + it("sets the new file's symlink destination", function() { + const p = buildFilePatch([{ + oldPath: 'old/path', + oldMode: '100644', + newPath: 'new/path', + newMode: '120000', + status: 'modified', + hunks: [ + { + oldStartLine: 0, + newStartLine: 0, + oldLineCount: 0, + newLineCount: 0, + lines: [' new/destination'], + }, + ], + }]); + + assert.isNull(p.getOldSymlink()); + assert.strictEqual(p.getNewSymlink(), 'new/destination'); + }); + + it("sets both files' symlink destinations", function() { + const p = buildFilePatch([{ + oldPath: 'old/path', + oldMode: '120000', + newPath: 'new/path', + newMode: '120000', + status: 'modified', + hunks: [ + { + oldStartLine: 0, + newStartLine: 0, + oldLineCount: 0, + newLineCount: 0, + lines: [ + ' old/destination', + ' --', + ' new/destination', + ], + }, + ], + }]); + + assert.strictEqual(p.getOldSymlink(), 'old/destination'); + assert.strictEqual(p.getNewSymlink(), 'new/destination'); + }); + + it('throws an error with an unknown diff status character', function() { + assert.throws(() => { + buildFilePatch([{ + oldPath: 'old/path', + oldMode: '100644', + newPath: 'new/path', + newMode: '100644', + status: 'modified', + hunks: [{oldStartLine: 0, newStartLine: 0, oldLineCount: 1, newLineCount: 1, lines: ['xline-0']}], + }]); + }, /diff status character: "x"/); + }); + + it('parses a no-newline marker', function() { + const p = buildFilePatch([{ + oldPath: 'old/path', + oldMode: '100644', + newPath: 'new/path', + newMode: '100644', + status: 'modified', + hunks: [{oldStartLine: 0, newStartLine: 0, oldLineCount: 1, newLineCount: 1, lines: [ + '+line-0', '-line-1', '\\No newline at end of file', + ]}], + }]); + + buffer = 'line-0\nline-1\nNo newline at end of file\n'; + assert.strictEqual(p.getBufferText(), buffer); + + assert.lengthOf(p.getHunks(), 1); + assertHunk(p.getHunks()[0], { + startPosition: [[0, 0], [0, 0]], + startOffset: 0, + header: '@@ -0,1 +0,1 @@\n', + additions: {strings: ['*line-0\n'], ranges: [[[0, 0], [0, 0]]]}, + deletions: {strings: ['*line-1\n'], ranges: [[[1, 0], [1, 0]]]}, + noNewline: {string: '*No newline at end of file\n', range: [[2, 0], [2, 0]]}, + }); + }); + + it('throws an error when multiple no-newline markers are encountered', function() { + assert.throws(() => { + buildFilePatch([{ + oldPath: 'old/path', + oldMode: '100644', + newPath: 'new/path', + newMode: '100644', + status: 'modified', + hunks: [{oldStartLine: 0, newStartLine: 0, oldLineCount: 1, newLineCount: 1, lines: [ + '\\No newline at end of file', ' unchanged', '\\No newline at end of file', + ]}], + }]); + }, /Multiple nonewline/); + }); + }); + + describe('with a mode change and a content diff', function() { + it('identifies a file that was deleted and replaced by a symlink', function() { + const p = buildFilePatch([ + { + oldPath: 'the-path', + oldMode: '000000', + newPath: 'the-path', + newMode: '120000', + status: 'added', + hunks: [ + { + oldStartLine: 0, + newStartLine: 0, + oldLineCount: 0, + newLineCount: 0, + lines: [' the-destination'], + }, + ], + }, + { + oldPath: 'the-path', + oldMode: '100644', + newPath: 'the-path', + newMode: '000000', + status: 'deleted', + hunks: [ + { + oldStartLine: 0, + newStartLine: 0, + oldLineCount: 0, + newLineCount: 2, + lines: ['+line-0', '+line-1'], + }, + ], + }, + ]); + + assert.strictEqual(p.getOldPath(), 'the-path'); + assert.strictEqual(p.getOldMode(), '100644'); + assert.isNull(p.getOldSymlink()); + assert.strictEqual(p.getNewPath(), 'the-path'); + assert.strictEqual(p.getNewMode(), '120000'); + assert.strictEqual(p.getNewSymlink(), 'the-destination'); + assert.strictEqual(p.getStatus(), 'deleted'); + + buffer = 'line-0\nline-1\n'; + assert.strictEqual(p.getBufferText(), buffer); + assert.lengthOf(p.getHunks(), 1); + assertHunk(p.getHunks()[0], { + startPosition: [[0, 0], [0, 0]], + startOffset: 0, + header: '@@ -0,0 +0,2 @@\n', + deletions: {strings: [], ranges: []}, + additions: { + strings: ['*line-0\n*line-1\n'], + ranges: [[[0, 0], [1, 0]]], + }, + }); + }); + + it('identifies a symlink that was deleted and replaced by a file', function() { + const p = buildFilePatch([ + { + oldPath: 'the-path', + oldMode: '120000', + newPath: 'the-path', + newMode: '000000', + status: 'deleted', + hunks: [ + { + oldStartLine: 0, + newStartLine: 0, + oldLineCount: 0, + newLineCount: 0, + lines: [' the-destination'], + }, + ], + }, + { + oldPath: 'the-path', + oldMode: '000000', + newPath: 'the-path', + newMode: '100644', + status: 'added', + hunks: [ + { + oldStartLine: 0, + newStartLine: 0, + oldLineCount: 2, + newLineCount: 0, + lines: ['-line-0', '-line-1'], + }, + ], + }, + ]); + + assert.strictEqual(p.getOldPath(), 'the-path'); + assert.strictEqual(p.getOldMode(), '120000'); + assert.strictEqual(p.getOldSymlink(), 'the-destination'); + assert.strictEqual(p.getNewPath(), 'the-path'); + assert.strictEqual(p.getNewMode(), '100644'); + assert.isNull(p.getNewSymlink()); + assert.strictEqual(p.getStatus(), 'added'); + + buffer = 'line-0\nline-1\n'; + assert.strictEqual(p.getBufferText(), buffer); + assert.lengthOf(p.getHunks(), 1); + assertHunk(p.getHunks()[0], { + startPosition: [[0, 0], [0, 0]], + startOffset: 0, + header: '@@ -0,2 +0,0 @@\n', + deletions: { + strings: ['*line-0\n*line-1\n'], + ranges: [[[0, 0], [1, 0]]], + }, + additions: {strings: [], ranges: []}, + }); + }); + + it('is indifferent to the order of the diffs', function() { + const p = buildFilePatch([ + { + oldMode: '100644', + newPath: 'the-path', + newMode: '000000', + status: 'deleted', + hunks: [ + { + oldStartLine: 0, + newStartLine: 0, + oldLineCount: 0, + newLineCount: 2, + lines: ['+line-0', '+line-1'], + }, + ], + }, + { + oldPath: 'the-path', + oldMode: '000000', + newPath: 'the-path', + newMode: '120000', + status: 'added', + hunks: [ + { + oldStartLine: 0, + newStartLine: 0, + oldLineCount: 0, + newLineCount: 0, + lines: [' the-destination'], + }, + ], + }, + ]); + + assert.strictEqual(p.getOldPath(), 'the-path'); + assert.strictEqual(p.getOldMode(), '100644'); + assert.isNull(p.getOldSymlink()); + assert.strictEqual(p.getNewPath(), 'the-path'); + assert.strictEqual(p.getNewMode(), '120000'); + assert.strictEqual(p.getNewSymlink(), 'the-destination'); + assert.strictEqual(p.getStatus(), 'deleted'); + + buffer = 'line-0\nline-1\n'; + assert.strictEqual(p.getBufferText(), buffer); + assert.lengthOf(p.getHunks(), 1); + assertHunk(p.getHunks()[0], { + startPosition: [[0, 0], [0, 0]], + startOffset: 0, + header: '@@ -0,0 +0,2 @@\n', + deletions: {strings: [], ranges: []}, + additions: { + strings: ['*line-0\n*line-1\n'], + ranges: [[[0, 0], [1, 0]]], + }, + }); + }); + + it('throws an error on an invalid mode diff status', function() { + assert.throws(() => { + buildFilePatch([ + { + oldMode: '100644', + newPath: 'the-path', + newMode: '000000', + status: 'deleted', + hunks: [ + {oldStartLine: 0, newStartLine: 0, oldLineCount: 0, newLineCount: 2, lines: ['+line-0', '+line-1']}, + ], + }, + { + oldPath: 'the-path', + oldMode: '000000', + newMode: '120000', + status: 'modified', + hunks: [ + {oldStartLine: 0, newStartLine: 0, oldLineCount: 0, newLineCount: 0, lines: [' the-destination']}, + ], + }, + ]); + }, /mode change diff status: modified/); + }); + }); + + it('throws an error with an unexpected number of diffs', function() { + assert.throws(() => buildFilePatch([1, 2, 3]), /Unexpected number of diffs: 3/); + }); +}); From 767bdd5306a085d816779460b1507157fbdf4ec7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Aug 2018 08:49:52 -0400 Subject: [PATCH 0065/4053] YAGNI for MarkerPositions --- lib/atom/marker.js | 23 ++++++++++++++--------- lib/prop-types.js | 14 ++++++++++---- lib/views/file-patch-view.js | 6 +++--- test/atom/decoration.test.js | 6 +++--- test/atom/marker.test.js | 18 ++++++++++-------- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/lib/atom/marker.js b/lib/atom/marker.js index 5bf575ba8f..1ebef279e6 100644 --- a/lib/atom/marker.js +++ b/lib/atom/marker.js @@ -3,17 +3,13 @@ import PropTypes from 'prop-types'; import {CompositeDisposable} from 'event-kit'; import {autobind, extractProps} from '../helpers'; -import {RefHolderPropType, MarkerPositionPropType} from '../prop-types'; +import {RefHolderPropType, RangePropType} from '../prop-types'; import RefHolder from '../models/ref-holder'; -import {fromChangeEvent} from '../models/marker-position'; import {TextEditorContext} from './atom-text-editor'; import {MarkerLayerContext} from './marker-layer'; const MarkablePropType = PropTypes.shape({ markBufferRange: PropTypes.func.isRequired, - markScreenRange: PropTypes.func.isRequired, - markBufferPosition: PropTypes.func.isRequired, - markScreenPosition: PropTypes.func.isRequired, }); const markerProps = { @@ -28,7 +24,7 @@ export const DecorableContext = React.createContext(); class BareMarker extends React.Component { static propTypes = { ...markerProps, - position: MarkerPositionPropType.isRequired, + bufferRange: RangePropType.isRequired, markableHolder: RefHolderPropType, children: PropTypes.node, onDidChange: PropTypes.func, @@ -97,7 +93,7 @@ class BareMarker extends React.Component { const options = extractProps(this.props, markerProps); this.props.markableHolder.map(markable => { - const marker = this.props.position.markOn(markable, options); + const marker = markable.markBufferRange(this.props.bufferRange, options); this.subs.add(marker.onDidChange(this.didChange)); this.markerHolder.setter(marker); @@ -107,12 +103,21 @@ class BareMarker extends React.Component { } updateMarkerPosition() { - this.markerHolder.map(marker => this.props.position.setIn(marker)); + this.markerHolder.map(marker => marker.setBufferRange(this.props.bufferRange)); } didChange(event) { + const reversed = this.markerHolder.map(marker => marker.isReversed()).getOr(false); + + const oldBufferStartPosition = reversed ? event.oldHeadBufferPosition : event.oldTailBufferPosition; + const oldBufferEndPosition = reversed ? event.oldTailBufferPosition : event.oldHeadBufferPosition; + + const newBufferStartPosition = reversed ? event.newHeadBufferPosition : event.newTailBufferPosition; + const newBufferEndPosition = reversed ? event.newTailBufferPosition : event.newHeadBufferPosition; + this.props.onDidChange({ - ...fromChangeEvent(event), + oldRange: new Range(oldBufferStartPosition, oldBufferEndPosition), + newRange: new Range(newBufferStartPosition, newBufferEndPosition), ...event, }); } diff --git a/lib/prop-types.js b/lib/prop-types.js index d8fa6c1ca6..13d0b4d394 100644 --- a/lib/prop-types.js +++ b/lib/prop-types.js @@ -89,10 +89,16 @@ export const RefHolderPropType = PropTypes.shape({ observe: PropTypes.func.isRequired, }); -export const MarkerPositionPropType = PropTypes.shape({ - markOn: PropTypes.func.isRequired, - setIn: PropTypes.func.isRequired, - matches: PropTypes.func.isRequired, +export const PointPropType = PropTypes.shape({ + row: PropTypes.number.isRequired, + column: PropTypes.number.isRequired, + isEqual: PropTypes.func.isRequired, +}); + +export const RangePropType = PropTypes.shape({ + start: PointPropType.isRequired, + end: PointPropType.isRequired, + isEqual: PropTypes.func.isRequired, }); export const EnableableOperationPropType = PropTypes.shape({ diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 9ba6dd3bd2..dcebafb3e3 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -147,7 +147,7 @@ export default class FilePatchView extends React.Component { onMouseMove={this.didMouseMoveOnLineNumber} /> - + {this.renderExecutableModeChangeMeta()} @@ -324,7 +324,7 @@ export default class FilePatchView extends React.Component { return ( @@ -365,7 +365,7 @@ export default class FilePatchView extends React.Component { return ( diff --git a/test/atom/decoration.test.js b/test/atom/decoration.test.js index 6be94ab9ff..ee715009be 100644 --- a/test/atom/decoration.test.js +++ b/test/atom/decoration.test.js @@ -1,12 +1,12 @@ import React from 'react'; import sinon from 'sinon'; import {mount} from 'enzyme'; +import {Range} from 'atom'; import Decoration from '../../lib/atom/decoration'; import AtomTextEditor from '../../lib/atom/atom-text-editor'; import Marker from '../../lib/atom/marker'; import MarkerLayer from '../../lib/atom/marker-layer'; -import {fromBufferRange, fromBufferPosition} from '../../lib/models/marker-position'; describe('Decoration', function() { let atomEnv, editor, marker; @@ -119,7 +119,7 @@ describe('Decoration', function() { it('decorates a parent Marker', function() { const wrapper = mount( - + , @@ -133,7 +133,7 @@ describe('Decoration', function() { mount( - + , diff --git a/test/atom/marker.test.js b/test/atom/marker.test.js index 5ca555f438..9d304bd2ee 100644 --- a/test/atom/marker.test.js +++ b/test/atom/marker.test.js @@ -1,10 +1,10 @@ import React from 'react'; import {mount} from 'enzyme'; +import {Range} from 'atom'; import Marker from '../../lib/atom/marker'; import AtomTextEditor from '../../lib/atom/atom-text-editor'; import MarkerLayer from '../../lib/atom/marker-layer'; -import {fromBufferRange, fromScreenRange, fromBufferPosition} from '../../lib/models/marker-position'; describe('Marker', function() { let atomEnv, editor, markerID; @@ -24,7 +24,7 @@ describe('Marker', function() { it('adds its marker on mount with default properties', function() { mount( - , + , ); const marker = editor.getMarker(markerID); @@ -38,7 +38,7 @@ describe('Marker', function() { , ); @@ -68,7 +68,9 @@ describe('Marker', function() { }); it('destroys its marker on unmount', function() { - const wrapper = mount(); + const wrapper = mount( + , + ); assert.isDefined(editor.getMarker(markerID)); wrapper.unmount(); @@ -78,7 +80,7 @@ describe('Marker', function() { it('marks an editor from a parent node', function() { const wrapper = mount( - + , ); @@ -92,7 +94,7 @@ describe('Marker', function() { const wrapper = mount( { layerID = id; }}> - + , ); From da5c52959c14e3aede06a6f059e59d21bfcbe794 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Aug 2018 10:08:05 -0400 Subject: [PATCH 0066/4053] Replace MarkerPosition and Change with IndexedRowRange --- lib/models/indexed-row-range.js | 91 ++++++++ lib/models/marker-position.js | 261 ---------------------- lib/models/patch/builder.js | 11 +- lib/models/patch/change.js | 113 ---------- test/models/indexed-row-range.test.js | 104 +++++++++ test/models/marker-position.test.js | 310 -------------------------- test/models/patch/change.test.js | 76 ------- 7 files changed, 200 insertions(+), 766 deletions(-) create mode 100644 lib/models/indexed-row-range.js delete mode 100644 lib/models/marker-position.js delete mode 100644 lib/models/patch/change.js create mode 100644 test/models/indexed-row-range.test.js delete mode 100644 test/models/marker-position.test.js delete mode 100644 test/models/patch/change.test.js diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js new file mode 100644 index 0000000000..31bf5af749 --- /dev/null +++ b/lib/models/indexed-row-range.js @@ -0,0 +1,91 @@ +import {Range} from 'atom'; + +// A {Range} of rows within a buffer accompanied by its corresponding start and end offsets. +// +// Note that the range's columns are disregarded for purposes of offset consistency. +export default class IndexedRowRange { + constructor({bufferRange, startOffset, endOffset}) { + this.bufferRange = Range.fromObject(bufferRange); + this.startOffset = startOffset; + this.endOffset = endOffset; + } + + bufferRowCount() { + return this.bufferRange.getRowCount(); + } + + toStringIn(buffer, prefix) { + return buffer.slice(this.startOffset, this.endOffset).replace(/(^|\n)(?!$)/g, '$&' + prefix); + } + + intersectRowsIn(rowSet, buffer) { + // Identify Ranges within our bufferRange that intersect the rows in rowSet. + const intersections = []; + let nextStartRow = null; + let nextStartOffset = null; + + let currentRow = this.bufferRange.start.row; + let currentOffset = this.startOffset; + + while (currentRow <= this.bufferRange.end.row) { + if (rowSet.has(currentRow) && nextStartRow === null) { + // Start of intersecting row range + nextStartRow = currentRow; + nextStartOffset = currentOffset; + } else if (!rowSet.has(currentRow) && nextStartRow !== null) { + // One row past the end of intersecting row range + intersections.push(new IndexedRowRange({ + bufferRange: Range.fromObject([[nextStartRow, 0], [currentRow - 1, 0]]), + startOffset: nextStartOffset, + endOffset: currentOffset, + })); + + nextStartRow = null; + nextStartOffset = null; + } + + currentOffset = buffer.indexOf('\n', currentOffset) + 1; + currentRow++; + } + + if (nextStartRow !== null) { + intersections.push(new IndexedRowRange({ + bufferRange: Range.fromObject([[nextStartRow, 0], this.bufferRange.end]), + startOffset: nextStartOffset, + endOffset: currentOffset, + })); + } + + return intersections; + } + + serialize() { + return { + bufferRange: this.bufferRange.serialize(), + startOffset: this.startOffset, + endOffset: this.endOffset, + }; + } + + isPresent() { + return true; + } +} + +export const nullIndexedRowRange = { + bufferRowCount() { + return 0; + }, + + toStringIn() { + return ''; + }, + + intersectRowsIn() { + return []; + }, + + isPresent() { + return false; + }, +}; diff --git a/lib/models/marker-position.js b/lib/models/marker-position.js deleted file mode 100644 index f60d9987ab..0000000000 --- a/lib/models/marker-position.js +++ /dev/null @@ -1,261 +0,0 @@ -import {Range} from 'atom'; - -class Position { - constructor(bufferRange, screenRange) { - this.bufferRange = bufferRange && Range.fromObject(bufferRange); - this.screenRange = screenRange && Range.fromObject(screenRange); - } - - /* istanbul ignore next */ - markOn(markable, options) { - throw new Error('markOn not overridden'); - } - - /* istanbul ignore next */ - setIn(marker) { - throw new Error('setIn not overridden'); - } - - /* istanbul ignore next */ - matches(other) { - throw new Error('matches not overridden'); - } - - matchFromBufferRange(other) { - return false; - } - - matchFromScreenRange(other) { - return false; - } - - bufferStartRow() { - return this.bufferRange !== null ? this.bufferRange.start.row : -1; - } - - bufferEndRow() { - return this.bufferRange !== null ? this.bufferRange.end.row : -1; - } - - bufferRowCount() { - return this.bufferRange !== null ? this.bufferRange.getRowCount() : 0; - } - - intersectRows(rowSet) { - if (this.bufferRange === null) { - return []; - } - - const intersections = []; - let currentRangeStart = null; - - let row = this.bufferRange.start.row; - while (row <= this.bufferRange.end.row) { - if (rowSet.has(row) && currentRangeStart === null) { - currentRangeStart = row; - } else if (!rowSet.has(row) && currentRangeStart !== null) { - intersections.push( - fromBufferRange([[currentRangeStart, 0], [row - 1, 0]]), - ); - currentRangeStart = null; - } - row++; - } - if (currentRangeStart !== null) { - intersections.push( - fromBufferRange([[currentRangeStart, 0], this.bufferRange.end]), - ); - } - - return intersections; - } - - /* istanbul ignore next */ - serialize() { - throw new Error('serialize not overridden'); - } - - isPresent() { - return true; - } -} - -class BufferRangePosition extends Position { - constructor(bufferRange) { - super(bufferRange, null); - } - - markOn(markable, options) { - return markable.markBufferRange(this.bufferRange, options); - } - - setIn(marker) { - return marker.setBufferRange(this.bufferRange); - } - - matches(other) { - return other.matchFromBufferRange(this); - } - - matchFromBufferRange(other) { - return other.bufferRange.isEqual(this.bufferRange); - } - - serialize() { - return this.bufferRange.serialize(); - } - - toString() { - return `buffer(${this.bufferRange.toString()})`; - } -} - -class ScreenRangePosition extends Position { - constructor(screenRange) { - super(null, screenRange); - } - - markOn(markable, options) { - return markable.markScreenRange(this.screenRange, options); - } - - setIn(marker) { - return marker.setScreenRange(this.screenRange); - } - - matches(other) { - return other.matchFromScreenRange(this); - } - - matchFromScreenRange(other) { - return other.screenRange.isEqual(this.screenRange); - } - - serialize() { - return this.screenRange.serialize(); - } - - toString() { - return `screen(${this.screenRange.toString()})`; - } -} - -class BufferOrScreenRangePosition extends Position { - markOn(markable, options) { - return markable.markBufferRange(this.bufferRange, options); - } - - setIn(marker) { - return marker.setBufferRange(this.bufferRange); - } - - matches(other) { - return other.matchFromBufferRange(this) || other.matchFromScreenRange(this); - } - - matchFromBufferRange(other) { - return other.bufferRange.isEqual(this.bufferRange); - } - - matchFromScreenRange(other) { - return other.screenRange.isEqual(this.screenRange); - } - - serialize() { - return this.bufferRange.serialize(); - } - - toString() { - return `either(b${this.bufferRange.toString()}/s${this.screenRange.toString()})`; - } -} - -export const nullPosition = { - markOn() { - return null; - }, - - setIn() {}, - - matches(other) { - return other === this; - }, - - matchFromBufferRange() { - return false; - }, - - matchFromScreenRange() { - return false; - }, - - bufferStartRow() { - return -1; - }, - - bufferRowCount() { - return 0; - }, - - intersectRows() { - return []; - }, - - serialize() { - return null; - }, - - isPresent() { - return false; - }, - - toString() { - return 'null'; - }, -}; - -export function fromBufferRange(bufferRange) { - return new BufferRangePosition(Range.fromObject(bufferRange)); -} - -export function fromBufferPosition(bufferPoint) { - return new BufferRangePosition(new Range(bufferPoint, bufferPoint)); -} - -export function fromScreenRange(screenRange) { - return new ScreenRangePosition(Range.fromObject(screenRange)); -} - -export function fromScreenPosition(screenPoint) { - return new ScreenRangePosition(new Range(screenPoint, screenPoint)); -} - -export function fromMarker(marker) { - return new BufferOrScreenRangePosition( - marker.getBufferRange(), - marker.getScreenRange(), - ); -} - -export function fromChangeEvent(event, reversed = false) { - const oldBufferStartPosition = reversed ? event.oldHeadBufferPosition : event.oldTailBufferPosition; - const oldBufferEndPosition = reversed ? event.oldTailBufferPosition : event.oldHeadBufferPosition; - const oldScreenStartPosition = reversed ? event.oldHeadScreenPosition : event.oldTailScreenPosition; - const oldScreenEndPosition = reversed ? event.oldTailScreenPosition : event.oldHeadScreenPosition; - - const newBufferStartPosition = reversed ? event.newHeadBufferPosition : event.newTailBufferPosition; - const newBufferEndPosition = reversed ? event.newTailBufferPosition : event.newHeadBufferPosition; - const newScreenStartPosition = reversed ? event.newHeadScreenPosition : event.newTailScreenPosition; - const newScreenEndPosition = reversed ? event.newTailScreenPosition : event.newHeadScreenPosition; - - return { - oldPosition: new BufferOrScreenRangePosition( - new Range(oldBufferStartPosition, oldBufferEndPosition), - new Range(oldScreenStartPosition, oldScreenEndPosition), - ), - newPosition: new BufferOrScreenRangePosition( - new Range(newBufferStartPosition, newBufferEndPosition), - new Range(newScreenStartPosition, newScreenEndPosition), - ), - }; -} diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index a0808a1971..2f5b5cd34b 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -1,9 +1,8 @@ import Hunk from './hunk'; import File, {nullFile} from './file'; import Patch, {nullPatch} from './patch'; -import Change, {nullChange} from './change'; +import IndexedRowRange, {nullIndexedRowRange} from '../indexed-row-range'; import FilePatch from './file-patch'; -import {fromBufferRange, fromBufferPosition} from '../marker-position'; export default function buildFilePatch(diffs) { if (diffs.length === 0) { @@ -117,8 +116,8 @@ function buildHunks(diff) { nonewline: noNewlines, }[lastStatus]; if (changes !== undefined) { - changes.push(new Change({ - position: fromBufferRange([[currentRangeStart, 0], [bufferRow - 1, 0]]), + changes.push(new IndexedRowRange({ + bufferRange: Range.fromObject([[currentRangeStart, 0], [bufferRow - 1, 0]]), startOffset, endOffset: bufferOffset, })); @@ -146,7 +145,7 @@ function buildHunks(diff) { } finishCurrentChange(); - let noNewline = nullChange; + let noNewline = nullIndexedRowRange; if (noNewlines.length === 1) { noNewline = noNewlines[0]; } else if (noNewlines.length > 1) { @@ -159,7 +158,7 @@ function buildHunks(diff) { oldRowCount: hunkData.oldLineCount, newRowCount: hunkData.newLineCount, sectionHeading: hunkData.heading, - bufferStartPosition: fromBufferPosition([bufferStartRow, 0]), + bufferStartPosition: null, // fromBufferPosition([bufferStartRow, 0]), bufferStartOffset, bufferEndRow: bufferRow, additions, diff --git a/lib/models/patch/change.js b/lib/models/patch/change.js deleted file mode 100644 index 4cea4461d8..0000000000 --- a/lib/models/patch/change.js +++ /dev/null @@ -1,113 +0,0 @@ -import {nullPosition} from '../marker-position'; - -// A contiguous region of additions or deletions within a {Hunk}. -// -// The Change's position is a {MarkerPosition} containing a {Range} of the change rows within the diff buffer. The -// calculated offsets delimit a half-open interval of {String} code point offsets within the diff buffer, such that -// `buffer.slice(startOffset, endOffset)` returns the exact contents of the changed lines, including the last line -// and its line-end character. -export default class Change { - constructor({position, startOffset, endOffset}) { - this.position = position; - this.startOffset = startOffset; - this.endOffset = endOffset; - } - - markOn(markable, options) { - return this.position.markOn(markable, options); - } - - setIn(marker) { - return this.position.setIn(marker); - } - - bufferRowCount() { - return this.position.bufferRowCount(); - } - - toStringIn(buffer, origin) { - let str = ''; - for (let offset = this.startOffset; offset < this.endOffset; offset++) { - const ch = buffer[offset]; - if (offset === this.startOffset) { - str += origin; - } - str += ch; - if (ch === '\n' && offset !== this.endOffset - 1) { - str += origin; - } - } - return str; - } - - intersectRowsIn(rowSet, buffer) { - const intPositions = this.position.intersectRows(rowSet); - const intChanges = []; - let intIndex = 0; - let currentRow = this.position.bufferStartRow(); - let currentOffset = this.startOffset; - let nextStartOffset = null; - - while (intIndex < intPositions.length && currentOffset < this.endOffset) { - const currentInt = intPositions[intIndex]; - - if (currentRow === currentInt.bufferStartRow()) { - nextStartOffset = currentOffset; - } - - if (currentRow === currentInt.bufferEndRow() + 1) { - intChanges.push(new this.constructor({ - position: currentInt, - startOffset: nextStartOffset, - endOffset: currentOffset, - })); - - intIndex++; - nextStartOffset = null; - } - - currentOffset = buffer.indexOf('\n', currentOffset) + 1; - currentRow++; - } - - if (intIndex < intPositions.length && nextStartOffset !== null) { - intChanges.push(new this.constructor({ - position: intPositions[intIndex], - startOffset: nextStartOffset, - endOffset: currentOffset, - })); - } - - return intChanges; - } - - isPresent() { - return true; - } -} - -export const nullChange = { - markOn(...args) { - return nullPosition.markOn(...args); - }, - - setIn(...args) { - return nullPosition.setIn(...args); - }, - - bufferRowCount() { - return nullPosition.bufferRowCount(); - }, - - toStringIn() { - return ''; - }, - - intersectRowsIn() { - return []; - }, - - isPresent() { - return false; - }, -}; diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js new file mode 100644 index 0000000000..1cd83d2a31 --- /dev/null +++ b/test/models/indexed-row-range.test.js @@ -0,0 +1,104 @@ +import IndexedRowRange, {nullIndexedRowRange} from '../../lib/models/indexed-row-range'; + +describe('IndexedRowRange', function() { + it('computes its row count', function() { + const range = new IndexedRowRange({ + bufferRange: [[0, 0], [1, 0]], + startOffset: 0, + endOffset: 10, + }); + assert.isTrue(range.isPresent()); + assert.deepEqual(range.bufferRowCount(), 2); + }); + + it('extracts its offset range from buffer text with toStringIn()', function() { + const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n'; + const range = new IndexedRowRange({ + bufferRange: [[1, 0], [2, 0]], + startOffset: 5, + endOffset: 25, + }); + + assert.strictEqual(range.toStringIn(buffer, '+'), '+1111\n+2222\n+3333\n+4444\n'); + assert.strictEqual(range.toStringIn(buffer, '-'), '-1111\n-2222\n-3333\n-4444\n'); + }); + + describe('intersectRowsIn()', function() { + const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999\n'; + // 0000.1111.2222.3333.4444.5555.6666.7777.8888.9999. + + it('returns an empty array with no intersection rows', function() { + const range = new IndexedRowRange({ + bufferRange: [[1, 0], [3, 0]], + startOffset: 5, + endOffset: 20, + }); + + assert.deepEqual(range.intersectRowsIn(new Set([0, 5, 6]), buffer), []); + }); + + it('detects an intersection at the beginning of the range', function() { + const range = new IndexedRowRange({ + bufferRange: [[2, 0], [6, 0]], + startOffset: 10, + endOffset: 35, + }); + const rowSet = new Set([0, 1, 2, 3]); + + assert.deepEqual(range.intersectRowsIn(rowSet, buffer).map(i => i.serialize()), [ + {bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20}, + ]); + }); + + it('detects an intersection in the middle of the range', function() { + const range = new IndexedRowRange({ + bufferRange: [[2, 0], [6, 0]], + startOffset: 10, + endOffset: 35, + }); + const rowSet = new Set([0, 3, 4, 8, 9]); + + assert.deepEqual(range.intersectRowsIn(rowSet, buffer).map(i => i.serialize()), [ + {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, + ]); + }); + + it('detects an intersection at the end of the range', function() { + const range = new IndexedRowRange({ + bufferRange: [[2, 0], [6, 0]], + startOffset: 10, + endOffset: 35, + }); + const rowSet = new Set([4, 5, 6, 7, 10, 11]); + + assert.deepEqual(range.intersectRowsIn(rowSet, buffer).map(i => i.serialize()), [ + {bufferRange: [[4, 0], [6, 0]], startOffset: 20, endOffset: 35}, + ]); + }); + + it('detects multiple intersections', function() { + const range = new IndexedRowRange({ + bufferRange: [[2, 0], [8, 0]], + startOffset: 10, + endOffset: 45, + }); + const rowSet = new Set([0, 3, 4, 6, 7, 10]); + + assert.deepEqual(range.intersectRowsIn(rowSet, buffer).map(i => i.serialize()), [ + {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, + {bufferRange: [[6, 0], [7, 0]], startOffset: 30, endOffset: 40}, + ]); + }); + + it('returns an empty array for the null range', function() { + assert.deepEqual(nullIndexedRowRange.intersectRowsIn(new Set([1, 2, 3]), buffer), []); + }); + }); + + it('returns appropriate values from nullIndexedRowRange methods', function() { + assert.deepEqual(nullIndexedRowRange.intersectRowsIn(new Set([0, 1, 2]), ''), []); + assert.strictEqual(nullIndexedRowRange.toStringIn('', '+'), ''); + assert.strictEqual(nullIndexedRowRange.bufferRowCount(), 0); + assert.isFalse(nullIndexedRowRange.isPresent()); + }); +}); diff --git a/test/models/marker-position.test.js b/test/models/marker-position.test.js deleted file mode 100644 index 3f5b069eec..0000000000 --- a/test/models/marker-position.test.js +++ /dev/null @@ -1,310 +0,0 @@ -import { - nullPosition, fromBufferRange, fromBufferPosition, fromScreenRange, fromScreenPosition, fromMarker, fromChangeEvent, -} from '../../lib/models/marker-position'; - -describe('MarkerPosition', function() { - let atomEnv, editor; - - beforeEach(async function() { - atomEnv = global.buildAtomEnvironment(); - - editor = await atomEnv.workspace.open(__filename); - }); - - afterEach(function() { - atomEnv.destroy(); - }); - - describe('markOn', function() { - it('marks a buffer range', function() { - const position = fromBufferRange([[1, 0], [4, 0]]); - const marker = position.markOn(editor, {invalidate: 'never'}); - - assert.deepEqual(marker.getBufferRange().serialize(), [[1, 0], [4, 0]]); - assert.strictEqual(marker.getInvalidationStrategy(), 'never'); - }); - - it('marks a buffer position', function() { - const position = fromBufferPosition([2, 0]); - const marker = position.markOn(editor, {invalidate: 'never'}); - - assert.deepEqual(marker.getBufferRange().serialize(), [[2, 0], [2, 0]]); - assert.strictEqual(marker.getInvalidationStrategy(), 'never'); - }); - - it('marks a screen range', function() { - const position = fromScreenRange([[2, 0], [5, 0]]); - const marker = position.markOn(editor, {invalidate: 'never'}); - - assert.deepEqual(marker.getBufferRange().serialize(), [[2, 0], [5, 0]]); - assert.strictEqual(marker.getInvalidationStrategy(), 'never'); - }); - - it('marks a screen position', function() { - const position = fromScreenPosition([3, 0]); - const marker = position.markOn(editor, {invalidate: 'never'}); - - assert.deepEqual(marker.getBufferRange().serialize(), [[3, 0], [3, 0]]); - assert.strictEqual(marker.getInvalidationStrategy(), 'never'); - }); - - it('marks a combination position', function() { - const marker0 = editor.markBufferRange([[0, 0], [2, 0]]); - const position = fromMarker(marker0); - - const marker1 = position.markOn(editor, {invalidate: 'never'}); - assert.deepEqual(marker1.getBufferRange().serialize(), [[0, 0], [2, 0]]); - assert.strictEqual(marker1.getInvalidationStrategy(), 'never'); - }); - - it('does nothing with a nullPosition', function() { - assert.isNull(nullPosition.markOn(editor, {})); - assert.lengthOf(editor.findMarkers({}), 0); - }); - }); - - describe('setIn', function() { - let marker; - - beforeEach(function() { - marker = editor.markBufferRange([[1, 0], [3, 0]]); - }); - - it('updates an existing marker by buffer range', function() { - fromBufferRange([[2, 0], [4, 0]]).setIn(marker); - assert.deepEqual(marker.getBufferRange().serialize(), [[2, 0], [4, 0]]); - }); - - it('updates an existing marker by screen range', function() { - fromScreenRange([[6, 0], [7, 0]]).setIn(marker); - assert.deepEqual(marker.getBufferRange().serialize(), [[6, 0], [7, 0]]); - }); - - it('updates with a combination position', function() { - const other = editor.markBufferRange([[2, 0], [4, 0]]); - fromMarker(other).setIn(marker); - assert.deepEqual(marker.getBufferRange().serialize(), [[2, 0], [4, 0]]); - }); - - it('does nothing with a nullPosition', function() { - nullPosition.setIn(marker); - assert.deepEqual(marker.getBufferRange().serialize(), [[1, 0], [3, 0]]); - }); - }); - - describe('matches', function() { - let marker0, marker1; - - beforeEach(function() { - marker0 = editor.markBufferRange([[2, 0], [4, 0]]); - marker1 = editor.markBufferRange([[1, 0], [3, 0]]); - }); - - it('a buffer range', function() { - const position = fromBufferRange([[2, 0], [4, 0]]); - assert.isTrue(position.matches(fromBufferRange([[2, 0], [4, 0]]))); - assert.isFalse(position.matches(fromBufferRange([[2, 0], [5, 0]]))); - assert.isFalse(position.matches(fromScreenRange([[2, 0], [4, 0]]))); - assert.isTrue(position.matches(fromMarker(marker0))); - assert.isFalse(position.matches(fromMarker(marker1))); - assert.isFalse(position.matches(nullPosition)); - }); - - it('a screen range', function() { - const position = fromScreenRange([[1, 0], [3, 0]]); - assert.isTrue(position.matches(fromScreenRange([[1, 0], [3, 0]]))); - assert.isFalse(position.matches(fromScreenRange([[2, 0], [4, 0]]))); - assert.isFalse(position.matches(fromBufferRange([[1, 0], [3, 0]]))); - assert.isFalse(position.matches(fromMarker(marker0))); - assert.isTrue(position.matches(fromMarker(marker1))); - assert.isFalse(position.matches(nullPosition)); - }); - - it('a combination range', function() { - const position = fromMarker(marker0); - assert.isTrue(position.matches(fromMarker(marker0))); - assert.isFalse(position.matches(fromMarker(marker1))); - assert.isTrue(position.matches(fromBufferRange([[2, 0], [4, 0]]))); - assert.isFalse(position.matches(fromBufferRange([[1, 0], [3, 0]]))); - assert.isTrue(position.matches(fromScreenRange([[2, 0], [4, 0]]))); - assert.isFalse(position.matches(fromScreenRange([[1, 0], [3, 0]]))); - assert.isFalse(position.matches(nullPosition)); - }); - - it('a null position', function() { - assert.isTrue(nullPosition.matches(nullPosition)); - assert.isFalse(nullPosition.matches(fromBufferRange([[1, 0], [2, 0]]))); - assert.isFalse(nullPosition.matches(fromScreenRange([[1, 0], [2, 0]]))); - assert.isFalse(nullPosition.matches(fromMarker(marker0))); - }); - }); - - describe('bufferStartRow()', function() { - it('retrieves the first row from a buffer range', function() { - assert.strictEqual(fromBufferRange([[2, 0], [3, 4]]).bufferStartRow(), 2); - }); - - it('retrieves the first row from a combination range', function() { - const marker = editor.markBufferRange([[3, 0], [5, 0]]); - assert.strictEqual(fromMarker(marker).bufferStartRow(), 3); - }); - - it('returns -1 from a screen range', function() { - assert.strictEqual(fromScreenRange([[1, 0], [2, 0]]).bufferStartRow(), -1); - }); - - it('returns -1 from a null position', function() { - assert.strictEqual(nullPosition.bufferStartRow(), -1); - }); - }); - - describe('bufferRowCount()', function() { - it('counts the rows in a buffer range', function() { - assert.strictEqual(fromBufferRange([[1, 0], [4, 0]]).bufferRowCount(), 4); - assert.strictEqual(fromBufferPosition([2, 0]).bufferRowCount(), 1); - }); - - it('counts the rows in a combination range', function() { - const marker = editor.markBufferRange([[2, 0], [6, 0]]); - assert.strictEqual(fromMarker(marker).bufferRowCount(), 5); - }); - - it('returns 0 for screen or null ranges', function() { - assert.strictEqual(fromScreenRange([[1, 0], [2, 0]]).bufferRowCount(), 0); - assert.strictEqual(nullPosition.bufferRowCount(), 0); - }); - }); - - describe('intersectRows()', function() { - it('returns an empty array with no intersection rows', function() { - assert.deepEqual(fromBufferRange([[1, 0], [3, 0]]).intersectRows(new Set([0, 5, 6])), []); - }); - - it('detects an intersection at the beginning of the range', function() { - const position = fromBufferRange([[2, 0], [6, 0]]); - const rowSet = new Set([0, 1, 2, 3]); - - assert.deepEqual(position.intersectRows(rowSet).map(i => i.serialize()), [ - [[2, 0], [3, 0]], - ]); - }); - - it('detects an intersection in the middle of the range', function() { - const position = fromBufferRange([[2, 0], [6, 0]]); - const rowSet = new Set([0, 3, 4, 8, 9]); - - assert.deepEqual(position.intersectRows(rowSet).map(i => i.serialize()), [ - [[3, 0], [4, 0]], - ]); - }); - - it('detects an intersection at the end of the range', function() { - const position = fromBufferRange([[2, 0], [6, 0]]); - const rowSet = new Set([4, 5, 6, 7, 10, 11]); - - assert.deepEqual(position.intersectRows(rowSet).map(i => i.serialize()), [ - [[4, 0], [6, 0]], - ]); - }); - - it('detects multiple intersections', function() { - const position = fromBufferRange([[2, 0], [8, 0]]); - const rowSet = new Set([0, 3, 4, 6, 7, 10]); - - assert.deepEqual(position.intersectRows(rowSet).map(i => i.serialize()), [ - [[3, 0], [4, 0]], - [[6, 0], [7, 0]], - ]); - }); - - it('returns an empty array for screen or null ranges', function() { - assert.deepEqual(fromScreenRange([[1, 0], [4, 0]]).intersectRows(new Set()), []); - assert.deepEqual(nullPosition.intersectRows(new Set()), []); - }); - }); - - describe('isPresent()', function() { - it('returns true on non-null positions', function() { - assert.isTrue(fromBufferRange([[1, 0], [2, 0]]).isPresent()); - assert.isTrue(fromScreenRange([[1, 0], [2, 0]]).isPresent()); - - const marker = editor.markBufferRange([[1, 0], [2, 0]]); - assert.isTrue(fromMarker(marker).isPresent()); - }); - - it('returns false on null positions', function() { - assert.isFalse(nullPosition.isPresent()); - }); - }); - - describe('serialize()', function() { - it('produces an array', function() { - assert.deepEqual(fromBufferRange([[0, 0], [1, 1]]).serialize(), [[0, 0], [1, 1]]); - assert.deepEqual(fromScreenRange([[0, 0], [1, 1]]).serialize(), [[0, 0], [1, 1]]); - - const marker = editor.markBufferRange([[2, 2], [3, 0]]); - assert.deepEqual(fromMarker(marker).serialize(), [[2, 2], [3, 0]]); - }); - - it('serializes a null position as null', function() { - assert.isNull(nullPosition.serialize()); - }); - }); - - describe('toString()', function() { - it('pretty-prints buffer ranges', function() { - assert.strictEqual(fromBufferRange([[0, 0], [2, 0]]).toString(), 'buffer([(0, 0) - (2, 0)])'); - }); - - it('pretty-prints screen ranges', function() { - assert.strictEqual(fromScreenRange([[3, 0], [7, 0]]).toString(), 'screen([(3, 0) - (7, 0)])'); - }); - - it('pretty-prints combination ranges', function() { - const marker = editor.markBufferRange([[1, 0], [3, 0]]); - assert.strictEqual(fromMarker(marker).toString(), 'either(b[(1, 0) - (3, 0)]/s[(1, 0) - (3, 0)])'); - }); - - it('pretty-prints a null position', function() { - assert.strictEqual(nullPosition.toString(), 'null'); - }); - }); - - describe('fromChangeEvent()', function() { - it('produces positions from a non-reversed marker change', function() { - const {oldPosition, newPosition} = fromChangeEvent({ - oldTailBufferPosition: [0, 0], - oldHeadBufferPosition: [1, 1], - oldTailScreenPosition: [2, 2], - oldHeadScreenPosition: [3, 3], - newTailBufferPosition: [4, 4], - newHeadBufferPosition: [5, 5], - newTailScreenPosition: [6, 6], - newHeadScreenPosition: [7, 7], - }); - - assert.isTrue(oldPosition.matches(fromBufferRange([[0, 0], [1, 1]]))); - assert.isTrue(oldPosition.matches(fromScreenRange([[2, 2], [3, 3]]))); - assert.isTrue(newPosition.matches(fromBufferRange([[4, 4], [5, 5]]))); - assert.isTrue(newPosition.matches(fromScreenRange([[6, 6], [7, 7]]))); - }); - - it('produces positions from a reversed marker change', function() { - const {oldPosition, newPosition} = fromChangeEvent({ - oldTailBufferPosition: [0, 0], - oldHeadBufferPosition: [1, 1], - oldTailScreenPosition: [2, 2], - oldHeadScreenPosition: [3, 3], - newTailBufferPosition: [4, 4], - newHeadBufferPosition: [5, 5], - newTailScreenPosition: [6, 6], - newHeadScreenPosition: [7, 7], - }, true); - - assert.isTrue(oldPosition.matches(fromBufferRange([[1, 1], [0, 0]]))); - assert.isTrue(oldPosition.matches(fromScreenRange([[3, 3], [2, 2]]))); - assert.isTrue(newPosition.matches(fromBufferRange([[5, 5], [4, 4]]))); - assert.isTrue(newPosition.matches(fromScreenRange([[7, 7], [6, 6]]))); - }); - }); -}); diff --git a/test/models/patch/change.test.js b/test/models/patch/change.test.js deleted file mode 100644 index 976f9d5202..0000000000 --- a/test/models/patch/change.test.js +++ /dev/null @@ -1,76 +0,0 @@ -import Change, {nullChange} from '../../../lib/models/patch/change'; -import {fromBufferRange, nullPosition} from '../../../lib/models/marker-position'; - -describe('Change', function() { - it('delegates methods to its MarkerPosition', function() { - const ch = new Change({ - position: fromBufferRange([[0, 0], [1, 0]]), - startOffset: 0, - endOffset: 10, - }); - - const markable = {markBufferRange: sinon.stub().returns(0)}; - assert.strictEqual(ch.markOn(markable, {}), 0); - assert.deepEqual(markable.markBufferRange.firstCall.args[0].serialize(), [[0, 0], [1, 0]]); - - const marker = {setBufferRange: sinon.stub().returns(1)}; - assert.strictEqual(ch.setIn(marker), 1); - assert.deepEqual(marker.setBufferRange.firstCall.args[0].serialize(), [[0, 0], [1, 0]]); - - assert.deepEqual(ch.bufferRowCount(), 2); - }); - - it('extracts its offset range from buffer text with toStringIn()', function() { - const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n'; - const ch = new Change({ - position: fromBufferRange([[1, 0], [2, 0]]), - startOffset: 5, - endOffset: 25, - }); - - assert.strictEqual(ch.toStringIn(buffer, '+'), '+1111\n+2222\n+3333\n+4444\n'); - assert.strictEqual(ch.toStringIn(buffer, '-'), '-1111\n-2222\n-3333\n-4444\n'); - }); - - it('returns Changes corresponding to intersecting buffer rows', function() { - const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999'; - const ch = new Change({ - position: fromBufferRange([[1, 0], [8, 0]]), - startOffset: 5, - endOffset: 45, - }); - - const intersections = ch.intersectRowsIn(new Set([4, 5]), buffer); - assert.lengthOf(intersections, 1); - assert.strictEqual(intersections[0].toStringIn(buffer, '-'), '-4444\n-5555\n'); - }); - - it('includes a Change corresponding to an intersection at the end of the range', function() { - const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999'; - const ch = new Change({ - position: fromBufferRange([[1, 0], [8, 0]]), - startOffset: 5, - endOffset: 45, - }); - - const intersections = ch.intersectRowsIn(new Set([1, 2, 4, 8]), buffer); - assert.lengthOf(intersections, 3); - - const int0 = intersections[0]; - assert.strictEqual(int0.toStringIn(buffer, '+'), '+1111\n+2222\n'); - - const int1 = intersections[1]; - assert.strictEqual(int1.toStringIn(buffer, '-'), '-4444\n'); - - const int2 = intersections[2]; - assert.strictEqual(int2.toStringIn(buffer, '+'), '+8888\n'); - }); - - it('returns appropriate values from nullChange methods', function() { - assert.deepEqual(nullChange.intersectRowsIn(new Set([0, 1, 2]), ''), []); - assert.strictEqual(nullChange.toStringIn('', '+'), ''); - assert.strictEqual(nullChange.markOn(), nullPosition.markOn()); - assert.strictEqual(nullChange.setIn(), nullPosition.setIn()); - assert.strictEqual(nullChange.bufferRowCount(), 0); - }); -}); From 98ae78d74a5616ca60fabbc43991f7ffcaca42c6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Aug 2018 11:01:38 -0400 Subject: [PATCH 0067/4053] More IndexedRowRange methods I need --- lib/models/indexed-row-range.js | 13 +++++++++++++ test/models/indexed-row-range.test.js | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js index 31bf5af749..6098792466 100644 --- a/lib/models/indexed-row-range.js +++ b/lib/models/indexed-row-range.js @@ -10,6 +10,15 @@ export default class IndexedRowRange { this.endOffset = endOffset; } + getBufferRows() { + return this.bufferRange.getRows(); + } + + getStartRange() { + const start = this.bufferRange.start; + return new Range(start, start); + } + bufferRowCount() { return this.bufferRange.getRowCount(); } @@ -73,6 +82,10 @@ export default class IndexedRowRange { } export const nullIndexedRowRange = { + startOffset: Infinity, + + endOffset: Infinity, + bufferRowCount() { return 0; }, diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index 1cd83d2a31..8eae81e685 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -11,6 +11,24 @@ describe('IndexedRowRange', function() { assert.deepEqual(range.bufferRowCount(), 2); }); + it('returns an array of the covered rows', function() { + const range = new IndexedRowRange({ + bufferRange: [[2, 0], [8, 0]], + startOffset: 0, + endOffset: 10, + }); + assert.sameMembers(range.getBufferRows(), [2, 3, 4, 5, 6, 7, 8]); + }); + + it('creates a Range from its first line', function() { + const range = new IndexedRowRange({ + bufferRange: [[2, 0], [8, 0]], + startOffset: 0, + endOffset: 10, + }); + assert.deepEqual(range.getStartRange().serialize(), [[2, 0], [2, 0]]); + }); + it('extracts its offset range from buffer text with toStringIn()', function() { const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n'; const range = new IndexedRowRange({ From 20ede32b69645f07d6388e7f8f512eeca60a4cb6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Aug 2018 11:02:25 -0400 Subject: [PATCH 0068/4053] Remodel Hunk to use IndexedRowRanges instead of HunkLines --- lib/models/hunk-line.js | 97 ------------- lib/models/hunk.js | 83 ------------ lib/models/patch/hunk.js | 137 +++++++++++++++++++ test/models/patch/hunk.test.js | 239 +++++++++++++++++++++++++++++++++ 4 files changed, 376 insertions(+), 180 deletions(-) delete mode 100644 lib/models/hunk-line.js delete mode 100644 lib/models/hunk.js create mode 100644 lib/models/patch/hunk.js create mode 100644 test/models/patch/hunk.test.js diff --git a/lib/models/hunk-line.js b/lib/models/hunk-line.js deleted file mode 100644 index 8ab430836e..0000000000 --- a/lib/models/hunk-line.js +++ /dev/null @@ -1,97 +0,0 @@ -export default class HunkLine { - static statusMap = { - '+': 'added', - '-': 'deleted', - ' ': 'unchanged', - '\\': 'nonewline', - } - - constructor(text, status, oldLineNumber, newLineNumber, diffLineNumber) { - this.text = text; - this.status = status; - this.oldLineNumber = oldLineNumber; - this.newLineNumber = newLineNumber; - this.diffLineNumber = diffLineNumber; - } - - copy({text, status, oldLineNumber, newLineNumber} = {}) { - return new HunkLine( - text || this.getText(), - status || this.getStatus(), - oldLineNumber || this.getOldLineNumber(), - newLineNumber || this.getNewLineNumber(), - ); - } - - getText() { - return this.text; - } - - getOldLineNumber() { - return this.oldLineNumber; - } - - getNewLineNumber() { - return this.newLineNumber; - } - - getDiffLineNumber() { - return this.diffLineNumber; - } - - getStatus() { - return this.status; - } - - isChanged() { - return this.getStatus() === 'added' || this.getStatus() === 'deleted'; - } - - getOrigin() { - switch (this.getStatus()) { - case 'added': - return '+'; - case 'deleted': - return '-'; - case 'unchanged': - return ' '; - case 'nonewline': - return '\\'; - default: - return ''; - } - } - - invert() { - let invertedStatus; - switch (this.getStatus()) { - case 'added': - invertedStatus = 'deleted'; - break; - case 'deleted': - invertedStatus = 'added'; - break; - case 'unchanged': - invertedStatus = 'unchanged'; - break; - case 'nonewline': - invertedStatus = 'nonewline'; - break; - } - - return new HunkLine( - this.text, - invertedStatus, - this.newLineNumber, - this.oldLineNumber, - ); - } - - toString() { - return this.getOrigin() + (this.getStatus() === 'nonewline' ? ' ' : '') + this.getText(); - } - - getByteSize() { - return Buffer.byteLength(this.getText(), 'utf8'); - } -} diff --git a/lib/models/hunk.js b/lib/models/hunk.js deleted file mode 100644 index 600b230ae9..0000000000 --- a/lib/models/hunk.js +++ /dev/null @@ -1,83 +0,0 @@ -export default class Hunk { - constructor(oldStartRow, newStartRow, oldRowCount, newRowCount, sectionHeading, lines) { - this.oldStartRow = oldStartRow; - this.newStartRow = newStartRow; - this.oldRowCount = oldRowCount; - this.newRowCount = newRowCount; - this.sectionHeading = sectionHeading; - this.lines = lines; - } - - copy() { - return new Hunk( - this.getOldStartRow(), - this.getNewStartRow(), - this.getOldRowCount(), - this.getNewRowCount(), - this.getSectionHeading(), - this.getLines().map(l => l.copy()), - ); - } - - getOldStartRow() { - return this.oldStartRow; - } - - getNewStartRow() { - return this.newStartRow; - } - - getOldRowCount() { - return this.oldRowCount; - } - - getNewRowCount() { - return this.newRowCount; - } - - getLines() { - return this.lines; - } - - getHeader() { - return `@@ -${this.oldStartRow},${this.oldRowCount} +${this.newStartRow},${this.newRowCount} @@\n`; - } - - getSectionHeading() { - return this.sectionHeading; - } - - invert() { - const invertedLines = []; - let addedLines = []; - for (const line of this.getLines()) { - const invertedLine = line.invert(); - if (invertedLine.getStatus() === 'added') { - addedLines.push(invertedLine); - } else if (invertedLine.getStatus() === 'deleted') { - invertedLines.push(invertedLine); - } else { - invertedLines.push(...addedLines); - invertedLines.push(invertedLine); - addedLines = []; - } - } - invertedLines.push(...addedLines); - return new Hunk( - this.getNewStartRow(), - this.getOldStartRow(), - this.getNewRowCount(), - this.getOldRowCount(), - this.getSectionHeading(), - invertedLines, - ); - } - - toString() { - return this.getLines().reduce((a, b) => a + b.toString() + '\n', this.getHeader()); - } - - getByteSize() { - return this.getLines().reduce((acc, line) => acc + line.getByteSize(), 0); - } -} diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js new file mode 100644 index 0000000000..fd3dc62865 --- /dev/null +++ b/lib/models/patch/hunk.js @@ -0,0 +1,137 @@ +import IndexedRowRange, {nullIndexedRowRange} from '../indexed-row-range'; + +export default class Hunk { + constructor({ + oldStartRow, + newStartRow, + oldRowCount, + newRowCount, + sectionHeading, + rowRange, + additions, + deletions, + noNewline, + }) { + this.oldStartRow = oldStartRow; + this.newStartRow = newStartRow; + this.oldRowCount = oldRowCount; + this.newRowCount = newRowCount; + this.sectionHeading = sectionHeading; + + this.rowRange = rowRange; + this.additions = additions; + this.deletions = deletions; + this.noNewline = noNewline; + } + + getOldStartRow() { + return this.oldStartRow; + } + + getNewStartRow() { + return this.newStartRow; + } + + getOldRowCount() { + return this.oldRowCount; + } + + getNewRowCount() { + return this.newRowCount; + } + + getHeader() { + return `@@ -${this.oldStartRow},${this.oldRowCount} +${this.newStartRow},${this.newRowCount} @@`; + } + + getSectionHeading() { + return this.sectionHeading; + } + + getAdditions() { + return this.additions; + } + + getDeletions() { + return this.deletions; + } + + getNoNewline() { + return this.noNewline; + } + + getStartRange() { + return this.rowRange.getStartRange(); + } + + getBufferRows() { + return this.rowRange.getBufferRows(); + } + + changedLineCount() { + return [ + this.additions, + this.deletions, + [this.noNewline], + ].reduce((count, ranges) => { + return ranges.reduce((subCount, range) => subCount + range.bufferRowCount(), count); + }, 0); + } + + invert() { + return new Hunk({ + oldStartRow: this.getNewStartRow(), + newStartRow: this.getOldStartRow(), + oldRowCount: this.getNewRowCount(), + newRowCount: this.getOldRowCount(), + sectionHeading: this.getSectionHeading(), + rowRange: this.rowRange, + additions: this.getDeletions(), + deletions: this.getAdditions(), + noNewline: this.getNoNewline(), + }); + } + + toStringIn(bufferText) { + let str = this.getHeader() + '\n'; + + let additionIndex = 0; + let deletionIndex = 0; + + const nextRange = () => { + const nextAddition = this.additions[additionIndex] || nullIndexedRowRange; + const nextDeletion = this.deletions[deletionIndex] || nullIndexedRowRange; + + const minRange = [this.noNewline, nextAddition, nextDeletion].reduce((least, range) => { + return range.startOffset < least.startOffset ? range : least; + }); + + const unchanged = minRange.startOffset === currentOffset + ? nullIndexedRowRange + : new IndexedRowRange({ + bufferRange: [[0, 0], [0, 0]], + startOffset: currentOffset, + endOffset: minRange.startOffset, + }); + + if (minRange === nextAddition) { + additionIndex++; + return {origin: '+', range: minRange, unchanged}; + } else if (minRange === nextDeletion) { + deletionIndex++; + return {origin: '-', range: minRange, unchanged}; + } else { + return {origin: '\\', range: this.noNewline, unchanged}; + } + }; + + let currentOffset = this.rowRange.startOffset; + while (currentOffset < bufferText.length) { + const {origin, range, unchanged} = nextRange(); + str += unchanged.toStringIn(bufferText, ' '); + str += range.toStringIn(bufferText, origin); + currentOffset = range.endOffset; + } + return str; + } +} diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js new file mode 100644 index 0000000000..9dade3990f --- /dev/null +++ b/test/models/patch/hunk.test.js @@ -0,0 +1,239 @@ +import Hunk from '../../../lib/models/patch/hunk'; +import IndexedRowRange, {nullIndexedRowRange} from '../../../lib/models/indexed-row-range'; + +describe('Hunk', function() { + const attrs = { + oldStartRow: 0, + newStartRow: 0, + oldRowCount: 0, + newRowCount: 0, + sectionHeading: 'sectionHeading', + rowRange: new IndexedRowRange({ + bufferRange: [[1, 0], [10, 0]], + startOffset: 5, + endOffset: 100, + }), + additions: [ + new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 6, endOffset: 7}), + ], + deletions: [ + new IndexedRowRange({bufferRange: [[3, 0], [4, 0]], startOffset: 8, endOffset: 9}), + new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 10, endOffset: 11}), + ], + noNewline: nullIndexedRowRange, + }; + + it('has some basic accessors', function() { + const h = new Hunk({ + oldStartRow: 0, + newStartRow: 1, + oldRowCount: 2, + newRowCount: 3, + sectionHeading: 'sectionHeading', + rowRange: new IndexedRowRange({ + bufferRange: [[0, 0], [10, 0]], + startOffset: 0, + endOffset: 100, + }), + additions: [ + new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 6, endOffset: 7}), + ], + deletions: [ + new IndexedRowRange({bufferRange: [[3, 0], [4, 0]], startOffset: 8, endOffset: 9}), + new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 10, endOffset: 11}), + ], + noNewline: nullIndexedRowRange, + }); + + assert.strictEqual(h.getOldStartRow(), 0); + assert.strictEqual(h.getNewStartRow(), 1); + assert.strictEqual(h.getOldRowCount(), 2); + assert.strictEqual(h.getNewRowCount(), 3); + assert.strictEqual(h.getSectionHeading(), 'sectionHeading'); + assert.lengthOf(h.getAdditions(), 1); + assert.lengthOf(h.getDeletions(), 2); + assert.isFalse(h.getNoNewline().isPresent()); + }); + + it('creates its start range for decoration placement', function() { + const h = new Hunk({ + ...attrs, + rowRange: new IndexedRowRange({ + bufferRange: [[3, 0], [6, 0]], + startOffset: 15, + endOffset: 35, + }), + }); + + assert.deepEqual(h.getStartRange().serialize(), [[3, 0], [3, 0]]); + }); + + it('generates a patch section header', function() { + const h = new Hunk({ + ...attrs, + oldStartRow: 0, + newStartRow: 1, + oldRowCount: 2, + newRowCount: 3, + }); + + assert.strictEqual(h.getHeader(), '@@ -0,2 +1,3 @@'); + }); + + it('returns a set of covered buffer rows', function() { + const h = new Hunk({ + ...attrs, + rowRange: new IndexedRowRange({ + bufferRange: [[6, 0], [10, 0]], + startOffset: 30, + endOffset: 55, + }), + }); + assert.sameMembers(Array.from(h.getBufferRows()), [6, 7, 8, 9, 10]); + }); + + it('computes the total number of changed lines', function() { + const h0 = new Hunk({ + ...attrs, + additions: [ + new IndexedRowRange({bufferRange: [[2, 0], [4, 0]], startOffset: 0, endOffset: 0}), + new IndexedRowRange({bufferRange: [[6, 0], [6, 0]], startOffset: 0, endOffset: 0}), + ], + deletions: [ + new IndexedRowRange({bufferRange: [[7, 0], [10, 0]], startOffset: 0, endOffset: 0}), + ], + noNewline: new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0}), + }); + assert.strictEqual(h0.changedLineCount(), 9); + + const h1 = new Hunk({ + ...attrs, + additions: [], + deletions: [], + noNewline: nullIndexedRowRange, + }); + assert.strictEqual(h1.changedLineCount(), 0); + }); + + it('computes an inverted hunk', function() { + const original = new Hunk({ + ...attrs, + oldStartRow: 0, + newStartRow: 1, + oldRowCount: 2, + newRowCount: 3, + sectionHeading: 'the-heading', + additions: [ + new IndexedRowRange({bufferRange: [[2, 0], [4, 0]], startOffset: 0, endOffset: 0}), + new IndexedRowRange({bufferRange: [[6, 0], [6, 0]], startOffset: 0, endOffset: 0}), + ], + deletions: [ + new IndexedRowRange({bufferRange: [[7, 0], [10, 0]], startOffset: 0, endOffset: 0}), + ], + noNewline: new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0}), + }); + + const inverted = original.invert(); + assert.strictEqual(inverted.getOldStartRow(), 1); + assert.strictEqual(inverted.getNewStartRow(), 0); + assert.strictEqual(inverted.getOldRowCount(), 3); + assert.strictEqual(inverted.getNewRowCount(), 2); + assert.strictEqual(inverted.getSectionHeading(), 'the-heading'); + assert.lengthOf(inverted.additions, 1); + assert.lengthOf(inverted.deletions, 2); + assert.isTrue(inverted.noNewline.isPresent()); + }); + + describe('toStringIn()', function() { + it('prints its header', function() { + const h = new Hunk({ + ...attrs, + oldStartRow: 0, + newStartRow: 1, + oldRowCount: 2, + newRowCount: 3, + additions: [], + deletions: [], + noNewline: nullIndexedRowRange, + }); + + assert.strictEqual(h.toStringIn(''), '@@ -0,2 +1,3 @@\n'); + }); + + it('renders changed and unchanged lines with the appropriate origin characters', function() { + const buffer = + '0000\n0111\n0222\n0333\n0444\n0555\n0666\n0777\n0888\n0999\n' + + '1000\n1111\n1222\n' + + 'No newline at end of file\n'; + // 0000.0111.0222.0333.0444.0555.0666.0777.0888.0999.1000.1111.1222.No newline at end of file. + + const h = new Hunk({ + ...attrs, + oldStartRow: 1, + newStartRow: 1, + oldRowCount: 6, + newRowCount: 6, + rowRange: new IndexedRowRange({ + bufferRange: [[1, 0], [13, 0]], + startOffset: 5, + endOffset: 91, + }), + additions: [ + new IndexedRowRange({bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20}), + new IndexedRowRange({bufferRange: [[7, 0], [7, 0]], startOffset: 35, endOffset: 40}), + new IndexedRowRange({bufferRange: [[10, 0], [10, 0]], startOffset: 50, endOffset: 55}), + ], + deletions: [ + new IndexedRowRange({bufferRange: [[5, 0], [5, 0]], startOffset: 25, endOffset: 30}), + new IndexedRowRange({bufferRange: [[8, 0], [9, 0]], startOffset: 40, endOffset: 50}), + ], + noNewline: new IndexedRowRange({bufferRange: [[13, 0], [13, 0]], startOffset: 65, endOffset: 91}), + }); + + assert.strictEqual(h.toStringIn(buffer), [ + '@@ -1,6 +1,6 @@\n', + ' 0111\n', + '+0222\n', + '+0333\n', + ' 0444\n', + '-0555\n', + ' 0666\n', + '+0777\n', + '-0888\n', + '-0999\n', + '+1000\n', + ' 1111\n', + ' 1222\n', + '\\No newline at end of file\n', + ].join('')); + }); + + it('renders a hunk without a nonewline', function() { + const buffer = '0000\n1111\n2222\n3333\n'; + + const h = new Hunk({ + ...attrs, + oldStartRow: 1, + newStartRow: 1, + oldRowCount: 1, + newRowCount: 1, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [3, 0]], startOffset: 0, endOffset: 20}), + additions: [ + new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10}), + ], + deletions: [ + new IndexedRowRange({bufferRange: [[2, 0], [2, 0]], startOffset: 10, endOffset: 15}), + ], + noNewline: nullIndexedRowRange, + }); + + assert.strictEqual(h.toStringIn(buffer), [ + '@@ -1,1 +1,1 @@\n', + ' 0000\n', + '+1111\n', + '-2222\n', + ' 3333\n', + ].join('')); + }); + }); +}); From 1dc4fb8dd75754c5267633e36448cbda969be14f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Aug 2018 11:13:02 -0400 Subject: [PATCH 0069/4053] Correctly render hunks early in the buffer without noNewline --- lib/models/patch/hunk.js | 15 ++++++++++++++- test/models/patch/hunk.test.js | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index fd3dc62865..65062534ea 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -98,11 +98,17 @@ export default class Hunk { let additionIndex = 0; let deletionIndex = 0; + const endRange = new IndexedRowRange({ + bufferRange: [[0, 0], [0, 0]], + startOffset: this.rowRange.endOffset, + endOffset: this.rowRange.endOffset, + }); + const nextRange = () => { const nextAddition = this.additions[additionIndex] || nullIndexedRowRange; const nextDeletion = this.deletions[deletionIndex] || nullIndexedRowRange; - const minRange = [this.noNewline, nextAddition, nextDeletion].reduce((least, range) => { + const minRange = [this.noNewline, nextAddition, nextDeletion, endRange].reduce((least, range) => { return range.startOffset < least.startOffset ? range : least; }); @@ -120,6 +126,8 @@ export default class Hunk { } else if (minRange === nextDeletion) { deletionIndex++; return {origin: '-', range: minRange, unchanged}; + } else if (minRange === endRange) { + return {origin: ' ', range: minRange, unchanged}; } else { return {origin: '\\', range: this.noNewline, unchanged}; } @@ -129,6 +137,11 @@ export default class Hunk { while (currentOffset < bufferText.length) { const {origin, range, unchanged} = nextRange(); str += unchanged.toStringIn(bufferText, ' '); + + if (range === endRange) { + break; + } + str += range.toStringIn(bufferText, origin); currentOffset = range.endOffset; } diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 9dade3990f..530ab39c58 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -209,7 +209,7 @@ describe('Hunk', function() { }); it('renders a hunk without a nonewline', function() { - const buffer = '0000\n1111\n2222\n3333\n'; + const buffer = '0000\n1111\n2222\n3333\n4444\n'; const h = new Hunk({ ...attrs, From ee6934188caf7645f15460e85e2a488ca979f0b5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Aug 2018 11:18:49 -0400 Subject: [PATCH 0070/4053] Build a Patch out of Hunks --- lib/models/patch/patch.js | 76 +++++++++++++++++++ test/models/patch/patch.test.js | 129 ++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 lib/models/patch/patch.js create mode 100644 test/models/patch/patch.test.js diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js new file mode 100644 index 0000000000..07ac604109 --- /dev/null +++ b/lib/models/patch/patch.js @@ -0,0 +1,76 @@ +export default class Patch { + constructor({status, hunks, bufferText}) { + this.status = status; + this.hunks = hunks; + this.bufferText = bufferText; + } + + getStatus() { + return this.status; + } + + getHunks() { + return this.hunks; + } + + getBufferText() { + return this.bufferText; + } + + getByteSize() { + return Buffer.byteLength(this.bufferText, 'utf8'); + } + + clone(opts = {}) { + return new this.constructor({ + status: opts.status !== undefined ? opts.status : this.getStatus(), + hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), + bufferText: opts.bufferText !== undefined ? opts.bufferText : this.getBufferText(), + }); + } + + toString() { + return this.getHunks().reduce((str, hunk) => { + str += hunk.toStringIn(this.getBufferText()); + return str; + }, ''); + } + + isPresent() { + return true; + } +} + +export const nullPatch = { + getStatus() { + return null; + }, + + getHunks() { + return []; + }, + + getBufferText() { + return ''; + }, + + getByteSize() { + return 0; + }, + + clone(opts = {}) { + if (opts.status === undefined && opts.hunks === undefined && opts.bufferText === undefined) { + return this; + } else { + return new Patch({ + status: opts.status !== undefined ? opts.status : this.getStatus(), + hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), + bufferText: opts.bufferText !== undefined ? opts.bufferText : this.getBufferText(), + }); + } + }, + + isPresent() { + return false; + }, +}; diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js new file mode 100644 index 0000000000..131ae9faa1 --- /dev/null +++ b/test/models/patch/patch.test.js @@ -0,0 +1,129 @@ +import Patch, {nullPatch} from '../../../lib/models/patch/patch'; +import Hunk from '../../../lib/models/patch/hunk'; +import IndexedRowRange, {nullIndexedRowRange} from '../../../lib/models/indexed-row-range'; + +describe('Patch', function() { + it('has some standard accessors', function() { + const p = new Patch({status: 'modified', hunks: [], bufferText: 'bufferText'}); + assert.strictEqual(p.getStatus(), 'modified'); + assert.deepEqual(p.getHunks(), []); + assert.strictEqual(p.getBufferText(), 'bufferText'); + assert.isTrue(p.isPresent()); + }); + + it('computes the byte size of the total patch data', function() { + const p = new Patch({status: 'modified', hunks: [], bufferText: '\u00bd + \u00bc = \u00be'}); + assert.strictEqual(p.getByteSize(), 12); + }); + + it('clones itself with optionally overridden properties', function() { + const original = new Patch({status: 'modified', hunks: [], bufferText: 'bufferText'}); + + const dup0 = original.clone(); + assert.notStrictEqual(dup0, original); + assert.strictEqual(dup0.getStatus(), 'modified'); + assert.deepEqual(dup0.getHunks(), []); + assert.strictEqual(dup0.getBufferText(), 'bufferText'); + + const dup1 = original.clone({status: 'added'}); + assert.notStrictEqual(dup1, original); + assert.strictEqual(dup1.getStatus(), 'added'); + assert.deepEqual(dup1.getHunks(), []); + assert.strictEqual(dup1.getBufferText(), 'bufferText'); + + const hunks = [new Hunk({})]; + const dup2 = original.clone({hunks}); + assert.notStrictEqual(dup2, original); + assert.strictEqual(dup2.getStatus(), 'modified'); + assert.deepEqual(dup2.getHunks(), hunks); + assert.strictEqual(dup2.getBufferText(), 'bufferText'); + + const dup3 = original.clone({bufferText: 'changed'}); + assert.notStrictEqual(dup3, original); + assert.strictEqual(dup3.getStatus(), 'modified'); + assert.deepEqual(dup3.getHunks(), []); + assert.strictEqual(dup3.getBufferText(), 'changed'); + }); + + it('clones a nullPatch as a nullPatch', function() { + assert.strictEqual(nullPatch, nullPatch.clone()); + }); + + it('clones a nullPatch to a real Patch if properties are provided', function() { + const dup0 = nullPatch.clone({status: 'added'}); + assert.notStrictEqual(dup0, nullPatch); + assert.strictEqual(dup0.getStatus(), 'added'); + assert.deepEqual(dup0.getHunks(), []); + assert.strictEqual(dup0.getBufferText(), ''); + + const hunks = [new Hunk({})]; + const dup1 = nullPatch.clone({hunks}); + assert.notStrictEqual(dup1, nullPatch); + assert.isNull(dup1.getStatus()); + assert.deepEqual(dup1.getHunks(), hunks); + assert.strictEqual(dup1.getBufferText(), ''); + + const dup2 = nullPatch.clone({bufferText: 'changed'}); + assert.notStrictEqual(dup2, nullPatch); + assert.isNull(dup2.getStatus()); + assert.deepEqual(dup2.getHunks(), []); + assert.strictEqual(dup2.getBufferText(), 'changed'); + }); + + it('prints itself as an apply-ready string', function() { + const bufferText = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999\n'; + // old: 0000.2222.3333.4444.5555.6666.7777.8888.9999. + // new: 0000.1111.2222.3333.4444.5555.6666.9999. + // 0000.1111.2222.3333.4444.5555.6666.7777.8888.9999. + + const hunk0 = new Hunk({ + oldStartRow: 0, + newStartRow: 0, + oldRowCount: 2, + newRowCount: 3, + sectionHeading: 'zero', + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + additions: [ + new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10}), + ], + deletions: [], + noNewline: nullIndexedRowRange, + }); + + const hunk1 = new Hunk({ + oldStartRow: 5, + newStartRow: 6, + oldRowCount: 4, + newRowCount: 2, + sectionHeading: 'one', + rowRange: new IndexedRowRange({bufferRange: [[6, 0], [10, 0]], startOffset: 30, endOffset: 55}), + additions: [], + deletions: [ + new IndexedRowRange({bufferRange: [[7, 0], [8, 0]], startOffset: 35, endOffset: 45}), + ], + noNewline: nullIndexedRowRange, + }); + + const p = new Patch({status: 'modified', hunks: [hunk0, hunk1], bufferText}); + + assert.strictEqual(p.toString(), [ + '@@ -0,2 +0,3 @@\n', + ' 0000\n', + '+1111\n', + ' 2222\n', + '@@ -5,4 +6,2 @@\n', + ' 6666\n', + '-7777\n', + '-8888\n', + ' 9999\n', + ].join('')); + }); + + it('has a stubbed nullPatch counterpart', function() { + assert.isNull(nullPatch.getStatus()); + assert.deepEqual(nullPatch.getHunks(), []); + assert.strictEqual(nullPatch.getBufferText(), ''); + assert.strictEqual(nullPatch.getByteSize(), 0); + assert.isFalse(nullPatch.isPresent()); + }); +}); From 0161350d747696e27dc42f5cb96a795f7ed92ddc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Aug 2018 11:21:09 -0400 Subject: [PATCH 0071/4053] File model, now with tests --- lib/models/patch/file.js | 80 ++++++++++++++++++++++++++++++++++ test/models/patch/file.test.js | 73 +++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 lib/models/patch/file.js create mode 100644 test/models/patch/file.test.js diff --git a/lib/models/patch/file.js b/lib/models/patch/file.js new file mode 100644 index 0000000000..a3eda44239 --- /dev/null +++ b/lib/models/patch/file.js @@ -0,0 +1,80 @@ +export default class File { + constructor({path, mode, symlink}) { + this.path = path; + this.mode = mode; + this.symlink = symlink; + } + + getPath() { + return this.path; + } + + getMode() { + return this.mode; + } + + getSymlink() { + return this.symlink; + } + + isSymlink() { + return this.getMode() === '120000'; + } + + isRegularFile() { + return this.getMode() === '100644' || this.getMode() === '100755'; + } + + isPresent() { + return true; + } + + clone(opts = {}) { + return new File({ + path: opts.path !== undefined ? opts.path : this.path, + mode: opts.mode !== undefined ? opts.mode : this.mode, + symlink: opts.symlink !== undefined ? opts.symlink : this.symlink, + }); + } +} + +export const nullFile = { + getPath() { + /* istanbul ignore next */ + return null; + }, + + getMode() { + /* istanbul ignore next */ + return null; + }, + + getSymlink() { + /* istanbul ignore next */ + return null; + }, + + isSymlink() { + return false; + }, + + isRegularFile() { + return false; + }, + + isPresent() { + return false; + }, + + clone(opts = {}) { + if (opts.path === undefined && opts.mode === undefined && opts.symlink === undefined) { + return this; + } else { + return new File({ + path: opts.path !== undefined ? opts.path : this.getPath(), + mode: opts.mode !== undefined ? opts.mode : this.getMode(), + symlink: opts.symlink !== undefined ? opts.symlink : this.getSymlink(), + }); + } + }, +}; diff --git a/test/models/patch/file.test.js b/test/models/patch/file.test.js new file mode 100644 index 0000000000..38bb449e78 --- /dev/null +++ b/test/models/patch/file.test.js @@ -0,0 +1,73 @@ +import File, {nullFile} from '../../../lib/models/patch/file'; + +describe('File', function() { + it("detects when it's a symlink", function() { + assert.isTrue(new File({path: 'path', mode: '120000', symlink: null}).isSymlink()); + assert.isFalse(new File({path: 'path', mode: '100644', symlink: null}).isSymlink()); + assert.isFalse(nullFile.isSymlink()); + }); + + it("detects when it's a regular file", function() { + assert.isTrue(new File({path: 'path', mode: '100644', symlink: null}).isRegularFile()); + assert.isTrue(new File({path: 'path', mode: '100755', symlink: null}).isRegularFile()); + assert.isFalse(new File({path: 'path', mode: '120000', symlink: null}).isRegularFile()); + assert.isFalse(nullFile.isRegularFile()); + }); + + it('clones itself with possible overrides', function() { + const original = new File({path: 'original', mode: '100644', symlink: null}); + + const dup0 = original.clone(); + assert.notStrictEqual(original, dup0); + assert.strictEqual(dup0.getPath(), 'original'); + assert.strictEqual(dup0.getMode(), '100644'); + assert.isNull(dup0.getSymlink()); + + const dup1 = original.clone({path: 'replaced'}); + assert.notStrictEqual(original, dup1); + assert.strictEqual(dup1.getPath(), 'replaced'); + assert.strictEqual(dup1.getMode(), '100644'); + assert.isNull(dup1.getSymlink()); + + const dup2 = original.clone({mode: '100755'}); + assert.notStrictEqual(original, dup2); + assert.strictEqual(dup2.getPath(), 'original'); + assert.strictEqual(dup2.getMode(), '100755'); + assert.isNull(dup2.getSymlink()); + + const dup3 = original.clone({mode: '120000', symlink: 'destination'}); + assert.notStrictEqual(original, dup3); + assert.strictEqual(dup3.getPath(), 'original'); + assert.strictEqual(dup3.getMode(), '120000'); + assert.strictEqual(dup3.getSymlink(), 'destination'); + }); + + it('clones the null file as itself', function() { + const dup = nullFile.clone(); + assert.strictEqual(dup, nullFile); + assert.isFalse(dup.isPresent()); + }); + + it('clones the null file with new properties', function() { + const dup0 = nullFile.clone({path: 'replaced'}); + assert.notStrictEqual(nullFile, dup0); + assert.strictEqual(dup0.getPath(), 'replaced'); + assert.isNull(dup0.getMode()); + assert.isNull(dup0.getSymlink()); + assert.isTrue(dup0.isPresent()); + + const dup1 = nullFile.clone({mode: '120000'}); + assert.notStrictEqual(nullFile, dup1); + assert.isNull(dup1.getPath()); + assert.strictEqual(dup1.getMode(), '120000'); + assert.isNull(dup1.getSymlink()); + assert.isTrue(dup1.isPresent()); + + const dup2 = nullFile.clone({symlink: 'target'}); + assert.notStrictEqual(nullFile, dup2); + assert.isNull(dup2.getPath()); + assert.isNull(dup2.getMode()); + assert.strictEqual(dup2.getSymlink(), 'target'); + assert.isTrue(dup2.isPresent()); + }); +}); From 584fffc21d14ad4a78cb0948cac711ef9dfd3012 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 2 Aug 2018 11:32:45 -0400 Subject: [PATCH 0072/4053] Revisit FilePatch builder with model changes --- lib/models/patch/builder.js | 23 +++++++++-------- test/models/patch/builder.test.js | 42 +++++++++++++------------------ 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 2f5b5cd34b..eec19f6609 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -102,6 +102,7 @@ function buildHunks(diff) { for (const hunkData of diff.hunks) { const bufferStartRow = bufferRow; const bufferStartOffset = bufferOffset; + const additions = []; const deletions = []; const noNewlines = []; @@ -109,15 +110,15 @@ function buildHunks(diff) { let lastStatus = null; let currentRangeStart = bufferRow; - const finishCurrentChange = () => { - const changes = { + const finishCurrentRange = () => { + const ranges = { added: additions, deleted: deletions, nonewline: noNewlines, }[lastStatus]; - if (changes !== undefined) { - changes.push(new IndexedRowRange({ - bufferRange: Range.fromObject([[currentRangeStart, 0], [bufferRow - 1, 0]]), + if (ranges !== undefined) { + ranges.push(new IndexedRowRange({ + bufferRange: [[currentRangeStart, 0], [bufferRow - 1, 0]], startOffset, endOffset: bufferOffset, })); @@ -136,14 +137,14 @@ function buildHunks(diff) { } if (status !== lastStatus && lastStatus !== null) { - finishCurrentChange(); + finishCurrentRange(); } lastStatus = status; bufferOffset += bufferLine.length; bufferRow++; } - finishCurrentChange(); + finishCurrentRange(); let noNewline = nullIndexedRowRange; if (noNewlines.length === 1) { @@ -158,9 +159,11 @@ function buildHunks(diff) { oldRowCount: hunkData.oldLineCount, newRowCount: hunkData.newLineCount, sectionHeading: hunkData.heading, - bufferStartPosition: null, // fromBufferPosition([bufferStartRow, 0]), - bufferStartOffset, - bufferEndRow: bufferRow, + rowRange: new IndexedRowRange({ + bufferRange: [[bufferStartRow, 0], [bufferRow, 0]], + startOffset: bufferStartOffset, + endOffset: bufferOffset, + }), additions, deletions, noNewline, diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 0c8628b71c..80fe4d494d 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -5,7 +5,7 @@ describe('buildFilePatch', function() { function assertHunkChanges(changes, expectedStrings, expectedRanges) { const actualStrings = changes.map(change => change.toStringIn(buffer, '*')); - const actualRanges = changes.map(change => change.position.serialize()); + const actualRanges = changes.map(change => change.bufferRange.serialize()); assert.deepEqual( {strings: actualStrings, ranges: actualRanges}, @@ -13,9 +13,8 @@ describe('buildFilePatch', function() { ); } - function assertHunk(hunk, {startPosition, startOffset, header, deletions, additions, noNewline}) { - assert.deepEqual(hunk.getBufferStartPosition().serialize(), startPosition); - assert.strictEqual(hunk.getBufferStartOffset(), startOffset); + function assertHunk(hunk, {startRow, header, deletions, additions, noNewline}) { + assert.deepEqual(hunk.getStartRange().serialize(), [[startRow, 0], [startRow, 0]]); assert.strictEqual(hunk.getHeader(), header); assertHunkChanges(hunk.getDeletions(), deletions.strings, deletions.ranges); @@ -104,9 +103,8 @@ describe('buildFilePatch', function() { assert.lengthOf(p.getHunks(), 3); assertHunk(p.getHunks()[0], { - startPosition: [[0, 0], [0, 0]], - startOffset: 0, - header: '@@ -0,7 +0,6 @@\n', + startRow: 0, + header: '@@ -0,7 +0,6 @@', deletions: { strings: ['*line-1\n*line-2\n*line-3\n'], ranges: [[[1, 0], [3, 0]]], @@ -118,9 +116,8 @@ describe('buildFilePatch', function() { }); assertHunk(p.getHunks()[1], { - startPosition: [[9, 0], [9, 0]], - startOffset: 63, - header: '@@ -10,3 +11,3 @@\n', + startRow: 9, + header: '@@ -10,3 +11,3 @@', deletions: { strings: ['*line-9\n'], ranges: [[[9, 0], [9, 0]]], @@ -132,9 +129,8 @@ describe('buildFilePatch', function() { }); assertHunk(p.getHunks()[2], { - startPosition: [[13, 0], [13, 0]], - startOffset: 94, - header: '@@ -20,4 +21,4 @@\n', + startRow: 13, + header: '@@ -20,4 +21,4 @@', deletions: { strings: ['*line-14\n*line-15\n'], ranges: [[[14, 0], [15, 0]]], @@ -246,9 +242,8 @@ describe('buildFilePatch', function() { assert.lengthOf(p.getHunks(), 1); assertHunk(p.getHunks()[0], { - startPosition: [[0, 0], [0, 0]], - startOffset: 0, - header: '@@ -0,1 +0,1 @@\n', + startRow: 0, + header: '@@ -0,1 +0,1 @@', additions: {strings: ['*line-0\n'], ranges: [[[0, 0], [0, 0]]]}, deletions: {strings: ['*line-1\n'], ranges: [[[1, 0], [1, 0]]]}, noNewline: {string: '*No newline at end of file\n', range: [[2, 0], [2, 0]]}, @@ -320,9 +315,8 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getBufferText(), buffer); assert.lengthOf(p.getHunks(), 1); assertHunk(p.getHunks()[0], { - startPosition: [[0, 0], [0, 0]], - startOffset: 0, - header: '@@ -0,0 +0,2 @@\n', + startRow: 0, + header: '@@ -0,0 +0,2 @@', deletions: {strings: [], ranges: []}, additions: { strings: ['*line-0\n*line-1\n'], @@ -379,9 +373,8 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getBufferText(), buffer); assert.lengthOf(p.getHunks(), 1); assertHunk(p.getHunks()[0], { - startPosition: [[0, 0], [0, 0]], - startOffset: 0, - header: '@@ -0,2 +0,0 @@\n', + startRow: 0, + header: '@@ -0,2 +0,0 @@', deletions: { strings: ['*line-0\n*line-1\n'], ranges: [[[0, 0], [1, 0]]], @@ -437,9 +430,8 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getBufferText(), buffer); assert.lengthOf(p.getHunks(), 1); assertHunk(p.getHunks()[0], { - startPosition: [[0, 0], [0, 0]], - startOffset: 0, - header: '@@ -0,0 +0,2 @@\n', + startRow: 0, + header: '@@ -0,0 +0,2 @@', deletions: {strings: [], ranges: []}, additions: { strings: ['*line-0\n*line-1\n'], From f53cf773e33ccf5282f103c280daf09e446479ce Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 3 Aug 2018 12:52:44 -0400 Subject: [PATCH 0073/4053] Refactor assertHunkChanges and assertHunk into test helpers --- test/helpers.js | 53 +++++++++++++ test/models/patch/builder.test.js | 125 +++++++++++------------------- 2 files changed, 98 insertions(+), 80 deletions(-) diff --git a/test/helpers.js b/test/helpers.js index afff422aa5..5f49a73076 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -123,6 +123,8 @@ export function buildRepositoryWithPipeline(workingDirPath, options) { return buildRepository(workingDirPath, {pipelineManager}); } +// Custom assertions + export function assertDeepPropertyVals(actual, expected) { function extractObjectSubset(actualValue, expectedValue) { if (actualValue !== Object(actualValue)) { return actualValue; } @@ -150,6 +152,57 @@ export function assertEqualSortedArraysByKey(arr1, arr2, key) { assert.deepEqual(arr1.sort(sortFn), arr2.sort(sortFn)); } +// Helpers for test/models/patch classes + +class PatchBufferAssertions { + constructor(patch) { + this.patch = patch; + } + + hunkChanges(changes, expectedStrings, expectedRanges) { + const actualStrings = changes.map(change => change.toStringIn(this.patch.getBufferText(), '*')); + const actualRanges = changes.map(change => change.bufferRange.serialize()); + + assert.deepEqual( + {strings: actualStrings, ranges: actualRanges}, + {strings: expectedStrings, ranges: expectedRanges}, + ); + } + + hunk(hunkIndex, {startRow, header, deletions, additions, noNewline}) { + const hunk = this.patch.getHunks()[hunkIndex]; + assert.isDefined(hunk); + + assert.deepEqual(hunk.getStartRange().serialize(), [[startRow, 0], [startRow, 0]]); + assert.strictEqual(hunk.getHeader(), header); + + this.hunkChanges(hunk.getDeletions(), deletions.strings, deletions.ranges); + this.hunkChanges(hunk.getAdditions(), additions.strings, additions.ranges); + + const noNewlineChange = hunk.getNoNewline(); + if (noNewlineChange.isPresent()) { + this.hunkChanges([noNewlineChange], [noNewline.string], [noNewline.range]); + } else { + assert.isUndefined(noNewline); + } + } + + hunks(...specs) { + assert.lengthOf(this.patch.getHunks(), specs.length); + for (let i = 0; i < specs.length; i++) { + this.hunk(i, specs[i]); + } + } +} + +export function assertInPatch(patch) { + return new PatchBufferAssertions(patch); +} + +export function assertInFilePatch(filePatch) { + return assertInPatch(filePatch.getPatch()); +} + let activeRenderers = []; export function createRenderer() { let instance; diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 80fe4d494d..a77bbe351b 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -1,33 +1,7 @@ import {buildFilePatch} from '../../../lib/models/patch'; +import {assertInPatch} from '../../helpers'; describe('buildFilePatch', function() { - let buffer; - - function assertHunkChanges(changes, expectedStrings, expectedRanges) { - const actualStrings = changes.map(change => change.toStringIn(buffer, '*')); - const actualRanges = changes.map(change => change.bufferRange.serialize()); - - assert.deepEqual( - {strings: actualStrings, ranges: actualRanges}, - {strings: expectedStrings, ranges: expectedRanges}, - ); - } - - function assertHunk(hunk, {startRow, header, deletions, additions, noNewline}) { - assert.deepEqual(hunk.getStartRange().serialize(), [[startRow, 0], [startRow, 0]]); - assert.strictEqual(hunk.getHeader(), header); - - assertHunkChanges(hunk.getDeletions(), deletions.strings, deletions.ranges); - assertHunkChanges(hunk.getAdditions(), additions.strings, additions.ranges); - - const noNewlineChange = hunk.getNoNewline(); - if (noNewlineChange.isPresent()) { - assertHunkChanges([noNewlineChange], [noNewline.string], [noNewline.range]); - } else { - assert.isUndefined(noNewline); - } - } - it('returns a null patch for an empty diff list', function() { const p = buildFilePatch([]); assert.isFalse(p.getOldFile().isPresent()); @@ -96,50 +70,49 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getNewMode(), '100755'); assert.strictEqual(p.getPatch().getStatus(), 'modified'); - buffer = + const buffer = 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\nline-6\nline-7\nline-8\nline-9\nline-10\n' + 'line-11\nline-12\nline-13\nline-14\nline-15\nline-16\nline-17\nline-18\n'; assert.strictEqual(p.getBufferText(), buffer); - assert.lengthOf(p.getHunks(), 3); - assertHunk(p.getHunks()[0], { - startRow: 0, - header: '@@ -0,7 +0,6 @@', - deletions: { - strings: ['*line-1\n*line-2\n*line-3\n'], - ranges: [[[1, 0], [3, 0]]], - }, - additions: { - strings: ['*line-5\n*line-6\n'], - ranges: [[[5, 0], [6, 0]]], - }, - }); - - assertHunk(p.getHunks()[1], { - startRow: 9, - header: '@@ -10,3 +11,3 @@', - deletions: { - strings: ['*line-9\n'], - ranges: [[[9, 0], [9, 0]]], - }, - additions: { - strings: ['*line-12\n'], - ranges: [[[12, 0], [12, 0]]], + assertInPatch(p).hunks( + { + startRow: 0, + header: '@@ -0,7 +0,6 @@', + deletions: { + strings: ['*line-1\n*line-2\n*line-3\n'], + ranges: [[[1, 0], [3, 0]]], + }, + additions: { + strings: ['*line-5\n*line-6\n'], + ranges: [[[5, 0], [6, 0]]], + }, }, - }); - - assertHunk(p.getHunks()[2], { - startRow: 13, - header: '@@ -20,4 +21,4 @@', - deletions: { - strings: ['*line-14\n*line-15\n'], - ranges: [[[14, 0], [15, 0]]], + { + startRow: 9, + header: '@@ -10,3 +11,3 @@', + deletions: { + strings: ['*line-9\n'], + ranges: [[[9, 0], [9, 0]]], + }, + additions: { + strings: ['*line-12\n'], + ranges: [[[12, 0], [12, 0]]], + }, }, - additions: { - strings: ['*line-16\n*line-17\n'], - ranges: [[[16, 0], [17, 0]]], + { + startRow: 13, + header: '@@ -20,4 +21,4 @@', + deletions: { + strings: ['*line-14\n*line-15\n'], + ranges: [[[14, 0], [15, 0]]], + }, + additions: { + strings: ['*line-16\n*line-17\n'], + ranges: [[[16, 0], [17, 0]]], + }, }, - }); + ); }); it("sets the old file's symlink destination", function() { @@ -237,11 +210,9 @@ describe('buildFilePatch', function() { ]}], }]); - buffer = 'line-0\nline-1\nNo newline at end of file\n'; - assert.strictEqual(p.getBufferText(), buffer); + assert.strictEqual(p.getBufferText(), 'line-0\nline-1\nNo newline at end of file\n'); - assert.lengthOf(p.getHunks(), 1); - assertHunk(p.getHunks()[0], { + assertInPatch(p).hunks({ startRow: 0, header: '@@ -0,1 +0,1 @@', additions: {strings: ['*line-0\n'], ranges: [[[0, 0], [0, 0]]]}, @@ -311,10 +282,8 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getNewSymlink(), 'the-destination'); assert.strictEqual(p.getStatus(), 'deleted'); - buffer = 'line-0\nline-1\n'; - assert.strictEqual(p.getBufferText(), buffer); - assert.lengthOf(p.getHunks(), 1); - assertHunk(p.getHunks()[0], { + assert.strictEqual(p.getBufferText(), 'line-0\nline-1\n'); + assertInPatch(p).hunks({ startRow: 0, header: '@@ -0,0 +0,2 @@', deletions: {strings: [], ranges: []}, @@ -369,10 +338,8 @@ describe('buildFilePatch', function() { assert.isNull(p.getNewSymlink()); assert.strictEqual(p.getStatus(), 'added'); - buffer = 'line-0\nline-1\n'; - assert.strictEqual(p.getBufferText(), buffer); - assert.lengthOf(p.getHunks(), 1); - assertHunk(p.getHunks()[0], { + assert.strictEqual(p.getBufferText(), 'line-0\nline-1\n'); + assertInPatch(p).hunks({ startRow: 0, header: '@@ -0,2 +0,0 @@', deletions: { @@ -426,10 +393,8 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getNewSymlink(), 'the-destination'); assert.strictEqual(p.getStatus(), 'deleted'); - buffer = 'line-0\nline-1\n'; - assert.strictEqual(p.getBufferText(), buffer); - assert.lengthOf(p.getHunks(), 1); - assertHunk(p.getHunks()[0], { + assert.strictEqual(p.getBufferText(), 'line-0\nline-1\n'); + assertInPatch(p).hunks({ startRow: 0, header: '@@ -0,0 +0,2 @@', deletions: {strings: [], ranges: []}, From 882925aeb46d0d77bb3d2284994b49352dc68f1a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 6 Aug 2018 10:12:52 -0400 Subject: [PATCH 0074/4053] Accessor for a hunk's IndexedRowRange --- lib/models/patch/hunk.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 65062534ea..cce3460957 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -60,6 +60,10 @@ export default class Hunk { return this.noNewline; } + getRowRange() { + return this.rowRange; + } + getStartRange() { return this.rowRange.getStartRange(); } From 48d467e0f7bb51b7c25825afe8e3293d0db994aa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 6 Aug 2018 10:13:20 -0400 Subject: [PATCH 0075/4053] Default "deletions" and "additions" in assertInPatch().hunk() --- test/helpers.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/helpers.js b/test/helpers.js index 5f49a73076..88b96a2b4e 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -173,6 +173,9 @@ class PatchBufferAssertions { const hunk = this.patch.getHunks()[hunkIndex]; assert.isDefined(hunk); + deletions = deletions !== undefined ? deletions : {strings: [], ranges: []}; + additions = additions !== undefined ? additions : {strings: [], ranges: []}; + assert.deepEqual(hunk.getStartRange().serialize(), [[startRow, 0], [startRow, 0]]); assert.strictEqual(hunk.getHeader(), header); From fd05d2716d8d7f685d4042c0af588f03cf4ef92f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 6 Aug 2018 10:13:51 -0400 Subject: [PATCH 0076/4053] Move and start work on FilePatch model --- lib/models/{ => patch}/file-patch.js | 280 ++++++++++++--------------- 1 file changed, 125 insertions(+), 155 deletions(-) rename lib/models/{ => patch}/file-patch.js (52%) diff --git a/lib/models/file-patch.js b/lib/models/patch/file-patch.js similarity index 52% rename from lib/models/file-patch.js rename to lib/models/patch/file-patch.js index 41a6976bf8..b4faebb93e 100644 --- a/lib/models/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -1,92 +1,15 @@ import Hunk from './hunk'; -import {toGitPathSep} from '../helpers'; -import PresentedFilePatch from './presented-file-patch'; - -class File { - static empty() { - return new File({path: null, mode: null, symlink: null}); - } - - constructor({path, mode, symlink}) { - this.path = path; - this.mode = mode; - this.symlink = symlink; - } - - getPath() { - return this.path; - } - - getMode() { - return this.mode; - } - - isSymlink() { - return this.getMode() === '120000'; - } - - isRegularFile() { - return this.getMode() === '100644' || this.getMode() === '100755'; - } - - getSymlink() { - return this.symlink; - } - - clone(opts = {}) { - return new File({ - path: opts.path !== undefined ? opts.path : this.path, - mode: opts.mode !== undefined ? opts.mode : this.mode, - symlink: opts.symlink !== undefined ? opts.symlink : this.symlink, - }); - } -} - -class Patch { - constructor({status, hunks}) { - this.status = status; - this.hunks = hunks; - } - - getStatus() { - return this.status; - } - - getHunks() { - return this.hunks; - } - - getByteSize() { - return this.getHunks().reduce((acc, hunk) => acc + hunk.getByteSize(), 0); - } - - clone(opts = {}) { - return new Patch({ - status: opts.status !== undefined ? opts.status : this.status, - hunks: opts.hunks !== undefined ? opts.hunks : this.hunks, - }); - } -} +import File, {nullFile} from './file'; +import Patch from './patch'; +import {toGitPathSep} from '../../helpers'; export default class FilePatch { - static File = File; - static Patch = Patch; - constructor(oldFile, newFile, patch) { this.oldFile = oldFile; this.newFile = newFile; this.patch = patch; - this.changedLineCount = this.getHunks().reduce((acc, hunk) => { - return acc + hunk.getLines().filter(line => line.isChanged()).length; - }, 0); - } - - clone(opts = {}) { - const oldFile = opts.oldFile !== undefined ? opts.oldFile : this.getOldFile(); - const newFile = opts.newFile !== undefined ? opts.newFile : this.getNewFile(); - const patch = opts.patch !== undefined ? opts.patch : this.patch; - return new FilePatch(oldFile, newFile, patch); + this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); } getOldFile() { @@ -129,6 +52,38 @@ export default class FilePatch { return this.getPatch().getByteSize(); } + getBufferText() { + return this.getPatch().getBufferText(); + } + + getHunkStartPositions() { + return this.getHunks().map(hunk => hunk.getBufferStartPosition()); + } + + getAddedBufferPositions() { + return this.getHunks().reduce((acc, hunk) => { + acc.push(...hunk.getAddedBufferPositions()); + return acc; + }, []); + } + + getBufferDeletedPositions() { + return this.getHunks().reduce((acc, hunk) => { + acc.push(...hunk.getDeletedBufferPositions()); + return acc; + }, []); + } + + getBufferNoNewlinePosition() { + return this.getHunks().reduce((acc, hunk) => { + const position = hunk.getBufferNoNewlinePosition(); + if (position.isPresent()) { + acc.push(position); + } + return acc; + }, []); + } + didChangeExecutableMode() { const oldMode = this.getOldMode(); const newMode = this.getNewMode(); @@ -166,24 +121,32 @@ export default class FilePatch { return this.getPatch().getHunks(); } + clone(opts) { + return new this.constructor( + opts.oldFile !== undefined ? opts.oldFile : this.oldFile, + opts.newFile !== undefined ? opts.newFile : this.newFile, + opts.patch !== undefined ? opts.patch : this.patch, + ); + } + getStagePatchForHunk(selectedHunk) { - return this.getStagePatchForLines(new Set(selectedHunk.getLines())); + return this.getStagePatchForLines(new Set(selectedHunk.getBufferRows())); } - getStagePatchForLines(selectedLines) { - const wholeFileSelected = this.changedLineCount === [...selectedLines].filter(line => line.isChanged()).length; + getStagePatchForLines(selectedLineSet) { + const wholeFileSelected = this.changedLineCount === selectedLineSet.size; if (wholeFileSelected) { if (this.hasTypechange() && this.getStatus() === 'deleted') { // handle special case when symlink is created where a file was deleted. In order to stage the file deletion, // we must ensure that the created file patch has no new file return this.clone({ - newFile: File.empty(), + newFile: nullFile, }); } else { return this; } } else { - const hunks = this.getStagePatchHunks(selectedLines); + const hunks = this.getStagePatchHunks(selectedLineSet); if (this.getStatus() === 'deleted') { // Set status to modified return this.clone({ @@ -198,56 +161,59 @@ export default class FilePatch { } } - getStagePatchHunks(selectedLines) { + getStagePatchHunks(selectedLineSet) { let delta = 0; const hunks = []; for (const hunk of this.getHunks()) { - const newStartRow = (hunk.getNewStartRow() || 1) + delta; - let newLineNumber = newStartRow; - const lines = []; - let hunkContainsSelectedLines = false; - for (const line of hunk.getLines()) { - if (line.getStatus() === 'nonewline') { - lines.push(line.copy({oldLineNumber: -1, newLineNumber: -1})); - } else if (selectedLines.has(line)) { - hunkContainsSelectedLines = true; - if (line.getStatus() === 'deleted') { - lines.push(line.copy()); - } else { - lines.push(line.copy({newLineNumber: newLineNumber++})); - } - } else if (line.getStatus() === 'deleted') { - lines.push(line.copy({newLineNumber: newLineNumber++, status: 'unchanged'})); - } else if (line.getStatus() === 'unchanged') { - lines.push(line.copy({newLineNumber: newLineNumber++})); + const additions = []; + const deletions = []; + let deletedRowCount = 0; + + for (const change of hunk.getAdditions()) { + additions.push(...change.intersectRowsIn(selectedLineSet, this.getBufferText())); + } + + for (const change of hunk.getDeletions()) { + for (const intersection of change.intersectRowsIn(selectedLineSet, this.getBufferText())) { + deletedRowCount += intersection.bufferRowCount(); + deletions.push(intersection); } } - const newRowCount = newLineNumber - newStartRow; - if (hunkContainsSelectedLines) { - // eslint-disable-next-line max-len - hunks.push(new Hunk(hunk.getOldStartRow(), newStartRow, hunk.getOldRowCount(), newRowCount, hunk.getSectionHeading(), lines)); + + if ( + additions.length > 0 || + deletions.length > 0 + ) { + // Hunk contains at least one selected line + hunks.push(new Hunk({ + oldStartRow: hunk.getOldStartRow(), + newStartRow: hunk.getNewStartRow() - delta, + oldRowCount: hunk.getOldRowCount(), + newRowCount: hunk.getNewRowCount() - deletedRowCount, + sectionHeading: hunk.getSectionHeading(), + rowRange: hunk.getRowRange(), + additions, + deletions, + noNewline: hunk.getNoNewline(), + })); } - delta += newRowCount - hunk.getNewRowCount(); + delta += deletedRowCount; } return hunks; } getUnstagePatch() { - let invertedStatus; - switch (this.getStatus()) { - case 'modified': - invertedStatus = 'modified'; - break; - case 'added': - invertedStatus = 'deleted'; - break; - case 'deleted': - invertedStatus = 'added'; - break; - default: - // throw new Error(`Unknown Status: ${this.getStatus()}`); + const invertedStatus = { + modified: 'modified', + added: 'deleted', + deleted: 'added', + }[this.getStatus()]; + if (!invertedStatus) { + throw new Error(`Unknown Status: ${this.getStatus()}`); } + const invertedHunks = this.getHunks().map(h => h.invert()); + return this.clone({ oldFile: this.getNewFile(), newFile: this.getOldFile(), @@ -259,11 +225,11 @@ export default class FilePatch { } getUnstagePatchForHunk(hunk) { - return this.getUnstagePatchForLines(new Set(hunk.getLines())); + return this.getUnstagePatchForLines(new Set(hunk.getBufferRows())); } - getUnstagePatchForLines(selectedLines) { - if (this.changedLineCount === [...selectedLines].filter(line => line.isChanged()).length) { + getUnstagePatchForLines(selectedRowSet) { + if (this.changedLineCount === selectedRowSet.size) { if (this.hasTypechange() && this.getStatus() === 'added') { // handle special case when a file was created after a symlink was deleted. // In order to unstage the file creation, we must ensure that the unstage patch has no new file, @@ -276,7 +242,7 @@ export default class FilePatch { } } - const hunks = this.getUnstagePatchHunks(selectedLines); + const hunks = this.getUnstagePatchHunks(selectedRowSet); if (this.getStatus() === 'added') { return this.clone({ oldFile: this.getNewFile(), @@ -289,44 +255,48 @@ export default class FilePatch { } } - getUnstagePatchHunks(selectedLines) { + getUnstagePatchHunks(selectedRowSet) { let delta = 0; const hunks = []; for (const hunk of this.getHunks()) { const oldStartRow = (hunk.getOldStartRow() || 1) + delta; - let oldLineNumber = oldStartRow; - const lines = []; - let hunkContainsSelectedLines = false; - for (const line of hunk.getLines()) { - if (line.getStatus() === 'nonewline') { - lines.push(line.copy({oldLineNumber: -1, newLineNumber: -1})); - } else if (selectedLines.has(line)) { - hunkContainsSelectedLines = true; - if (line.getStatus() === 'added') { - lines.push(line.copy()); - } else { - lines.push(line.copy({oldLineNumber: oldLineNumber++})); - } - } else if (line.getStatus() === 'added') { - lines.push(line.copy({oldLineNumber: oldLineNumber++, status: 'unchanged'})); - } else if (line.getStatus() === 'unchanged') { - lines.push(line.copy({oldLineNumber: oldLineNumber++})); + + const additions = []; + const deletions = []; + let addedRowCount = 0; + + for (const change of hunk.getAdditions()) { + for (const intersection of change.intersectRowsIn(selectedRowSet, this.getBufferText())) { + addedRowCount += intersection.bufferRowCount(); + additions.push(intersection); } } - const oldRowCount = oldLineNumber - oldStartRow; - if (hunkContainsSelectedLines) { - // eslint-disable-next-line max-len - hunks.push(new Hunk(oldStartRow, hunk.getNewStartRow(), oldRowCount, hunk.getNewRowCount(), hunk.getSectionHeading(), lines)); + + for (const change of hunk.getBufferDeletedPositions()) { + deletions.push(...change.intersectRowIn(selectedRowSet, this.getBufferText())); } - delta += oldRowCount - hunk.getOldRowCount(); + + if (additions.length > 0 || deletions.length > 0) { + // Hunk contains at least one selected line + hunks.push(new Hunk({ + oldStartRow, + newStartRow: hunk.getNewStartRow(), + oldRowCount: hunk.getOldRowCount() - addedRowCount, + newRowCount: hunk.getNewRowCount(), + sectionHeading: hunk.getSectionHeading(), + bufferStartPosition: hunk.getBufferStartPosition(), + bufferStartOffset: hunk.getBufferStartOffset(), + bufferEndRow: hunk.getBufferEndRow(), + additions, + deletions, + noNewline: hunk.noNewline, + })); + } + delta += addedRowCount; } return hunks; } - present() { - return new PresentedFilePatch(this); - } - toString() { if (this.hasTypechange()) { const left = this.clone({ @@ -346,7 +316,7 @@ export default class FilePatch { const symlinkPath = this.getOldSymlink(); return this.getHeaderString() + `@@ -1 +0,0 @@\n-${symlinkPath}\n\\ No newline at end of file\n`; } else { - return this.getHeaderString() + this.getHunks().map(h => h.toString()).join(''); + return this.getHeaderString() + this.getPatch().toString(); } } From ff60d4e5b56aa530c7312053babaa36445f716c5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 6 Aug 2018 10:14:05 -0400 Subject: [PATCH 0077/4053] Export buildFilePatch from lib/models/patch --- lib/models/patch/index.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/models/patch/index.js diff --git a/lib/models/patch/index.js b/lib/models/patch/index.js new file mode 100644 index 0000000000..596fcfde50 --- /dev/null +++ b/lib/models/patch/index.js @@ -0,0 +1 @@ +export {default as buildFilePatch} from './builder'; From fa49f658bd0c79f3d18ba239a54d8798dc760650 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 6 Aug 2018 10:14:22 -0400 Subject: [PATCH 0078/4053] Use the new FilePatch builder from Repository --- lib/models/repository-states/present.js | 115 +----------------------- 1 file changed, 3 insertions(+), 112 deletions(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 2c00949633..abf67378b5 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -6,9 +6,7 @@ import State from './state'; import {LargeRepoError} from '../../git-shell-out-strategy'; import {FOCUS} from '../workspace-change-observer'; -import FilePatch from '../file-patch'; -import Hunk from '../hunk'; -import HunkLine from '../hunk-line'; +import {buildFilePatch} from '../patch'; import DiscardHistory from '../discard-history'; import Branch, {nullBranch} from '../branch'; import Author from '../author'; @@ -598,13 +596,8 @@ export default class Present extends State { getFilePatchForPath(filePath, {staged} = {staged: false}) { return this.cache.getOrSet(Keys.filePatch.oneWith(filePath, {staged}), async () => { - const rawDiffs = await this.git().getDiffsForFilePath(filePath, {staged}); - if (rawDiffs.length > 0) { - const filePatch = buildFilePatchFromRawDiffs(rawDiffs); - return filePatch; - } else { - return null; - } + const diffs = await this.git().getDiffsForFilePath(filePath, {staged}); + return buildFilePatch(diffs); }); } @@ -792,108 +785,6 @@ function partition(array, predicate) { return [matches, nonmatches]; } -function buildHunksFromDiff(diff) { - let diffLineNumber = 0; - return diff.hunks.map(hunk => { - let oldLineNumber = hunk.oldStartLine; - let newLineNumber = hunk.newStartLine; - const hunkLines = hunk.lines.map(line => { - const status = HunkLine.statusMap[line[0]]; - const text = line.slice(1); - let hunkLine; - if (status === 'unchanged') { - hunkLine = new HunkLine(text, status, oldLineNumber, newLineNumber, diffLineNumber++); - oldLineNumber++; - newLineNumber++; - } else if (status === 'added') { - hunkLine = new HunkLine(text, status, -1, newLineNumber, diffLineNumber++); - newLineNumber++; - } else if (status === 'deleted') { - hunkLine = new HunkLine(text, status, oldLineNumber, -1, diffLineNumber++); - oldLineNumber++; - } else if (status === 'nonewline') { - hunkLine = new HunkLine(text.substr(1), status, -1, -1, diffLineNumber++); - } else { - throw new Error(`unknown status type: ${status}`); - } - return hunkLine; - }); - return new Hunk( - hunk.oldStartLine, - hunk.newStartLine, - hunk.oldLineCount, - hunk.newLineCount, - hunk.heading, - hunkLines, - ); - }); -} - -function buildFilePatchFromSingleDiff(rawDiff) { - const wasSymlink = rawDiff.oldMode === '120000'; - const isSymlink = rawDiff.newMode === '120000'; - const diff = rawDiff; - const hunks = buildHunksFromDiff(diff); - let oldFile, newFile; - if (wasSymlink && !isSymlink) { - const symlink = diff.hunks[0].lines[0].slice(1); - oldFile = new FilePatch.File({path: diff.oldPath, mode: diff.oldMode, symlink}); - newFile = new FilePatch.File({path: diff.newPath, mode: diff.newMode, symlink: null}); - } else if (!wasSymlink && isSymlink) { - const symlink = diff.hunks[0].lines[0].slice(1); - oldFile = new FilePatch.File({path: diff.oldPath, mode: diff.oldMode, symlink: null}); - newFile = new FilePatch.File({path: diff.newPath, mode: diff.newMode, symlink}); - } else if (wasSymlink && isSymlink) { - const oldSymlink = diff.hunks[0].lines[0].slice(1); - const newSymlink = diff.hunks[0].lines[2].slice(1); - oldFile = new FilePatch.File({path: diff.oldPath, mode: diff.oldMode, symlink: oldSymlink}); - newFile = new FilePatch.File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink}); - } else { - oldFile = new FilePatch.File({path: diff.oldPath, mode: diff.oldMode, symlink: null}); - newFile = new FilePatch.File({path: diff.newPath, mode: diff.newMode, symlink: null}); - } - const patch = new FilePatch.Patch({status: diff.status, hunks}); - return new FilePatch(oldFile, newFile, patch); -} - -function buildFilePatchFromDualDiffs(diff1, diff2) { - let modeChangeDiff, contentChangeDiff; - if (diff1.oldMode === '120000' || diff1.newMode === '120000') { - modeChangeDiff = diff1; - contentChangeDiff = diff2; - } else { - modeChangeDiff = diff2; - contentChangeDiff = diff1; - } - const hunks = buildHunksFromDiff(contentChangeDiff); - const filePath = contentChangeDiff.oldPath || contentChangeDiff.newPath; - const symlink = modeChangeDiff.hunks[0].lines[0].slice(1); - let oldFile, newFile, status; - if (modeChangeDiff.status === 'added') { - oldFile = new FilePatch.File({path: filePath, mode: contentChangeDiff.oldMode, symlink: null}); - newFile = new FilePatch.File({path: filePath, mode: modeChangeDiff.newMode, symlink}); - status = 'deleted'; // contents were deleted and replaced with symlink - } else if (modeChangeDiff.status === 'deleted') { - oldFile = new FilePatch.File({path: filePath, mode: modeChangeDiff.oldMode, symlink}); - newFile = new FilePatch.File({path: filePath, mode: contentChangeDiff.newMode, symlink: null}); - status = 'added'; // contents were added after symlink was deleted - } else { - throw new Error(`Invalid mode change diff status: ${modeChangeDiff.status}`); - } - const patch = new FilePatch.Patch({status, hunks}); - return new FilePatch(oldFile, newFile, patch); -} - -function buildFilePatchFromRawDiffs(rawDiffs) { - if (rawDiffs.length === 1) { - return buildFilePatchFromSingleDiff(rawDiffs[0]); - } else if (rawDiffs.length === 2) { - return buildFilePatchFromDualDiffs(rawDiffs[0], rawDiffs[1]); - } else { - throw new Error(`Unexpected number of diffs: ${rawDiffs.length}`); - } -} - class Cache { constructor() { this.storage = new Map(); From 2d15f7003c470a948b94ce8543cf934de54e6b5d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 6 Aug 2018 10:23:53 -0400 Subject: [PATCH 0079/4053] Temporarily move broken tests out of the way --- ...h-selection.test.js => file-patch-selection.test.pending.js} | 0 test/models/{file-patch.test.js => file-patch.test.old.js} | 2 ++ ...{file-patch-view.test.js => file-patch-view.test.pending.js} | 0 ...unk-header-view.test.js => hunk-header-view.test.pending.js} | 0 test/views/{hunk-view.test.js => hunk-view.test.pending.js} | 0 5 files changed, 2 insertions(+) rename test/models/{file-patch-selection.test.js => file-patch-selection.test.pending.js} (100%) rename test/models/{file-patch.test.js => file-patch.test.old.js} (99%) rename test/views/{file-patch-view.test.js => file-patch-view.test.pending.js} (100%) rename test/views/{hunk-header-view.test.js => hunk-header-view.test.pending.js} (100%) rename test/views/{hunk-view.test.js => hunk-view.test.pending.js} (100%) diff --git a/test/models/file-patch-selection.test.js b/test/models/file-patch-selection.test.pending.js similarity index 100% rename from test/models/file-patch-selection.test.js rename to test/models/file-patch-selection.test.pending.js diff --git a/test/models/file-patch.test.js b/test/models/file-patch.test.old.js similarity index 99% rename from test/models/file-patch.test.js rename to test/models/file-patch.test.old.js index e78e625a73..9c87041202 100644 --- a/test/models/file-patch.test.js +++ b/test/models/file-patch.test.old.js @@ -16,6 +16,8 @@ function createFilePatch(oldFilePath, newFilePath, status, hunks) { return new FilePatch(oldFile, newFile, patch); } +// oldStartRow, newStartRow, oldRowCount, newRowCount, sectionHeading, lines + describe('FilePatch', function() { describe('getStagePatchForLines()', function() { it('returns a new FilePatch that applies only the specified lines', function() { diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.pending.js similarity index 100% rename from test/views/file-patch-view.test.js rename to test/views/file-patch-view.test.pending.js diff --git a/test/views/hunk-header-view.test.js b/test/views/hunk-header-view.test.pending.js similarity index 100% rename from test/views/hunk-header-view.test.js rename to test/views/hunk-header-view.test.pending.js diff --git a/test/views/hunk-view.test.js b/test/views/hunk-view.test.pending.js similarity index 100% rename from test/views/hunk-view.test.js rename to test/views/hunk-view.test.pending.js From b4decf77e3eac678aaa6635e7a340eee4fff9b0e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 6 Aug 2018 10:24:03 -0400 Subject: [PATCH 0080/4053] Delete an unused import --- lib/models/presented-file-patch.js | 1 - lib/views/file-patch-view.js | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/models/presented-file-patch.js b/lib/models/presented-file-patch.js index 871190dfc2..8fb5c00437 100644 --- a/lib/models/presented-file-patch.js +++ b/lib/models/presented-file-patch.js @@ -1,5 +1,4 @@ import {Point} from 'atom'; -import {fromBufferPosition} from './marker-position'; export default class PresentedFilePatch { constructor(filePatch) { diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index dcebafb3e3..1d12eeedcb 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -11,7 +11,6 @@ import Gutter from '../atom/gutter'; import FilePatchHeaderView from './file-patch-header-view'; import FilePatchMetaView from './file-patch-meta-view'; import HunkHeaderView from './hunk-header-view'; -import {fromBufferPosition} from '../models/marker-position'; const executableText = { 100644: 'non executable', From 6f7511252aeb00fde566f7825fdf406a596646b1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 6 Aug 2018 10:24:20 -0400 Subject: [PATCH 0081/4053] Start translating FilePatch tests --- test/models/patch/file-patch.test.js | 187 +++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 test/models/patch/file-patch.test.js diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js new file mode 100644 index 0000000000..3c6cb6f1ac --- /dev/null +++ b/test/models/patch/file-patch.test.js @@ -0,0 +1,187 @@ +import {buildFilePatch} from '../../../lib/models/patch'; +import {assertInFilePatch} from '../../helpers'; + +describe('FilePatch', function() { + describe('getStagePatchForLines()', function() { + it('returns a new FilePatch that applies only the selected lines', function() { + const filePatch = buildFilePatch([{ + oldPath: 'a.txt', + oldMode: '100644', + newPath: 'a.txt', + newMode: '100644', + hunks: [ + { + oldStartLine: 1, + oldLineCount: 1, + newStartLine: 1, + newLineCount: 3, + lines: [ + '+line-0', + '+line-1', + ' line-2', + ], + }, + { + oldStartLine: 5, + oldLineCount: 5, + newStartLine: 7, + newLineCount: 4, + lines: [ + ' line-3', + '-line-4', + '-line-5', + '+line-6', + '+line-7', + '+line-8', + '-line-9', + '-line-10', + ], + }, + { + oldStartLine: 20, + oldLineCount: 2, + newStartLine: 19, + newLineCount: 2, + lines: [ + '-line-11', + '+line-12', + ' line-13', + '\\No newline at end of file', + ], + }, + ], + }]); + + assert.strictEqual( + filePatch.getBufferText(), + 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\nline-6\nline-7\nline-8\nline-9\nline-10\n' + + 'line-11\nline-12\nline-13\nNo newline at end of file\n', + ); + + const stagePatch0 = filePatch.getStagePatchForLines(new Set([4, 5, 6])); + assertInFilePatch(stagePatch0).hunks( + { + startRow: 3, + header: '@@ -5,5 +7,4 @@', + deletions: {strings: ['*line-4\nline-5\n'], ranges: [[[4, 0], [5, 0]]]}, + additions: {strings: ['*line-6\n'], ranges: [[[6, 0], [6, 0]]]}, + }, + ); + + const stagePatch1 = filePatch.getStagePatchForLines(new Set([0, 4, 5, 6, 11])); + assertInFilePatch(stagePatch1).hunks( + { + startRow: 0, + header: '@@ -1,1 +1,2 @@', + additions: {strings: ['*line-0\n'], ranges: [[[0, 0], [0, 0]]]}, + }, + { + startRow: 3, + header: '@@ -5,5 +7,4 @@', + deletions: {strings: ['*line-4\n*line-5\n'], ranges: [[[4, 0], [5, 0]]]}, + additions: {strings: ['*line-6\n'], ranges: [[[6, 0], [6, 0]]]}, + }, + { + startRow: 11, + header: '@@ -20,2 +19,2 @@', + deletions: {strings: ['*line-11\n'], ranges: [[[11, 0], [11, 0]]]}, + noNewline: {string: '*No newline at end of file\n', range: [[14, 0], [14, 0]]}, + }, + ); + }); + + describe('staging lines from deleted files', function() { + it('handles staging part of the file', function() { + const filePatch = buildFilePatch([{ + oldPath: 'a.txt', + oldMode: '100644', + newPath: null, + newMode: '000000', + status: 'deleted', + hunks: [ + { + oldStartLine: 1, + newStartLine: 0, + oldLineCount: 3, + newLineCount: 0, + lines: [ + '-line-1', + '-line-2', + '-line-3', + ], + }, + { + oldStartLine: 19, + newStartLine: 21, + oldLineCount: 2, + newLineCount: 2, + lines: [ + '-line-13', + '+line-12', + ' line-14', + '\\No newline at end of file', + ], + }, + ], + }]); + + assert.strictEqual(filePatch.getBufferText(), + 'line-1\nline-2\nline-3\nline-13\nline-12\nline-14\n' + + 'No newline at end of file\n'); + + const stagePatch = filePatch.getStagePatchForLines(new Set([0, 1])); + assertInFilePatch(stagePatch).hunks( + { + startRow: 0, + header: '@@ -1,1 +3,1 @@', + deletions: {strings: ['*line-1\n*line-2'], ranges: [[[0, 0], [1, 0]]]}, + }, + ); + }); + + it('handles staging all lines, leaving nothing unstaged', function() { + const filePatch = buildFilePatch([{ + oldPath: 'a.txt', + oldMode: '100644', + newPath: null, + newMode: '000000', + status: 'deleted', + hunks: [ + { + oldStartLine: 1, + oldLineCount: 3, + newStartLine: 1, + newLineCount: 0, + lines: [ + '-line-1', + '-line-2', + '-line-3', + ], + }, + ], + }]); + + assert.strictEqual(filePatch.getBufferText(), 'line-1\nline-2\nline-3\n'); + + const stagePatch = filePatch.getStagePatchForLines(new Set(0, 1, 2)); + assertInFilePatch(stagePatch).hunks( + { + startRow: 0, + header: '@@ -1,3 +1,0 @@', + deletions: {strings: ['*line-1\n*line-2\n*line-3\n'], ranges: [[[0, 0], [2, 0]]]}, + }, + ); + }); + }); + }); + + describe('getUnstagePatchForLines()', function() { + it('returns a new FilePatch that applies only the specified lines'); + + describe('unstaging lines from an added file', function() { + it('handles unstaging part of the file'); + + it('handles unstaging all lines, leaving nothing staged'); + }); + }); +}); From b3879be0e097f82859b4256f72506e5d050ad6e6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 10 Aug 2018 16:37:06 -0400 Subject: [PATCH 0082/4053] Return gap ranges from IndexedRowRange::intersectRowRange() --- lib/models/indexed-row-range.js | 55 +++++++++++++++++---------- test/models/indexed-row-range.test.js | 53 ++++++++++++++++++++------ 2 files changed, 75 insertions(+), 33 deletions(-) diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js index 6098792466..0911012e85 100644 --- a/lib/models/indexed-row-range.js +++ b/lib/models/indexed-row-range.js @@ -27,44 +27,57 @@ export default class IndexedRowRange { return buffer.slice(this.startOffset, this.endOffset).replace(/(^|\n)(?!$)/g, '$&' + prefix); } - intersectRowsIn(rowSet, buffer) { - // Identify Ranges within our bufferRange that intersect the rows in rowSet. + // Identify {IndexedRowRanges} within our bufferRange that intersect the rows in rowSet. If {includeGaps} is true, + // also return an {IndexedRowRange} for each gap between intersecting ranges. + intersectRowsIn(rowSet, buffer, includeGaps) { const intersections = []; - let nextStartRow = null; - let nextStartOffset = null; + let withinIntersection = false; let currentRow = this.bufferRange.start.row; let currentOffset = this.startOffset; + let nextStartRow = currentRow; + let nextStartOffset = currentOffset; - while (currentRow <= this.bufferRange.end.row) { - if (rowSet.has(currentRow) && nextStartRow === null) { - // Start of intersecting row range + const finishRowRange = isGap => { + if (isGap && !includeGaps) { nextStartRow = currentRow; nextStartOffset = currentOffset; - } else if (!rowSet.has(currentRow) && nextStartRow !== null) { - // One row past the end of intersecting row range - intersections.push(new IndexedRowRange({ + return; + } + + if (nextStartOffset === currentOffset) { + return; + } + + intersections.push({ + intersection: new IndexedRowRange({ bufferRange: Range.fromObject([[nextStartRow, 0], [currentRow - 1, 0]]), startOffset: nextStartOffset, endOffset: currentOffset, - })); + }), + gap: isGap, + }); - nextStartRow = null; - nextStartOffset = null; + nextStartRow = currentRow; + nextStartOffset = currentOffset; + }; + + while (currentRow <= this.bufferRange.end.row) { + if (rowSet.has(currentRow) && !withinIntersection) { + // One row past the end of a gap. Start of intersecting row range. + finishRowRange(true); + withinIntersection = true; + } else if (!rowSet.has(currentRow) && withinIntersection) { + // One row past the end of intersecting row range. Start of the next gap. + finishRowRange(false); + withinIntersection = false; } currentOffset = buffer.indexOf('\n', currentOffset) + 1; currentRow++; } - if (nextStartRow !== null) { - intersections.push(new IndexedRowRange({ - bufferRange: Range.fromObject([[nextStartRow, 0], this.bufferRange.end]), - startOffset: nextStartOffset, - endOffset: currentOffset, - })); - } - + finishRowRange(!withinIntersection); return intersections; } diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index 8eae81e685..2b96c3501b 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -45,14 +45,22 @@ describe('IndexedRowRange', function() { const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999\n'; // 0000.1111.2222.3333.4444.5555.6666.7777.8888.9999. - it('returns an empty array with no intersection rows', function() { + function assertIntersections(actual, expected) { + const serialized = actual.map(({intersection, gap}) => ({intersection: intersection.serialize(), gap})); + assert.deepEqual(serialized, expected); + } + + it('returns an array containing all gaps with no intersection rows', function() { const range = new IndexedRowRange({ bufferRange: [[1, 0], [3, 0]], startOffset: 5, endOffset: 20, }); - assert.deepEqual(range.intersectRowsIn(new Set([0, 5, 6]), buffer), []); + assertIntersections(range.intersectRowsIn(new Set([0, 5, 6]), buffer, false), []); + assertIntersections(range.intersectRowsIn(new Set([0, 5, 6]), buffer, true), [ + {intersection: {bufferRange: [[1, 0], [3, 0]], startOffset: 5, endOffset: 20}, gap: true}, + ]); }); it('detects an intersection at the beginning of the range', function() { @@ -63,8 +71,12 @@ describe('IndexedRowRange', function() { }); const rowSet = new Set([0, 1, 2, 3]); - assert.deepEqual(range.intersectRowsIn(rowSet, buffer).map(i => i.serialize()), [ - {bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20}, + assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ + {intersection: {bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20}, gap: false}, + ]); + assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ + {intersection: {bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20}, gap: false}, + {intersection: {bufferRange: [[4, 0], [6, 0]], startOffset: 20, endOffset: 35}, gap: true}, ]); }); @@ -76,8 +88,13 @@ describe('IndexedRowRange', function() { }); const rowSet = new Set([0, 3, 4, 8, 9]); - assert.deepEqual(range.intersectRowsIn(rowSet, buffer).map(i => i.serialize()), [ - {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, + assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ + {intersection: {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, gap: false}, + ]); + assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ + {intersection: {bufferRange: [[2, 0], [2, 0]], startOffset: 10, endOffset: 15}, gap: true}, + {intersection: {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, gap: false}, + {intersection: {bufferRange: [[5, 0], [6, 0]], startOffset: 25, endOffset: 35}, gap: true}, ]); }); @@ -89,8 +106,12 @@ describe('IndexedRowRange', function() { }); const rowSet = new Set([4, 5, 6, 7, 10, 11]); - assert.deepEqual(range.intersectRowsIn(rowSet, buffer).map(i => i.serialize()), [ - {bufferRange: [[4, 0], [6, 0]], startOffset: 20, endOffset: 35}, + assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ + {intersection: {bufferRange: [[4, 0], [6, 0]], startOffset: 20, endOffset: 35}, gap: false}, + ]); + assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ + {intersection: {bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20}, gap: true}, + {intersection: {bufferRange: [[4, 0], [6, 0]], startOffset: 20, endOffset: 35}, gap: false}, ]); }); @@ -102,14 +123,22 @@ describe('IndexedRowRange', function() { }); const rowSet = new Set([0, 3, 4, 6, 7, 10]); - assert.deepEqual(range.intersectRowsIn(rowSet, buffer).map(i => i.serialize()), [ - {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, - {bufferRange: [[6, 0], [7, 0]], startOffset: 30, endOffset: 40}, + assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ + {intersection: {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, gap: false}, + {intersection: {bufferRange: [[6, 0], [7, 0]], startOffset: 30, endOffset: 40}, gap: false}, + ]); + assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ + {intersection: {bufferRange: [[2, 0], [2, 0]], startOffset: 10, endOffset: 15}, gap: true}, + {intersection: {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, gap: false}, + {intersection: {bufferRange: [[5, 0], [5, 0]], startOffset: 25, endOffset: 30}, gap: true}, + {intersection: {bufferRange: [[6, 0], [7, 0]], startOffset: 30, endOffset: 40}, gap: false}, + {intersection: {bufferRange: [[8, 0], [8, 0]], startOffset: 40, endOffset: 45}, gap: true}, ]); }); it('returns an empty array for the null range', function() { - assert.deepEqual(nullIndexedRowRange.intersectRowsIn(new Set([1, 2, 3]), buffer), []); + assertIntersections(nullIndexedRowRange.intersectRowsIn(new Set([1, 2, 3]), buffer, true), []); + assertIntersections(nullIndexedRowRange.intersectRowsIn(new Set([1, 2, 3]), buffer, false), []); }); }); From 4badd00860e1542ebc906c3dfef0dda506f9cfe0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 13 Aug 2018 10:21:53 -0400 Subject: [PATCH 0083/4053] Remove unused methods --- lib/controllers/git-tab-controller.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/controllers/git-tab-controller.js b/lib/controllers/git-tab-controller.js index 96bb9f708a..3459d08d14 100644 --- a/lib/controllers/git-tab-controller.js +++ b/lib/controllers/git-tab-controller.js @@ -56,7 +56,7 @@ export default class GitTabController extends React.Component { super(props, context); autobind( this, - 'unstageFilePatch', 'attemptStageAllOperation', 'attemptFileStageOperation', 'unstageFiles', 'prepareToCommit', + 'attemptStageAllOperation', 'attemptFileStageOperation', 'unstageFiles', 'prepareToCommit', 'commit', 'updateSelectedCoAuthors', 'undoLastCommit', 'abortMerge', 'resolveAsOurs', 'resolveAsTheirs', 'checkout', 'rememberLastFocus', 'quietlySelectItem', ); @@ -115,8 +115,6 @@ export default class GitTabController extends React.Component { discardWorkDirChangesForPaths={this.props.discardWorkDirChangesForPaths} undoLastDiscard={this.props.undoLastDiscard} - stageFilePatch={this.stageFilePatch} - unstageFilePatch={this.unstageFilePatch} attemptFileStageOperation={this.attemptFileStageOperation} attemptStageAllOperation={this.attemptStageAllOperation} prepareToCommit={this.prepareToCommit} @@ -186,10 +184,6 @@ export default class GitTabController extends React.Component { } } - unstageFilePatch(filePatch) { - return this.props.repository.applyPatchToIndex(filePatch.getUnstagePatch()); - } - attemptStageAllOperation(stageStatus) { return this.attemptFileStageOperation(['.'], stageStatus); } From 95f50b53bd97aeeaa102f8e9364e2277a0fbe854 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 15 Aug 2018 07:54:15 -0400 Subject: [PATCH 0084/4053] WIP --- lib/models/patch/builder.js | 2 +- lib/models/patch/file-patch.js | 113 ++++++----------------- lib/models/patch/hunk.js | 4 + lib/models/patch/patch.js | 116 ++++++++++++++++++++++++ notes.txt | 66 ++++++++++++++ stage.patch | 20 +++++ test/helpers.js | 4 +- test/models/indexed-row-range.test.js | 8 +- test/models/patch/file-patch.test.js | 123 +++++++++++++++++++++++--- unstage.patch | 15 ++++ 10 files changed, 368 insertions(+), 103 deletions(-) create mode 100644 notes.txt create mode 100644 stage.patch create mode 100644 unstage.patch diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index eec19f6609..b611f711f6 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -160,7 +160,7 @@ function buildHunks(diff) { newRowCount: hunkData.newLineCount, sectionHeading: hunkData.heading, rowRange: new IndexedRowRange({ - bufferRange: [[bufferStartRow, 0], [bufferRow, 0]], + bufferRange: [[bufferStartRow, 0], [bufferRow - 1, 0]], startOffset: bufferStartOffset, endOffset: bufferOffset, }), diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index b4faebb93e..d765ca82c7 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -8,8 +8,6 @@ export default class FilePatch { this.oldFile = oldFile; this.newFile = newFile; this.patch = patch; - - this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); } getOldFile() { @@ -134,94 +132,27 @@ export default class FilePatch { } getStagePatchForLines(selectedLineSet) { - const wholeFileSelected = this.changedLineCount === selectedLineSet.size; + const wholeFileSelected = this.patch.getChangedLineCount() === selectedLineSet.size; if (wholeFileSelected) { if (this.hasTypechange() && this.getStatus() === 'deleted') { // handle special case when symlink is created where a file was deleted. In order to stage the file deletion, // we must ensure that the created file patch has no new file - return this.clone({ - newFile: nullFile, - }); + return this.clone({newFile: nullFile}); } else { return this; } } else { - const hunks = this.getStagePatchHunks(selectedLineSet); + const patch = this.patch.getStagePatchForLines(selectedLineSet); if (this.getStatus() === 'deleted') { - // Set status to modified + // Populate newFile return this.clone({ newFile: this.getOldFile(), - patch: this.getPatch().clone({hunks, status: 'modified'}), + patch, }); } else { - return this.clone({ - patch: this.getPatch().clone({hunks}), - }); - } - } - } - - getStagePatchHunks(selectedLineSet) { - let delta = 0; - const hunks = []; - for (const hunk of this.getHunks()) { - const additions = []; - const deletions = []; - let deletedRowCount = 0; - - for (const change of hunk.getAdditions()) { - additions.push(...change.intersectRowsIn(selectedLineSet, this.getBufferText())); - } - - for (const change of hunk.getDeletions()) { - for (const intersection of change.intersectRowsIn(selectedLineSet, this.getBufferText())) { - deletedRowCount += intersection.bufferRowCount(); - deletions.push(intersection); - } - } - - if ( - additions.length > 0 || - deletions.length > 0 - ) { - // Hunk contains at least one selected line - hunks.push(new Hunk({ - oldStartRow: hunk.getOldStartRow(), - newStartRow: hunk.getNewStartRow() - delta, - oldRowCount: hunk.getOldRowCount(), - newRowCount: hunk.getNewRowCount() - deletedRowCount, - sectionHeading: hunk.getSectionHeading(), - rowRange: hunk.getRowRange(), - additions, - deletions, - noNewline: hunk.getNoNewline(), - })); + return this.clone({patch}); } - delta += deletedRowCount; } - return hunks; - } - - getUnstagePatch() { - const invertedStatus = { - modified: 'modified', - added: 'deleted', - deleted: 'added', - }[this.getStatus()]; - if (!invertedStatus) { - throw new Error(`Unknown Status: ${this.getStatus()}`); - } - - const invertedHunks = this.getHunks().map(h => h.invert()); - - return this.clone({ - oldFile: this.getNewFile(), - newFile: this.getOldFile(), - patch: this.getPatch().clone({ - status: invertedStatus, - hunks: invertedHunks, - }), - }); } getUnstagePatchForHunk(hunk) { @@ -259,40 +190,46 @@ export default class FilePatch { let delta = 0; const hunks = []; for (const hunk of this.getHunks()) { - const oldStartRow = (hunk.getOldStartRow() || 1) + delta; - const additions = []; const deletions = []; + let notAddedRowCount = 0; let addedRowCount = 0; + let notDeletedRowCount = 0; + let deletedRowCount = 0; for (const change of hunk.getAdditions()) { + notDeletedRowCount += change.bufferRowCount(); for (const intersection of change.intersectRowsIn(selectedRowSet, this.getBufferText())) { - addedRowCount += intersection.bufferRowCount(); - additions.push(intersection); + deletedRowCount += intersection.bufferRowCount(); + notDeletedRowCount -= intersection.bufferRowCount(); + deletions.push(intersection); } } - for (const change of hunk.getBufferDeletedPositions()) { - deletions.push(...change.intersectRowIn(selectedRowSet, this.getBufferText())); + for (const change of hunk.getDeletions()) { + notAddedRowCount = change.bufferRowCount(); + for (const intersection of change.intersectRowsIn(selectedRowSet, this.getBufferText())) { + addedRowCount += intersection.bufferRowCount(); + notAddedRowCount -= intersection.bufferRowCount(); + additions.push(intersection); + } } if (additions.length > 0 || deletions.length > 0) { // Hunk contains at least one selected line hunks.push(new Hunk({ - oldStartRow, + oldStartRow: hunk.getOldStartRow() + delta, newStartRow: hunk.getNewStartRow(), - oldRowCount: hunk.getOldRowCount() - addedRowCount, + oldRowCount: hunk.bufferRowCount() - addedRowCount, newRowCount: hunk.getNewRowCount(), sectionHeading: hunk.getSectionHeading(), - bufferStartPosition: hunk.getBufferStartPosition(), - bufferStartOffset: hunk.getBufferStartOffset(), - bufferEndRow: hunk.getBufferEndRow(), + rowRange: hunk.getRowRange(), additions, deletions, - noNewline: hunk.noNewline, + noNewline: hunk.getNoNewline(), })); } - delta += addedRowCount; + delta += notDeletedRowCount - notAddedRowCount; } return hunks; } diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index cce3460957..f4510ef180 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -72,6 +72,10 @@ export default class Hunk { return this.rowRange.getBufferRows(); } + bufferRowCount() { + return this.rowRange.bufferRowCount(); + } + changedLineCount() { return [ this.additions, diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 07ac604109..aebdd83a94 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -1,8 +1,12 @@ +import Hunk from './hunk'; + export default class Patch { constructor({status, hunks, bufferText}) { this.status = status; this.hunks = hunks; this.bufferText = bufferText; + + this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); } getStatus() { @@ -21,6 +25,10 @@ export default class Patch { return Buffer.byteLength(this.bufferText, 'utf8'); } + getChangedLineCount() { + return this.changedLineCount; + } + clone(opts = {}) { return new this.constructor({ status: opts.status !== undefined ? opts.status : this.getStatus(), @@ -29,6 +37,114 @@ export default class Patch { }); } + getStagePatchForLines(lineSet) { + const hunks = []; + let delta = 0; + + for (const hunk of this.getHunks()) { + const additions = []; + const deletions = []; + let notAddedRowCount = 0; + let deletedRowCount = 0; + let notDeletedRowCount = 0; + + for (const change of hunk.getAdditions()) { + notAddedRowCount += change.bufferRowCount(); + for (const intersection of change.intersectRowsIn(lineSet, this.getBufferText())) { + notAddedRowCount -= intersection.bufferRowCount(); + additions.push(intersection); + } + } + + for (const change of hunk.getDeletions()) { + notDeletedRowCount += change.bufferRowCount(); + for (const intersection of change.intersectRowsIn(lineSet, this.getBufferText())) { + deletedRowCount += intersection.bufferRowCount(); + notDeletedRowCount -= intersection.bufferRowCount(); + deletions.push(intersection); + } + } + + if (additions.length > 0 || deletions.length > 0) { + // Hunk contains at least one selected line + hunks.push(new Hunk({ + oldStartRow: hunk.getOldStartRow(), + newStartRow: hunk.getNewStartRow() + delta, + oldRowCount: hunk.getOldRowCount(), + newRowCount: hunk.bufferRowCount() - deletedRowCount, + sectionHeading: hunk.getSectionHeading(), + rowRange: hunk.getRowRange(), + additions, + deletions, + noNewline: hunk.getNoNewline(), + })); + } + + delta += notDeletedRowCount - notAddedRowCount; + } + + if (this.getStatus() === 'deleted') { + // Set status to modified + return this.clone({hunks, status: 'modified'}); + } else { + return this.clone({hunks}); + } + } + + getUnstagePatchForLines(lineSet) { + let delta = 0; + const hunks = []; + let bufferText = this.getBufferText(); + let bufferOffset = 0; + + for (const hunk of this.getHunks()) { + const additions = []; + const deletions = []; + let notAddedRowCount = 0; + let addedRowCount = 0; + let notDeletedRowCount = 0; + + for (const change of hunk.getAdditions()) { + notDeletedRowCount += change.bufferRowCount(); + for (const intersection of change.intersectRowsIn(lineSet, bufferText)) { + notDeletedRowCount -= intersection.bufferRowCount(); + deletions.push(intersection); + } + } + + for (const change of hunk.getDeletions()) { + notAddedRowCount = change.bufferRowCount(); + for (const intersection of change.intersectRowsIn(lineSet, bufferText)) { + addedRowCount += intersection.bufferRowCount(); + notAddedRowCount -= intersection.bufferRowCount(); + additions.push(intersection); + } + } + + if (additions.length > 0 || deletions.length > 0) { + // Hunk contains at least one selected line + hunks.push(new Hunk({ + oldStartRow: hunk.getOldStartRow() + delta, + newStartRow: hunk.getNewStartRow(), + oldRowCount: hunk.bufferRowCount() - addedRowCount, + newRowCount: hunk.getNewRowCount(), + sectionHeading: hunk.getSectionHeading(), + rowRange: hunk.getRowRange(), + additions, + deletions, + noNewline: hunk.getNoNewline(), + })); + } + delta += notAddedRowCount - notDeletedRowCount; + } + + if (this.getStatus() === 'added') { + return this.clone({hunks, bufferText, status: 'modified'}); + } else { + return this.clone({hunks, bufferText}); + } + } + toString() { return this.getHunks().reduce((str, hunk) => { str += hunk.toStringIn(this.getBufferText()); diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000000..c323737af0 --- /dev/null +++ b/notes.txt @@ -0,0 +1,66 @@ +file in index: +----------------- +00: aaa +01: line-0 +02: line-1 +03: line-2 +04: bbb +05: ccc +06: ddd +07: line-3 +08: line-6 +09: line-7 +10: line-8 +11: eee +12: fff +13: ggg +14: hhh +15: iii +16: jjj +17: kkk +18: lll +19: mmm +20: nnn +21: line-12 +22: line-13 +----------------- + +file on HEAD: +----------------- +00: aaa +01: line-2 +02: bbb +03: ccc +04: ddd +05: line-3 +06: line-4 +07: line-5 +08: line-9 +09: line-10 +10: eee +11: fff +12: ggg +13: hhh +14: iii +15: jjj +16: kkk +17: lll +18: mmm +19: nnn +20: line-11 +21: line-13 +----------------- + +unstage patch +----------------- +@@ -7,4 +7,4 @@ + line-3 ++line-4 ++line-5 +-line-6 +-line-7 + line-8 +@@ -21,2 +21,3 @@ ++line-11 + line-12 + line-13 diff --git a/stage.patch b/stage.patch new file mode 100644 index 0000000000..d820eaef0d --- /dev/null +++ b/stage.patch @@ -0,0 +1,20 @@ +dif --git a/a.txt b/a.txt +--- a/a.txt ++++ b/a.txt +@@ -2,1 +2,3 @@ ++line-0 ++line-1 + line-2 +@@ -6,5 +8,4 @@ + line-3 +-line-4 - +-line-5 - ++line-6 - ++line-7 - ++line-8 +-line-9 +-line-10 +@@ -20,2 +21,2 @@ +-line-11 - ++line-12 - + line-13 - diff --git a/test/helpers.js b/test/helpers.js index 88b96a2b4e..00a47c7e7c 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -169,14 +169,14 @@ class PatchBufferAssertions { ); } - hunk(hunkIndex, {startRow, header, deletions, additions, noNewline}) { + hunk(hunkIndex, {startRow, endRow, header, deletions, additions, noNewline}) { const hunk = this.patch.getHunks()[hunkIndex]; assert.isDefined(hunk); deletions = deletions !== undefined ? deletions : {strings: [], ranges: []}; additions = additions !== undefined ? additions : {strings: [], ranges: []}; - assert.deepEqual(hunk.getStartRange().serialize(), [[startRow, 0], [startRow, 0]]); + assert.deepEqual(hunk.getRowRange().serialize().bufferRange, [[startRow, 0], [endRow, 0]]); assert.strictEqual(hunk.getHeader(), header); this.hunkChanges(hunk.getDeletions(), deletions.strings, deletions.ranges); diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index 2b96c3501b..01e970059a 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -1,6 +1,6 @@ import IndexedRowRange, {nullIndexedRowRange} from '../../lib/models/indexed-row-range'; -describe('IndexedRowRange', function() { +describe.only('IndexedRowRange', function() { it('computes its row count', function() { const range = new IndexedRowRange({ bufferRange: [[0, 0], [1, 0]], @@ -142,6 +142,12 @@ describe('IndexedRowRange', function() { }); }); + describe('offsetBy()', function() { + it('returns the receiver as-is when there is no change'); + + it('modifies the buffer range and the buffer offset'); + }); + it('returns appropriate values from nullIndexedRowRange methods', function() { assert.deepEqual(nullIndexedRowRange.intersectRowsIn(new Set([0, 1, 2]), ''), []); assert.strictEqual(nullIndexedRowRange.toStringIn('', '+'), ''); diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 3c6cb6f1ac..594beb7732 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -62,8 +62,9 @@ describe('FilePatch', function() { assertInFilePatch(stagePatch0).hunks( { startRow: 3, - header: '@@ -5,5 +7,4 @@', - deletions: {strings: ['*line-4\nline-5\n'], ranges: [[[4, 0], [5, 0]]]}, + endRow: 10, + header: '@@ -5,5 +5,6 @@', + deletions: {strings: ['*line-4\n*line-5\n'], ranges: [[[4, 0], [5, 0]]]}, additions: {strings: ['*line-6\n'], ranges: [[[6, 0], [6, 0]]]}, }, ); @@ -72,18 +73,21 @@ describe('FilePatch', function() { assertInFilePatch(stagePatch1).hunks( { startRow: 0, - header: '@@ -1,1 +1,2 @@', + endRow: 2, + header: '@@ -1,1 +1,3 @@', additions: {strings: ['*line-0\n'], ranges: [[[0, 0], [0, 0]]]}, }, { startRow: 3, - header: '@@ -5,5 +7,4 @@', + endRow: 10, + header: '@@ -5,5 +6,6 @@', deletions: {strings: ['*line-4\n*line-5\n'], ranges: [[[4, 0], [5, 0]]]}, additions: {strings: ['*line-6\n'], ranges: [[[6, 0], [6, 0]]]}, }, { startRow: 11, - header: '@@ -20,2 +19,2 @@', + endRow: 14, + header: '@@ -20,2 +18,3 @@', deletions: {strings: ['*line-11\n'], ranges: [[[11, 0], [11, 0]]]}, noNewline: {string: '*No newline at end of file\n', range: [[14, 0], [14, 0]]}, }, @@ -96,7 +100,7 @@ describe('FilePatch', function() { oldPath: 'a.txt', oldMode: '100644', newPath: null, - newMode: '000000', + newMode: null, status: 'deleted', hunks: [ { @@ -133,8 +137,9 @@ describe('FilePatch', function() { assertInFilePatch(stagePatch).hunks( { startRow: 0, - header: '@@ -1,1 +3,1 @@', - deletions: {strings: ['*line-1\n*line-2'], ranges: [[[0, 0], [1, 0]]]}, + endRow: 2, + header: '@@ -1,3 +0,1 @@', + deletions: {strings: ['*line-1\n*line-2\n'], ranges: [[[0, 0], [1, 0]]]}, }, ); }); @@ -144,7 +149,7 @@ describe('FilePatch', function() { oldPath: 'a.txt', oldMode: '100644', newPath: null, - newMode: '000000', + newMode: null, status: 'deleted', hunks: [ { @@ -163,10 +168,11 @@ describe('FilePatch', function() { assert.strictEqual(filePatch.getBufferText(), 'line-1\nline-2\nline-3\n'); - const stagePatch = filePatch.getStagePatchForLines(new Set(0, 1, 2)); + const stagePatch = filePatch.getStagePatchForLines(new Set([0, 1, 2])); assertInFilePatch(stagePatch).hunks( { startRow: 0, + endRow: 2, header: '@@ -1,3 +1,0 @@', deletions: {strings: ['*line-1\n*line-2\n*line-3\n'], ranges: [[[0, 0], [2, 0]]]}, }, @@ -176,7 +182,82 @@ describe('FilePatch', function() { }); describe('getUnstagePatchForLines()', function() { - it('returns a new FilePatch that applies only the specified lines'); + it('returns a new FilePatch that unstages only the specified lines', function() { + const filePatch = buildFilePatch([{ + oldPath: 'a.txt', + oldMode: '100644', + newPath: 'a.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 1, + oldLineCount: 1, + newStartLine: 1, + newLineCount: 3, + lines: [ + '+line-0', + '+line-1', + ' line-2', + ], + }, + { + oldStartLine: 5, + oldLineCount: 5, + newStartLine: 7, + newLineCount: 4, + lines: [ + ' line-3', + '-line-4', + '-line-5', + '+line-6', + '+line-7', + '+line-8', + '-line-9', + '-line-10', + ], + }, + { + oldStartLine: 20, + oldLineCount: 2, + newStartLine: 21, + newLineCount: 2, + lines: [ + '-line-11', + '+line-12', + ' line-13', + '\\No newline at end of file', + ], + }, + ], + }]); + + assert.strictEqual( + filePatch.getBufferText(), + 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\nline-6\nline-7\nline-8\nline-9\nline-10\n' + + 'line-11\nline-12\nline-13\nNo newline at end of file\n', + ); + + const unstagedPatch0 = filePatch.getUnstagePatchForLines(new Set([4, 5, 6, 7, 11, 12, 13])); + console.log(unstagedPatch0.toString()); + assertInFilePatch(unstagedPatch0).hunks( + { + startRow: 3, + endRow: 10, + header: '@@ -7,4 +7,4 @@', + additions: {strings: ['*line-4\n*line-5\n'], ranges: [[[4, 0], [5, 0]]]}, + deletions: {strings: ['*line-6\n*line-7\n'], ranges: [[[5, 0], [6, 0]]]}, + }, + { + startRow: 11, + endRow: 14, + header: '@@ -19,2 +21,2 @@', + additions: {strings: ['*line-11\n'], ranges: [[[11, 0], [11, 0]]]}, + deletions: {strings: ['*line-12\n'], ranges: [[[12, 0], [12, 0]]]}, + noNewline: {string: '*No newline at end of file\n', range: [[14, 0], [14, 0]]}, + }, + ); + }); describe('unstaging lines from an added file', function() { it('handles unstaging part of the file'); @@ -184,4 +265,24 @@ describe('FilePatch', function() { it('handles unstaging all lines, leaving nothing staged'); }); }); + + it('handles newly added files'); + + describe('toString()', function() { + it('converts the patch to the standard textual format'); + + it('correctly formats new files with no newline at the end'); + + describe('typechange file patches', function() { + it('handles typechange patches for a symlink replaced with a file'); + + it('handles typechange patches for a file replaced with a symlink'); + }); + }); + + describe('getHeaderString()', function() { + it('formats paths with git path separators'); + }); + + it('getByteSize() returns the size in bytes'); }); diff --git a/unstage.patch b/unstage.patch new file mode 100644 index 0000000000..d66cfb3978 --- /dev/null +++ b/unstage.patch @@ -0,0 +1,15 @@ +dif --git a/a.txt b/a.txt +--- a/a.txt ++++ b/a.txt +@@ -8,4 +8,4 @@ + line-3 ++line-4 ++line-5 +-line-6 +-line-7 + line-8 +@@ -22,2 +22,2 @@ ++line-11 +-line-12 + line-13 +\ No newline at end of file From c8fe7f6dffc070396ce837050c629fea116cbb49 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 15 Aug 2018 08:36:55 -0400 Subject: [PATCH 0085/4053] Change model --- lib/models/patch/change.js | 54 +++++++++++ test/models/patch/change.test.js | 150 +++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 lib/models/patch/change.js create mode 100644 test/models/patch/change.test.js diff --git a/lib/models/patch/change.js b/lib/models/patch/change.js new file mode 100644 index 0000000000..2e103faff2 --- /dev/null +++ b/lib/models/patch/change.js @@ -0,0 +1,54 @@ +class Change { + constructor(range) { + this.range = range; + } + + getRange() { + return this.range; + } + + isAddition() { + return false; + } + + isDeletion() { + return false; + } + + isNoNewline() { + return false; + } + + when(callbacks) { + const callback = callbacks[this.constructor.name.toLowerCase()] || callbacks.default || (() => undefined); + return callback(); + } + + toStringIn(buffer) { + return this.range.toStringIn(buffer, this.constructor.origin); + } +} + +export class Addition extends Change { + static origin = '+'; + + isAddition() { + return true; + } +} + +export class Deletion extends Change { + static origin = '-'; + + isDeletion() { + return true; + } +} + +export class NoNewline extends Change { + static origin = '\\ '; + + isNoNewline() { + return true; + } +} diff --git a/test/models/patch/change.test.js b/test/models/patch/change.test.js new file mode 100644 index 0000000000..ddf4ce35f3 --- /dev/null +++ b/test/models/patch/change.test.js @@ -0,0 +1,150 @@ +import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/change'; +import IndexedRowRange from '../../../lib/models/indexed-row-range'; + +describe('Changes', function() { + let buffer, range; + + beforeEach(function() { + buffer = '0000\n1111\n2222\n3333\n4444\n5555\n'; + range = new IndexedRowRange({bufferRange: [[1, 0], [3, 0]], startOffset: 5, endOffset: 20}); + }); + + describe('Addition', function() { + let addition; + + beforeEach(function() { + addition = new Addition(range); + }); + + it('has a range accessor', function() { + assert.strictEqual(addition.getRange(), range); + }); + + it('can be recognized by the isAddition predicate', function() { + assert.isTrue(addition.isAddition()); + assert.isFalse(addition.isDeletion()); + assert.isFalse(addition.isNoNewline()); + }); + + it('executes the "addition" branch of a when() call', function() { + const result = addition.when({ + addition: () => 'correct', + deletion: () => 'wrong: deletion', + nonewline: () => 'wrong: nonewline', + default: () => 'wrong: default', + }); + assert.strictEqual(result, 'correct'); + }); + + it('executes the "default" branch of a when() call when no "addition" is provided', function() { + const result = addition.when({ + deletion: () => 'wrong: deletion', + nonewline: () => 'wrong: nonewline', + default: () => 'correct', + }); + assert.strictEqual(result, 'correct'); + }); + + it('returns undefined from when() if neither "addition" nor "default" are provided', function() { + const result = addition.when({ + deletion: () => 'wrong: deletion', + nonewline: () => 'wrong: nonewline', + }); + assert.isUndefined(result); + }); + + it('uses "+" as a prefix for toStringIn()', function() { + assert.strictEqual(addition.toStringIn(buffer), '+1111\n+2222\n+3333\n'); + }); + }); + + describe('Deletion', function() { + let deletion; + + beforeEach(function() { + deletion = new Deletion(range); + }); + + it('can be recognized by the isDeletion predicate', function() { + assert.isFalse(deletion.isAddition()); + assert.isTrue(deletion.isDeletion()); + assert.isFalse(deletion.isNoNewline()); + }); + + it('executes the "deletion" branch of a when() call', function() { + const result = deletion.when({ + addition: () => 'wrong: addition', + deletion: () => 'correct', + nonewline: () => 'wrong: nonewline', + default: () => 'wrong: default', + }); + assert.strictEqual(result, 'correct'); + }); + + it('executes the "default" branch of a when() call when no "deletion" is provided', function() { + const result = deletion.when({ + addition: () => 'wrong: addition', + nonewline: () => 'wrong: nonewline', + default: () => 'correct', + }); + assert.strictEqual(result, 'correct'); + }); + + it('returns undefined from when() if neither "deletion" nor "default" are provided', function() { + const result = deletion.when({ + addition: () => 'wrong: addition', + nonewline: () => 'wrong: nonewline', + }); + assert.isUndefined(result); + }); + + it('uses "-" as a prefix for toStringIn()', function() { + assert.strictEqual(deletion.toStringIn(buffer), '-1111\n-2222\n-3333\n'); + }); + }); + + describe('NoNewline', function() { + let noNewline; + + beforeEach(function() { + noNewline = new NoNewline(range); + }); + + it('can be recognized by the isNoNewline predicate', function() { + assert.isFalse(noNewline.isAddition()); + assert.isFalse(noNewline.isDeletion()); + assert.isTrue(noNewline.isNoNewline()); + }); + + it('executes the "nonewline" branch of a when() call', function() { + const result = noNewline.when({ + addition: () => 'wrong: addition', + deletion: () => 'wrong: deletion', + nonewline: () => 'correct', + default: () => 'wrong: default', + }); + assert.strictEqual(result, 'correct'); + }); + + it('executes the "default" branch of a when() call when no "nonewline" is provided', function() { + const result = noNewline.when({ + addition: () => 'wrong: addition', + deletion: () => 'wrong: deletion', + default: () => 'correct', + }); + assert.strictEqual(result, 'correct'); + }); + + it('returns undefined from when() if neither "nonewline" nor "default" are provided', function() { + const result = noNewline.when({ + addition: () => 'wrong: addition', + deletion: () => 'wrong: deletion', + }); + assert.isUndefined(result); + }); + + it('uses "\\ " as a prefix for toStringIn()', function() { + assert.strictEqual(noNewline.toStringIn(buffer), '\\ 1111\n\\ 2222\n\\ 3333\n'); + }); + }); +}); From 7040d1f7c117809c52df16f81aea17830de6c564 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 15 Aug 2018 08:37:01 -0400 Subject: [PATCH 0086/4053] :fire: dot only --- test/models/indexed-row-range.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index 01e970059a..1aa7a0331c 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -1,6 +1,6 @@ import IndexedRowRange, {nullIndexedRowRange} from '../../lib/models/indexed-row-range'; -describe.only('IndexedRowRange', function() { +describe('IndexedRowRange', function() { it('computes its row count', function() { const range = new IndexedRowRange({ bufferRange: [[0, 0], [1, 0]], From d203e585e2fdb2650f9f908f98b1c7b2c5d7f237 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 15 Aug 2018 09:15:05 -0400 Subject: [PATCH 0087/4053] Teach Changes to invert themselves --- lib/models/patch/change.js | 12 ++++++++++++ test/models/patch/change.test.js | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/models/patch/change.js b/lib/models/patch/change.js index 2e103faff2..5f75f9e0a0 100644 --- a/lib/models/patch/change.js +++ b/lib/models/patch/change.js @@ -35,6 +35,10 @@ export class Addition extends Change { isAddition() { return true; } + + invert() { + return new Deletion(this.getRange()); + } } export class Deletion extends Change { @@ -43,6 +47,10 @@ export class Deletion extends Change { isDeletion() { return true; } + + invert() { + return new Addition(this.getRange()); + } } export class NoNewline extends Change { @@ -51,4 +59,8 @@ export class NoNewline extends Change { isNoNewline() { return true; } + + invert() { + return this; + } } diff --git a/test/models/patch/change.test.js b/test/models/patch/change.test.js index ddf4ce35f3..dfab23f61c 100644 --- a/test/models/patch/change.test.js +++ b/test/models/patch/change.test.js @@ -56,6 +56,12 @@ describe('Changes', function() { it('uses "+" as a prefix for toStringIn()', function() { assert.strictEqual(addition.toStringIn(buffer), '+1111\n+2222\n+3333\n'); }); + + it('inverts to a deletion', function() { + const inverted = addition.invert(); + assert.isTrue(inverted.isDeletion()); + assert.strictEqual(inverted.getRange(), addition.getRange()); + }); }); describe('Deletion', function() { @@ -101,6 +107,12 @@ describe('Changes', function() { it('uses "-" as a prefix for toStringIn()', function() { assert.strictEqual(deletion.toStringIn(buffer), '-1111\n-2222\n-3333\n'); }); + + it('inverts to an addition', function() { + const inverted = deletion.invert(); + assert.isTrue(inverted.isAddition()); + assert.strictEqual(inverted.getRange(), deletion.getRange()); + }); }); describe('NoNewline', function() { @@ -146,5 +158,9 @@ describe('Changes', function() { it('uses "\\ " as a prefix for toStringIn()', function() { assert.strictEqual(noNewline.toStringIn(buffer), '\\ 1111\n\\ 2222\n\\ 3333\n'); }); + + it('inverts as itself', function() { + assert.strictEqual(noNewline.invert(), noNewline); + }); }); }); From 905e5607d9182ead50e3a958c84529bb7c8b3f8f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 15 Aug 2018 09:15:24 -0400 Subject: [PATCH 0088/4053] Use Changes within the Hunk model --- lib/models/patch/hunk.js | 99 +++++++++++++--------------------- test/models/patch/hunk.test.js | 99 +++++++++++++++------------------- 2 files changed, 81 insertions(+), 117 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index f4510ef180..97f89a330d 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -8,9 +8,7 @@ export default class Hunk { newRowCount, sectionHeading, rowRange, - additions, - deletions, - noNewline, + changes, }) { this.oldStartRow = oldStartRow; this.newStartRow = newStartRow; @@ -19,9 +17,7 @@ export default class Hunk { this.sectionHeading = sectionHeading; this.rowRange = rowRange; - this.additions = additions; - this.deletions = deletions; - this.noNewline = noNewline; + this.changes = changes; } getOldStartRow() { @@ -48,16 +44,25 @@ export default class Hunk { return this.sectionHeading; } + getChanges() { + return this.changes; + } + getAdditions() { - return this.additions; + return this.changes.filter(change => change.isAddition()).map(change => change.getRange()); } getDeletions() { - return this.deletions; + return this.changes.filter(change => change.isDeletion()).map(change => change.getRange()); } getNoNewline() { - return this.noNewline; + const lastChange = this.changes[this.changes.length - 1]; + if (lastChange && lastChange.isNoNewline()) { + return lastChange.getRange(); + } else { + return nullIndexedRowRange; + } } getRowRange() { @@ -77,13 +82,10 @@ export default class Hunk { } changedLineCount() { - return [ - this.additions, - this.deletions, - [this.noNewline], - ].reduce((count, ranges) => { - return ranges.reduce((subCount, range) => subCount + range.bufferRowCount(), count); - }, 0); + return this.changes.reduce((count, change) => change.when({ + nonewline: () => count, + default: () => count + change.getRange().bufferRowCount(), + }), 0); } invert() { @@ -94,65 +96,38 @@ export default class Hunk { newRowCount: this.getOldRowCount(), sectionHeading: this.getSectionHeading(), rowRange: this.rowRange, - additions: this.getDeletions(), - deletions: this.getAdditions(), - noNewline: this.getNoNewline(), + changes: this.getChanges().map(change => change.invert()), }); } toStringIn(bufferText) { let str = this.getHeader() + '\n'; - let additionIndex = 0; - let deletionIndex = 0; - - const endRange = new IndexedRowRange({ - bufferRange: [[0, 0], [0, 0]], - startOffset: this.rowRange.endOffset, - endOffset: this.rowRange.endOffset, - }); - - const nextRange = () => { - const nextAddition = this.additions[additionIndex] || nullIndexedRowRange; - const nextDeletion = this.deletions[deletionIndex] || nullIndexedRowRange; - - const minRange = [this.noNewline, nextAddition, nextDeletion, endRange].reduce((least, range) => { - return range.startOffset < least.startOffset ? range : least; - }); - - const unchanged = minRange.startOffset === currentOffset - ? nullIndexedRowRange - : new IndexedRowRange({ + let currentOffset = this.rowRange.startOffset; + for (const change of this.getChanges()) { + const range = change.getRange(); + if (range.startOffset !== currentOffset) { + const unchanged = new IndexedRowRange({ bufferRange: [[0, 0], [0, 0]], startOffset: currentOffset, - endOffset: minRange.startOffset, + endOffset: range.startOffset, }); - - if (minRange === nextAddition) { - additionIndex++; - return {origin: '+', range: minRange, unchanged}; - } else if (minRange === nextDeletion) { - deletionIndex++; - return {origin: '-', range: minRange, unchanged}; - } else if (minRange === endRange) { - return {origin: ' ', range: minRange, unchanged}; - } else { - return {origin: '\\', range: this.noNewline, unchanged}; + str += unchanged.toStringIn(bufferText, ' '); } - }; - let currentOffset = this.rowRange.startOffset; - while (currentOffset < bufferText.length) { - const {origin, range, unchanged} = nextRange(); - str += unchanged.toStringIn(bufferText, ' '); - - if (range === endRange) { - break; - } - - str += range.toStringIn(bufferText, origin); + str += change.toStringIn(bufferText); currentOffset = range.endOffset; } + + if (currentOffset !== this.rowRange.endOffset) { + const unchanged = new IndexedRowRange({ + bufferRange: [[0, 0], [0, 0]], + startOffset: currentOffset, + endOffset: this.rowRange.endOffset, + }); + str += unchanged.toStringIn(bufferText, ' '); + } + return str; } } diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 530ab39c58..28ac982057 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -1,5 +1,6 @@ import Hunk from '../../../lib/models/patch/hunk'; -import IndexedRowRange, {nullIndexedRowRange} from '../../../lib/models/indexed-row-range'; +import IndexedRowRange from '../../../lib/models/indexed-row-range'; +import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/change'; describe('Hunk', function() { const attrs = { @@ -13,14 +14,11 @@ describe('Hunk', function() { startOffset: 5, endOffset: 100, }), - additions: [ - new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 6, endOffset: 7}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 6, endOffset: 7})), + new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [4, 0]], startOffset: 8, endOffset: 9})), + new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 10, endOffset: 11})), ], - deletions: [ - new IndexedRowRange({bufferRange: [[3, 0], [4, 0]], startOffset: 8, endOffset: 9}), - new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 10, endOffset: 11}), - ], - noNewline: nullIndexedRowRange, }; it('has some basic accessors', function() { @@ -35,14 +33,11 @@ describe('Hunk', function() { startOffset: 0, endOffset: 100, }), - additions: [ - new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 6, endOffset: 7}), - ], - deletions: [ - new IndexedRowRange({bufferRange: [[3, 0], [4, 0]], startOffset: 8, endOffset: 9}), - new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 10, endOffset: 11}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 6, endOffset: 7})), + new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [4, 0]], startOffset: 8, endOffset: 9})), + new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 10, endOffset: 11})), ], - noNewline: nullIndexedRowRange, }); assert.strictEqual(h.getOldStartRow(), 0); @@ -50,6 +45,13 @@ describe('Hunk', function() { assert.strictEqual(h.getOldRowCount(), 2); assert.strictEqual(h.getNewRowCount(), 3); assert.strictEqual(h.getSectionHeading(), 'sectionHeading'); + assert.deepEqual(h.getRowRange().serialize(), { + bufferRange: [[0, 0], [10, 0]], + startOffset: 0, + endOffset: 100, + }); + assert.strictEqual(h.bufferRowCount(), 11); + assert.lengthOf(h.getChanges(), 3); assert.lengthOf(h.getAdditions(), 1); assert.lengthOf(h.getDeletions(), 2); assert.isFalse(h.getNoNewline().isPresent()); @@ -95,22 +97,18 @@ describe('Hunk', function() { it('computes the total number of changed lines', function() { const h0 = new Hunk({ ...attrs, - additions: [ - new IndexedRowRange({bufferRange: [[2, 0], [4, 0]], startOffset: 0, endOffset: 0}), - new IndexedRowRange({bufferRange: [[6, 0], [6, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[2, 0], [4, 0]], startOffset: 0, endOffset: 0})), + new Addition(new IndexedRowRange({bufferRange: [[6, 0], [6, 0]], startOffset: 0, endOffset: 0})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [10, 0]], startOffset: 0, endOffset: 0})), + new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0})), ], - deletions: [ - new IndexedRowRange({bufferRange: [[7, 0], [10, 0]], startOffset: 0, endOffset: 0}), - ], - noNewline: new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0}), }); - assert.strictEqual(h0.changedLineCount(), 9); + assert.strictEqual(h0.changedLineCount(), 8); const h1 = new Hunk({ ...attrs, - additions: [], - deletions: [], - noNewline: nullIndexedRowRange, + changes: [], }); assert.strictEqual(h1.changedLineCount(), 0); }); @@ -123,14 +121,12 @@ describe('Hunk', function() { oldRowCount: 2, newRowCount: 3, sectionHeading: 'the-heading', - additions: [ - new IndexedRowRange({bufferRange: [[2, 0], [4, 0]], startOffset: 0, endOffset: 0}), - new IndexedRowRange({bufferRange: [[6, 0], [6, 0]], startOffset: 0, endOffset: 0}), - ], - deletions: [ - new IndexedRowRange({bufferRange: [[7, 0], [10, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[2, 0], [4, 0]], startOffset: 0, endOffset: 0})), + new Addition(new IndexedRowRange({bufferRange: [[6, 0], [6, 0]], startOffset: 0, endOffset: 0})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [10, 0]], startOffset: 0, endOffset: 0})), + new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0})), ], - noNewline: new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0}), }); const inverted = original.invert(); @@ -139,9 +135,9 @@ describe('Hunk', function() { assert.strictEqual(inverted.getOldRowCount(), 3); assert.strictEqual(inverted.getNewRowCount(), 2); assert.strictEqual(inverted.getSectionHeading(), 'the-heading'); - assert.lengthOf(inverted.additions, 1); - assert.lengthOf(inverted.deletions, 2); - assert.isTrue(inverted.noNewline.isPresent()); + assert.lengthOf(inverted.getAdditions(), 1); + assert.lengthOf(inverted.getDeletions(), 2); + assert.isTrue(inverted.getNoNewline().isPresent()); }); describe('toStringIn()', function() { @@ -152,9 +148,7 @@ describe('Hunk', function() { newStartRow: 1, oldRowCount: 2, newRowCount: 3, - additions: [], - deletions: [], - noNewline: nullIndexedRowRange, + changes: [], }); assert.strictEqual(h.toStringIn(''), '@@ -0,2 +1,3 @@\n'); @@ -178,16 +172,14 @@ describe('Hunk', function() { startOffset: 5, endOffset: 91, }), - additions: [ - new IndexedRowRange({bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20}), - new IndexedRowRange({bufferRange: [[7, 0], [7, 0]], startOffset: 35, endOffset: 40}), - new IndexedRowRange({bufferRange: [[10, 0], [10, 0]], startOffset: 50, endOffset: 55}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20})), + new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [5, 0]], startOffset: 25, endOffset: 30})), + new Addition(new IndexedRowRange({bufferRange: [[7, 0], [7, 0]], startOffset: 35, endOffset: 40})), + new Deletion(new IndexedRowRange({bufferRange: [[8, 0], [9, 0]], startOffset: 40, endOffset: 50})), + new Addition(new IndexedRowRange({bufferRange: [[10, 0], [10, 0]], startOffset: 50, endOffset: 55})), + new NoNewline(new IndexedRowRange({bufferRange: [[13, 0], [13, 0]], startOffset: 65, endOffset: 91})), ], - deletions: [ - new IndexedRowRange({bufferRange: [[5, 0], [5, 0]], startOffset: 25, endOffset: 30}), - new IndexedRowRange({bufferRange: [[8, 0], [9, 0]], startOffset: 40, endOffset: 50}), - ], - noNewline: new IndexedRowRange({bufferRange: [[13, 0], [13, 0]], startOffset: 65, endOffset: 91}), }); assert.strictEqual(h.toStringIn(buffer), [ @@ -204,7 +196,7 @@ describe('Hunk', function() { '+1000\n', ' 1111\n', ' 1222\n', - '\\No newline at end of file\n', + '\\ No newline at end of file\n', ].join('')); }); @@ -218,13 +210,10 @@ describe('Hunk', function() { oldRowCount: 1, newRowCount: 1, rowRange: new IndexedRowRange({bufferRange: [[0, 0], [3, 0]], startOffset: 0, endOffset: 20}), - additions: [ - new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10}), - ], - deletions: [ - new IndexedRowRange({bufferRange: [[2, 0], [2, 0]], startOffset: 10, endOffset: 15}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + new Deletion(new IndexedRowRange({bufferRange: [[2, 0], [2, 0]], startOffset: 10, endOffset: 15})), ], - noNewline: nullIndexedRowRange, }); assert.strictEqual(h.toStringIn(buffer), [ From 5a2716f198508126ae628e22365e97170f489695 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 15 Aug 2018 09:59:29 -0400 Subject: [PATCH 0089/4053] Keep the NoNewline status marker as '\\' without the space --- lib/models/patch/change.js | 2 +- test/models/patch/change.test.js | 4 ++-- test/models/patch/hunk.test.js | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/models/patch/change.js b/lib/models/patch/change.js index 5f75f9e0a0..3442b8c0e4 100644 --- a/lib/models/patch/change.js +++ b/lib/models/patch/change.js @@ -54,7 +54,7 @@ export class Deletion extends Change { } export class NoNewline extends Change { - static origin = '\\ '; + static origin = '\\'; isNoNewline() { return true; diff --git a/test/models/patch/change.test.js b/test/models/patch/change.test.js index dfab23f61c..bab482f9b5 100644 --- a/test/models/patch/change.test.js +++ b/test/models/patch/change.test.js @@ -155,8 +155,8 @@ describe('Changes', function() { assert.isUndefined(result); }); - it('uses "\\ " as a prefix for toStringIn()', function() { - assert.strictEqual(noNewline.toStringIn(buffer), '\\ 1111\n\\ 2222\n\\ 3333\n'); + it('uses "\\" as a prefix for toStringIn()', function() { + assert.strictEqual(noNewline.toStringIn(buffer), '\\1111\n\\2222\n\\3333\n'); }); it('inverts as itself', function() { diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 28ac982057..6118cf4e80 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -158,8 +158,8 @@ describe('Hunk', function() { const buffer = '0000\n0111\n0222\n0333\n0444\n0555\n0666\n0777\n0888\n0999\n' + '1000\n1111\n1222\n' + - 'No newline at end of file\n'; - // 0000.0111.0222.0333.0444.0555.0666.0777.0888.0999.1000.1111.1222.No newline at end of file. + ' No newline at end of file\n'; + // 0000.0111.0222.0333.0444.0555.0666.0777.0888.0999.1000.1111.1222. No newline at end of file. const h = new Hunk({ ...attrs, @@ -178,7 +178,7 @@ describe('Hunk', function() { new Addition(new IndexedRowRange({bufferRange: [[7, 0], [7, 0]], startOffset: 35, endOffset: 40})), new Deletion(new IndexedRowRange({bufferRange: [[8, 0], [9, 0]], startOffset: 40, endOffset: 50})), new Addition(new IndexedRowRange({bufferRange: [[10, 0], [10, 0]], startOffset: 50, endOffset: 55})), - new NoNewline(new IndexedRowRange({bufferRange: [[13, 0], [13, 0]], startOffset: 65, endOffset: 91})), + new NoNewline(new IndexedRowRange({bufferRange: [[13, 0], [13, 0]], startOffset: 65, endOffset: 92})), ], }); From e67ab801c5d762a83f7432453d9c874f35cab96e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 15 Aug 2018 09:59:51 -0400 Subject: [PATCH 0090/4053] Update the PatchBufferAssertion helper to test Changes --- test/helpers.js | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/test/helpers.js b/test/helpers.js index 00a47c7e7c..c1a2ca2c0d 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -159,34 +159,21 @@ class PatchBufferAssertions { this.patch = patch; } - hunkChanges(changes, expectedStrings, expectedRanges) { - const actualStrings = changes.map(change => change.toStringIn(this.patch.getBufferText(), '*')); - const actualRanges = changes.map(change => change.bufferRange.serialize()); - - assert.deepEqual( - {strings: actualStrings, ranges: actualRanges}, - {strings: expectedStrings, ranges: expectedRanges}, - ); - } - - hunk(hunkIndex, {startRow, endRow, header, deletions, additions, noNewline}) { + hunk(hunkIndex, {startRow, endRow, header, changes}) { const hunk = this.patch.getHunks()[hunkIndex]; assert.isDefined(hunk); - deletions = deletions !== undefined ? deletions : {strings: [], ranges: []}; - additions = additions !== undefined ? additions : {strings: [], ranges: []}; - assert.deepEqual(hunk.getRowRange().serialize().bufferRange, [[startRow, 0], [endRow, 0]]); assert.strictEqual(hunk.getHeader(), header); + assert.lengthOf(hunk.getChanges(), changes.length); - this.hunkChanges(hunk.getDeletions(), deletions.strings, deletions.ranges); - this.hunkChanges(hunk.getAdditions(), additions.strings, additions.ranges); + for (let i = 0; i < changes.length; i++) { + const change = hunk.getChanges()[i]; + const spec = changes[i]; - const noNewlineChange = hunk.getNoNewline(); - if (noNewlineChange.isPresent()) { - this.hunkChanges([noNewlineChange], [noNewline.string], [noNewline.range]); - } else { - assert.isUndefined(noNewline); + assert.strictEqual(change.constructor.name.toLowerCase(), spec.kind); + assert.strictEqual(change.toStringIn(this.patch.getBufferText()), spec.string); + assert.deepEqual(change.getRange().bufferRange.serialize(), spec.range); } } From d7815a2598e62a5d77ca8d866c39c87b4144c9ab Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 15 Aug 2018 10:00:06 -0400 Subject: [PATCH 0091/4053] Construct Changes in Builder --- lib/models/patch/builder.js | 63 +++++++++------------ test/models/patch/builder.test.js | 94 ++++++++++++------------------- 2 files changed, 63 insertions(+), 94 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index b611f711f6..ac697d304f 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -1,7 +1,8 @@ import Hunk from './hunk'; import File, {nullFile} from './file'; import Patch, {nullPatch} from './patch'; -import IndexedRowRange, {nullIndexedRowRange} from '../indexed-row-range'; +import IndexedRowRange from '../indexed-row-range'; +import {Addition, Deletion, NoNewline} from './change'; import FilePatch from './file-patch'; export default function buildFilePatch(diffs) { @@ -84,11 +85,11 @@ function dualDiffFilePatch(diff1, diff2) { return new FilePatch(oldFile, newFile, patch); } -const STATUS = { - '+': 'added', - '-': 'deleted', - ' ': 'unchanged', - '\\': 'nonewline', +const CHANGEKIND = { + '+': Addition, + '-': Deletion, + ' ': null, + '\\': NoNewline, }; function buildHunks(diff) { @@ -103,25 +104,26 @@ function buildHunks(diff) { const bufferStartRow = bufferRow; const bufferStartOffset = bufferOffset; - const additions = []; - const deletions = []; - const noNewlines = []; + const changes = []; - let lastStatus = null; + let LastChangeKind = null; let currentRangeStart = bufferRow; const finishCurrentRange = () => { - const ranges = { - added: additions, - deleted: deletions, - nonewline: noNewlines, - }[lastStatus]; - if (ranges !== undefined) { - ranges.push(new IndexedRowRange({ - bufferRange: [[currentRangeStart, 0], [bufferRow - 1, 0]], - startOffset, - endOffset: bufferOffset, - })); + if (currentRangeStart === bufferRow) { + return; + } + + if (LastChangeKind !== null) { + changes.push( + new LastChangeKind( + new IndexedRowRange({ + bufferRange: [[currentRangeStart, 0], [bufferRow - 1, 0]], + startOffset, + endOffset: bufferOffset, + }), + ), + ); } startOffset = bufferOffset; currentRangeStart = bufferRow; @@ -131,28 +133,21 @@ function buildHunks(diff) { const bufferLine = lineText.slice(1) + '\n'; bufferText += bufferLine; - const status = STATUS[lineText[0]]; - if (status === undefined) { + const ChangeKind = CHANGEKIND[lineText[0]]; + if (ChangeKind === undefined) { throw new Error(`Unknown diff status character: "${lineText[0]}"`); } - if (status !== lastStatus && lastStatus !== null) { + if (ChangeKind !== LastChangeKind) { finishCurrentRange(); } - lastStatus = status; + LastChangeKind = ChangeKind; bufferOffset += bufferLine.length; bufferRow++; } finishCurrentRange(); - let noNewline = nullIndexedRowRange; - if (noNewlines.length === 1) { - noNewline = noNewlines[0]; - } else if (noNewlines.length > 1) { - throw new Error('Multiple nonewline lines encountered in diff'); - } - hunks.push(new Hunk({ oldStartRow: hunkData.oldStartLine, newStartRow: hunkData.newStartLine, @@ -164,9 +159,7 @@ function buildHunks(diff) { startOffset: bufferStartOffset, endOffset: bufferOffset, }), - additions, - deletions, - noNewline, + changes, })); } diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index a77bbe351b..3bd1db0bbb 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -78,39 +78,30 @@ describe('buildFilePatch', function() { assertInPatch(p).hunks( { startRow: 0, + endRow: 8, header: '@@ -0,7 +0,6 @@', - deletions: { - strings: ['*line-1\n*line-2\n*line-3\n'], - ranges: [[[1, 0], [3, 0]]], - }, - additions: { - strings: ['*line-5\n*line-6\n'], - ranges: [[[5, 0], [6, 0]]], - }, + changes: [ + {kind: 'deletion', string: '-line-1\n-line-2\n-line-3\n', range: [[1, 0], [3, 0]]}, + {kind: 'addition', string: '+line-5\n+line-6\n', range: [[5, 0], [6, 0]]}, + ], }, { startRow: 9, + endRow: 12, header: '@@ -10,3 +11,3 @@', - deletions: { - strings: ['*line-9\n'], - ranges: [[[9, 0], [9, 0]]], - }, - additions: { - strings: ['*line-12\n'], - ranges: [[[12, 0], [12, 0]]], - }, + changes: [ + {kind: 'deletion', string: '-line-9\n', range: [[9, 0], [9, 0]]}, + {kind: 'addition', string: '+line-12\n', range: [[12, 0], [12, 0]]}, + ], }, { startRow: 13, + endRow: 18, header: '@@ -20,4 +21,4 @@', - deletions: { - strings: ['*line-14\n*line-15\n'], - ranges: [[[14, 0], [15, 0]]], - }, - additions: { - strings: ['*line-16\n*line-17\n'], - ranges: [[[16, 0], [17, 0]]], - }, + changes: [ + {kind: 'deletion', string: '-line-14\n-line-15\n', range: [[14, 0], [15, 0]]}, + {kind: 'addition', string: '+line-16\n+line-17\n', range: [[16, 0], [17, 0]]}, + ], }, ); }); @@ -206,35 +197,23 @@ describe('buildFilePatch', function() { newMode: '100644', status: 'modified', hunks: [{oldStartLine: 0, newStartLine: 0, oldLineCount: 1, newLineCount: 1, lines: [ - '+line-0', '-line-1', '\\No newline at end of file', + '+line-0', '-line-1', '\\ No newline at end of file', ]}], }]); - assert.strictEqual(p.getBufferText(), 'line-0\nline-1\nNo newline at end of file\n'); + assert.strictEqual(p.getBufferText(), 'line-0\nline-1\n No newline at end of file\n'); assertInPatch(p).hunks({ startRow: 0, + endRow: 2, header: '@@ -0,1 +0,1 @@', - additions: {strings: ['*line-0\n'], ranges: [[[0, 0], [0, 0]]]}, - deletions: {strings: ['*line-1\n'], ranges: [[[1, 0], [1, 0]]]}, - noNewline: {string: '*No newline at end of file\n', range: [[2, 0], [2, 0]]}, + changes: [ + {kind: 'addition', string: '+line-0\n', range: [[0, 0], [0, 0]]}, + {kind: 'deletion', string: '-line-1\n', range: [[1, 0], [1, 0]]}, + {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[2, 0], [2, 0]]}, + ], }); }); - - it('throws an error when multiple no-newline markers are encountered', function() { - assert.throws(() => { - buildFilePatch([{ - oldPath: 'old/path', - oldMode: '100644', - newPath: 'new/path', - newMode: '100644', - status: 'modified', - hunks: [{oldStartLine: 0, newStartLine: 0, oldLineCount: 1, newLineCount: 1, lines: [ - '\\No newline at end of file', ' unchanged', '\\No newline at end of file', - ]}], - }]); - }, /Multiple nonewline/); - }); }); describe('with a mode change and a content diff', function() { @@ -285,12 +264,11 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getBufferText(), 'line-0\nline-1\n'); assertInPatch(p).hunks({ startRow: 0, + endRow: 1, header: '@@ -0,0 +0,2 @@', - deletions: {strings: [], ranges: []}, - additions: { - strings: ['*line-0\n*line-1\n'], - ranges: [[[0, 0], [1, 0]]], - }, + changes: [ + {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 0]]}, + ], }); }); @@ -341,12 +319,11 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getBufferText(), 'line-0\nline-1\n'); assertInPatch(p).hunks({ startRow: 0, + endRow: 1, header: '@@ -0,2 +0,0 @@', - deletions: { - strings: ['*line-0\n*line-1\n'], - ranges: [[[0, 0], [1, 0]]], - }, - additions: {strings: [], ranges: []}, + changes: [ + {kind: 'deletion', string: '-line-0\n-line-1\n', range: [[0, 0], [1, 0]]}, + ], }); }); @@ -396,12 +373,11 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getBufferText(), 'line-0\nline-1\n'); assertInPatch(p).hunks({ startRow: 0, + endRow: 1, header: '@@ -0,0 +0,2 @@', - deletions: {strings: [], ranges: []}, - additions: { - strings: ['*line-0\n*line-1\n'], - ranges: [[[0, 0], [1, 0]]], - }, + changes: [ + {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 0]]}, + ], }); }); From 97e2775dc2065c8b8d4df942834149bfda090a5a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 15 Aug 2018 10:11:10 -0400 Subject: [PATCH 0092/4053] Use .offsetBy() to translate an IndexedRowRange --- lib/models/indexed-row-range.js | 16 ++++++++++++++++ test/models/indexed-row-range.test.js | 27 +++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js index 0911012e85..d8c74a06a1 100644 --- a/lib/models/indexed-row-range.js +++ b/lib/models/indexed-row-range.js @@ -81,6 +81,18 @@ export default class IndexedRowRange { return intersections; } + offsetBy(bufferOffset, rowOffset) { + if (bufferOffset === 0 && rowOffset === 0) { + return this; + } + + return new this.constructor({ + bufferRange: this.bufferRange.translate([rowOffset, 0]), + startOffset: this.startOffset + bufferOffset, + endOffset: this.endOffset + bufferOffset, + }); + } + serialize() { return { bufferRange: this.bufferRange.serialize(), @@ -111,6 +123,10 @@ export const nullIndexedRowRange = { return []; }, + offsetBy() { + return this; + }, + isPresent() { return false; }, diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index 1aa7a0331c..eb8ea8c138 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -143,9 +143,32 @@ describe('IndexedRowRange', function() { }); describe('offsetBy()', function() { - it('returns the receiver as-is when there is no change'); + let original; - it('modifies the buffer range and the buffer offset'); + beforeEach(function() { + original = new IndexedRowRange({ + bufferRange: [[3, 0], [5, 0]], + startOffset: 15, + endOffset: 25, + }); + }); + + it('returns the receiver as-is when there is no change', function() { + assert.strictEqual(original.offsetBy(0, 0), original); + }); + + it('modifies the buffer range and the buffer offset', function() { + const changed = original.offsetBy(10, 3); + assert.deepEqual(changed.serialize(), { + bufferRange: [[6, 0], [8, 0]], + startOffset: 25, + endOffset: 35, + }); + }); + + it('is a no-op on a nullIndexedRowRange', function() { + assert.strictEqual(nullIndexedRowRange.offsetBy(100, 200), nullIndexedRowRange); + }); }); it('returns appropriate values from nullIndexedRowRange methods', function() { From 4c18a50f4dc2941ffd3a85dd08663cca72c2a5fb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 15 Aug 2018 11:12:42 -0400 Subject: [PATCH 0093/4053] Rename getRange() to getRowRange() --- lib/models/patch/change.js | 18 +++++++++++++++--- lib/models/patch/hunk.js | 4 ++-- test/helpers.js | 2 +- test/models/patch/change.test.js | 12 +++++++++--- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/models/patch/change.js b/lib/models/patch/change.js index 3442b8c0e4..339a35faf1 100644 --- a/lib/models/patch/change.js +++ b/lib/models/patch/change.js @@ -3,7 +3,7 @@ class Change { this.range = range; } - getRange() { + getRowRange() { return this.range; } @@ -19,6 +19,18 @@ class Change { return false; } + getBufferRows() { + return this.range.getBufferRows(); + } + + getStartRange() { + return this.range.getStartRange(); + } + + bufferRowCount() { + return this.range.bufferRowCount(); + } + when(callbacks) { const callback = callbacks[this.constructor.name.toLowerCase()] || callbacks.default || (() => undefined); return callback(); @@ -37,7 +49,7 @@ export class Addition extends Change { } invert() { - return new Deletion(this.getRange()); + return new Deletion(this.getRowRange()); } } @@ -49,7 +61,7 @@ export class Deletion extends Change { } invert() { - return new Addition(this.getRange()); + return new Addition(this.getRowRange()); } } diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 97f89a330d..e955601b9e 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -84,7 +84,7 @@ export default class Hunk { changedLineCount() { return this.changes.reduce((count, change) => change.when({ nonewline: () => count, - default: () => count + change.getRange().bufferRowCount(), + default: () => count + change.getRowRange().bufferRowCount(), }), 0); } @@ -105,7 +105,7 @@ export default class Hunk { let currentOffset = this.rowRange.startOffset; for (const change of this.getChanges()) { - const range = change.getRange(); + const range = change.getRowRange(); if (range.startOffset !== currentOffset) { const unchanged = new IndexedRowRange({ bufferRange: [[0, 0], [0, 0]], diff --git a/test/helpers.js b/test/helpers.js index c1a2ca2c0d..8f0caf2fba 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -173,7 +173,7 @@ class PatchBufferAssertions { assert.strictEqual(change.constructor.name.toLowerCase(), spec.kind); assert.strictEqual(change.toStringIn(this.patch.getBufferText()), spec.string); - assert.deepEqual(change.getRange().bufferRange.serialize(), spec.range); + assert.deepEqual(change.getRowRange().bufferRange.serialize(), spec.range); } } diff --git a/test/models/patch/change.test.js b/test/models/patch/change.test.js index bab482f9b5..4f18998a48 100644 --- a/test/models/patch/change.test.js +++ b/test/models/patch/change.test.js @@ -17,7 +17,13 @@ describe('Changes', function() { }); it('has a range accessor', function() { - assert.strictEqual(addition.getRange(), range); + assert.strictEqual(addition.getRowRange(), range); + }); + + it('delegates some methods to its row range', function() { + assert.sameMembers(Array.from(addition.getBufferRows()), [1, 2, 3]); + assert.deepEqual(addition.getStartRange().serialize(), [[1, 0], [1, 0]]); + assert.strictEqual(addition.bufferRowCount(), 3); }); it('can be recognized by the isAddition predicate', function() { @@ -60,7 +66,7 @@ describe('Changes', function() { it('inverts to a deletion', function() { const inverted = addition.invert(); assert.isTrue(inverted.isDeletion()); - assert.strictEqual(inverted.getRange(), addition.getRange()); + assert.strictEqual(inverted.getRowRange(), addition.getRowRange()); }); }); @@ -111,7 +117,7 @@ describe('Changes', function() { it('inverts to an addition', function() { const inverted = deletion.invert(); assert.isTrue(inverted.isAddition()); - assert.strictEqual(inverted.getRange(), deletion.getRange()); + assert.strictEqual(inverted.getRowRange(), deletion.getRowRange()); }); }); From 0b494b3266a4526d4fcf76e033e98442b54b2626 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 16 Aug 2018 11:34:02 -0400 Subject: [PATCH 0094/4053] Offset an IndexedRowRange with separate start and end translations --- lib/models/indexed-row-range.js | 10 +++++----- test/models/indexed-row-range.test.js | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js index d8c74a06a1..87b9b6d673 100644 --- a/lib/models/indexed-row-range.js +++ b/lib/models/indexed-row-range.js @@ -81,15 +81,15 @@ export default class IndexedRowRange { return intersections; } - offsetBy(bufferOffset, rowOffset) { - if (bufferOffset === 0 && rowOffset === 0) { + offsetBy(startBufferOffset, startRowOffset, endBufferOffset = startBufferOffset, endRowOffset = startRowOffset) { + if (startBufferOffset === 0 && startRowOffset === 0 && endBufferOffset === 0 && endRowOffset === 0) { return this; } return new this.constructor({ - bufferRange: this.bufferRange.translate([rowOffset, 0]), - startOffset: this.startOffset + bufferOffset, - endOffset: this.endOffset + bufferOffset, + bufferRange: this.bufferRange.translate([startRowOffset, 0], [endRowOffset, 0]), + startOffset: this.startOffset + startBufferOffset, + endOffset: this.endOffset + endBufferOffset, }); } diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index eb8ea8c138..27daef0d27 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -166,6 +166,15 @@ describe('IndexedRowRange', function() { }); }); + it('may specify separate start and end offsets', function() { + const changed = original.offsetBy(10, 2, 30, 4); + assert.deepEqual(changed.serialize(), { + bufferRange: [[5, 0], [9, 0]], + startOffset: 25, + endOffset: 55, + }); + }); + it('is a no-op on a nullIndexedRowRange', function() { assert.strictEqual(nullIndexedRowRange.offsetBy(100, 200), nullIndexedRowRange); }); From 3f4b96cf26e957dad52f7a5459dd4f1eb7c0328c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 16 Aug 2018 11:35:05 -0400 Subject: [PATCH 0095/4053] Rename Change to Region and add an Unchanged region --- lib/models/patch/{change.js => region.js} | 36 +++++++-- test/models/patch/hunk.test.js | 2 +- .../patch/{change.test.js => region.test.js} | 77 ++++++++++++++++++- 3 files changed, 106 insertions(+), 9 deletions(-) rename lib/models/patch/{change.js => region.js} (72%) rename test/models/patch/{change.test.js => region.test.js} (68%) diff --git a/lib/models/patch/change.js b/lib/models/patch/region.js similarity index 72% rename from lib/models/patch/change.js rename to lib/models/patch/region.js index 339a35faf1..4535ae9a07 100644 --- a/lib/models/patch/change.js +++ b/lib/models/patch/region.js @@ -1,4 +1,4 @@ -class Change { +class Region { constructor(range) { this.range = range; } @@ -15,6 +15,10 @@ class Change { return false; } + isUnchanged() { + return false; + } + isNoNewline() { return false; } @@ -39,9 +43,17 @@ class Change { toStringIn(buffer) { return this.range.toStringIn(buffer, this.constructor.origin); } + + invert() { + return this; + } + + isChange() { + return true; + } } -export class Addition extends Change { +export class Addition extends Region { static origin = '+'; isAddition() { @@ -53,7 +65,7 @@ export class Addition extends Change { } } -export class Deletion extends Change { +export class Deletion extends Region { static origin = '-'; isDeletion() { @@ -65,14 +77,26 @@ export class Deletion extends Change { } } -export class NoNewline extends Change { +export class Unchanged extends Region { + static origin = ' '; + + isUnchanged() { + return true; + } + + isChange() { + return false; + } +} + +export class NoNewline extends Region { static origin = '\\'; isNoNewline() { return true; } - invert() { - return this; + isChange() { + return false; } } diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 6118cf4e80..ba412defca 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -1,6 +1,6 @@ import Hunk from '../../../lib/models/patch/hunk'; import IndexedRowRange from '../../../lib/models/indexed-row-range'; -import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/change'; +import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; describe('Hunk', function() { const attrs = { diff --git a/test/models/patch/change.test.js b/test/models/patch/region.test.js similarity index 68% rename from test/models/patch/change.test.js rename to test/models/patch/region.test.js index 4f18998a48..c7ca5dd6b6 100644 --- a/test/models/patch/change.test.js +++ b/test/models/patch/region.test.js @@ -1,7 +1,7 @@ -import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/change'; +import {Addition, Deletion, NoNewline, Unchanged} from '../../../lib/models/patch/region'; import IndexedRowRange from '../../../lib/models/indexed-row-range'; -describe('Changes', function() { +describe('Regions', function() { let buffer, range; beforeEach(function() { @@ -29,13 +29,17 @@ describe('Changes', function() { it('can be recognized by the isAddition predicate', function() { assert.isTrue(addition.isAddition()); assert.isFalse(addition.isDeletion()); + assert.isFalse(addition.isUnchanged()); assert.isFalse(addition.isNoNewline()); + + assert.isTrue(addition.isChange()); }); it('executes the "addition" branch of a when() call', function() { const result = addition.when({ addition: () => 'correct', deletion: () => 'wrong: deletion', + unchanged: () => 'wrong: unchanged', nonewline: () => 'wrong: nonewline', default: () => 'wrong: default', }); @@ -45,6 +49,7 @@ describe('Changes', function() { it('executes the "default" branch of a when() call when no "addition" is provided', function() { const result = addition.when({ deletion: () => 'wrong: deletion', + unchanged: () => 'wrong: unchanged', nonewline: () => 'wrong: nonewline', default: () => 'correct', }); @@ -54,6 +59,7 @@ describe('Changes', function() { it('returns undefined from when() if neither "addition" nor "default" are provided', function() { const result = addition.when({ deletion: () => 'wrong: deletion', + unchanged: () => 'wrong: unchanged', nonewline: () => 'wrong: nonewline', }); assert.isUndefined(result); @@ -80,13 +86,17 @@ describe('Changes', function() { it('can be recognized by the isDeletion predicate', function() { assert.isFalse(deletion.isAddition()); assert.isTrue(deletion.isDeletion()); + assert.isFalse(deletion.isUnchanged()); assert.isFalse(deletion.isNoNewline()); + + assert.isTrue(deletion.isChange()); }); it('executes the "deletion" branch of a when() call', function() { const result = deletion.when({ addition: () => 'wrong: addition', deletion: () => 'correct', + unchanged: () => 'wrong: unchanged', nonewline: () => 'wrong: nonewline', default: () => 'wrong: default', }); @@ -96,6 +106,7 @@ describe('Changes', function() { it('executes the "default" branch of a when() call when no "deletion" is provided', function() { const result = deletion.when({ addition: () => 'wrong: addition', + unchanged: () => 'wrong: unchanged', nonewline: () => 'wrong: nonewline', default: () => 'correct', }); @@ -105,6 +116,7 @@ describe('Changes', function() { it('returns undefined from when() if neither "deletion" nor "default" are provided', function() { const result = deletion.when({ addition: () => 'wrong: addition', + unchanged: () => 'wrong: unchanged', nonewline: () => 'wrong: nonewline', }); assert.isUndefined(result); @@ -121,6 +133,61 @@ describe('Changes', function() { }); }); + describe('Unchanged', function() { + let unchanged; + + beforeEach(function() { + unchanged = new Unchanged(range); + }); + + it('can be recognized by the isUnchanged predicate', function() { + assert.isFalse(unchanged.isAddition()); + assert.isFalse(unchanged.isDeletion()); + assert.isTrue(unchanged.isUnchanged()); + assert.isFalse(unchanged.isNoNewline()); + + assert.isFalse(unchanged.isChange()); + }); + + it('executes the "unchanged" branch of a when() call', function() { + const result = unchanged.when({ + addition: () => 'wrong: addition', + deletion: () => 'wrong: deletion', + unchanged: () => 'correct', + nonewline: () => 'wrong: nonewline', + default: () => 'wrong: default', + }); + assert.strictEqual(result, 'correct'); + }); + + it('executes the "default" branch of a when() call when no "unchanged" is provided', function() { + const result = unchanged.when({ + addition: () => 'wrong: addition', + deletion: () => 'wrong: deletion', + nonewline: () => 'wrong: nonewline', + default: () => 'correct', + }); + assert.strictEqual(result, 'correct'); + }); + + it('returns undefined from when() if neither "unchanged" nor "default" are provided', function() { + const result = unchanged.when({ + addition: () => 'wrong: addition', + deletion: () => 'wrong: deletion', + nonewline: () => 'wrong: nonewline', + }); + assert.isUndefined(result); + }); + + it('uses " " as a prefix for toStringIn()', function() { + assert.strictEqual(unchanged.toStringIn(buffer), ' 1111\n 2222\n 3333\n'); + }); + + it('inverts as itself', function() { + assert.strictEqual(unchanged.invert(), unchanged); + }); + }); + describe('NoNewline', function() { let noNewline; @@ -131,13 +198,17 @@ describe('Changes', function() { it('can be recognized by the isNoNewline predicate', function() { assert.isFalse(noNewline.isAddition()); assert.isFalse(noNewline.isDeletion()); + assert.isFalse(noNewline.isUnchanged()); assert.isTrue(noNewline.isNoNewline()); + + assert.isFalse(noNewline.isChange()); }); it('executes the "nonewline" branch of a when() call', function() { const result = noNewline.when({ addition: () => 'wrong: addition', deletion: () => 'wrong: deletion', + unchanged: () => 'wrong: unchanged', nonewline: () => 'correct', default: () => 'wrong: default', }); @@ -148,6 +219,7 @@ describe('Changes', function() { const result = noNewline.when({ addition: () => 'wrong: addition', deletion: () => 'wrong: deletion', + unchanged: () => 'wrong: unchanged', default: () => 'correct', }); assert.strictEqual(result, 'correct'); @@ -157,6 +229,7 @@ describe('Changes', function() { const result = noNewline.when({ addition: () => 'wrong: addition', deletion: () => 'wrong: deletion', + unchanged: () => 'wrong: unchanged', }); assert.isUndefined(result); }); From 69094aa27737e499347c82111b9759aba088b6b5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 16 Aug 2018 11:36:20 -0400 Subject: [PATCH 0096/4053] Iterate over all contiguous Regions within a Hunk --- lib/models/patch/hunk.js | 77 +++++++++++++++++++------------- test/models/patch/hunk.test.js | 80 +++++++++++++++++++++++++++++++--- 2 files changed, 120 insertions(+), 37 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index e955601b9e..1b4290d20e 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -1,4 +1,5 @@ import IndexedRowRange, {nullIndexedRowRange} from '../indexed-row-range'; +import {Unchanged} from './region'; export default class Hunk { constructor({ @@ -48,18 +49,55 @@ export default class Hunk { return this.changes; } - getAdditions() { - return this.changes.filter(change => change.isAddition()).map(change => change.getRange()); + getRegions() { + const regions = []; + let currentRow = this.rowRange.bufferRange.start.row; + let currentPosition = this.rowRange.startOffset; + + for (const change of this.changes) { + const startRow = change.getRowRange().bufferRange.start.row; + const startPosition = change.getRowRange().startOffset; + + if (currentRow !== startRow) { + regions.push(new Unchanged(new IndexedRowRange({ + bufferRange: [[currentRow, 0], [startRow - 1, 0]], + startOffset: currentPosition, + endOffset: startPosition, + }))); + } + + regions.push(change); + + currentRow = change.getRowRange().bufferRange.end.row + 1; + currentPosition = change.getRowRange().endOffset; + } + + const endRow = this.rowRange.bufferRange.end.row; + const endPosition = this.rowRange.endOffset; + + if (currentRow <= endRow) { + regions.push(new Unchanged(new IndexedRowRange({ + bufferRange: [[currentRow, 0], [endRow, 0]], + startOffset: currentPosition, + endOffset: endPosition, + }))); + } + + return regions; + } + + getAdditionRanges() { + return this.changes.filter(change => change.isAddition()).map(change => change.getRowRange()); } - getDeletions() { - return this.changes.filter(change => change.isDeletion()).map(change => change.getRange()); + getDeletionRanges() { + return this.changes.filter(change => change.isDeletion()).map(change => change.getRowRange()); } - getNoNewline() { + getNoNewlineRange() { const lastChange = this.changes[this.changes.length - 1]; if (lastChange && lastChange.isNoNewline()) { - return lastChange.getRange(); + return lastChange.getRowRange(); } else { return nullIndexedRowRange; } @@ -102,32 +140,9 @@ export default class Hunk { toStringIn(bufferText) { let str = this.getHeader() + '\n'; - - let currentOffset = this.rowRange.startOffset; - for (const change of this.getChanges()) { - const range = change.getRowRange(); - if (range.startOffset !== currentOffset) { - const unchanged = new IndexedRowRange({ - bufferRange: [[0, 0], [0, 0]], - startOffset: currentOffset, - endOffset: range.startOffset, - }); - str += unchanged.toStringIn(bufferText, ' '); - } - - str += change.toStringIn(bufferText); - currentOffset = range.endOffset; - } - - if (currentOffset !== this.rowRange.endOffset) { - const unchanged = new IndexedRowRange({ - bufferRange: [[0, 0], [0, 0]], - startOffset: currentOffset, - endOffset: this.rowRange.endOffset, - }); - str += unchanged.toStringIn(bufferText, ' '); + for (const region of this.getRegions()) { + str += region.toStringIn(bufferText); } - return str; } } diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index ba412defca..671e9232bc 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -52,9 +52,9 @@ describe('Hunk', function() { }); assert.strictEqual(h.bufferRowCount(), 11); assert.lengthOf(h.getChanges(), 3); - assert.lengthOf(h.getAdditions(), 1); - assert.lengthOf(h.getDeletions(), 2); - assert.isFalse(h.getNoNewline().isPresent()); + assert.lengthOf(h.getAdditionRanges(), 1); + assert.lengthOf(h.getDeletionRanges(), 2); + assert.isFalse(h.getNoNewlineRange().isPresent()); }); it('creates its start range for decoration placement', function() { @@ -82,6 +82,74 @@ describe('Hunk', function() { assert.strictEqual(h.getHeader(), '@@ -0,2 +1,3 @@'); }); + it('returns a full set of covered regions, including unchanged', function() { + const h = new Hunk({ + ...attrs, + rowRange: new IndexedRowRange({ + bufferRange: [[0, 0], [11, 0]], + startOffset: 0, + endOffset: 120, + }), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [3, 0]], startOffset: 10, endOffset: 40})), + new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 50, endOffset: 70})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, 0]], startOffset: 70, endOffset: 100})), + ], + }); + + const regions = h.getRegions(); + assert.lengthOf(regions, 6); + + assert.isTrue(regions[0].isUnchanged()); + assert.deepEqual(regions[0].range.serialize(), {bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 10}); + + assert.isTrue(regions[1].isAddition()); + assert.deepEqual(regions[1].range.serialize(), {bufferRange: [[1, 0], [3, 0]], startOffset: 10, endOffset: 40}); + + assert.isTrue(regions[2].isUnchanged()); + assert.deepEqual(regions[2].range.serialize(), {bufferRange: [[4, 0], [4, 0]], startOffset: 40, endOffset: 50}); + + assert.isTrue(regions[3].isDeletion()); + assert.deepEqual(regions[3].range.serialize(), {bufferRange: [[5, 0], [6, 0]], startOffset: 50, endOffset: 70}); + + assert.isTrue(regions[4].isDeletion()); + assert.deepEqual(regions[4].range.serialize(), {bufferRange: [[7, 0], [9, 0]], startOffset: 70, endOffset: 100}); + + assert.isTrue(regions[5].isUnchanged()); + assert.deepEqual(regions[5].range.serialize(), {bufferRange: [[10, 0], [11, 0]], startOffset: 100, endOffset: 120}); + }); + + it('omits empty regions at the hunk beginning and end', function() { + const h = new Hunk({ + ...attrs, + rowRange: new IndexedRowRange({ + bufferRange: [[1, 0], [9, 0]], + startOffset: 10, + endOffset: 100, + }), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [3, 0]], startOffset: 10, endOffset: 40})), + new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 50, endOffset: 70})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, 0]], startOffset: 70, endOffset: 100})), + ], + }); + + const regions = h.getRegions(); + assert.lengthOf(regions, 4); + + assert.isTrue(regions[0].isAddition()); + assert.deepEqual(regions[0].range.serialize(), {bufferRange: [[1, 0], [3, 0]], startOffset: 10, endOffset: 40}); + + assert.isTrue(regions[1].isUnchanged()); + assert.deepEqual(regions[1].range.serialize(), {bufferRange: [[4, 0], [4, 0]], startOffset: 40, endOffset: 50}); + + assert.isTrue(regions[2].isDeletion()); + assert.deepEqual(regions[2].range.serialize(), {bufferRange: [[5, 0], [6, 0]], startOffset: 50, endOffset: 70}); + + assert.isTrue(regions[3].isDeletion()); + assert.deepEqual(regions[3].range.serialize(), {bufferRange: [[7, 0], [9, 0]], startOffset: 70, endOffset: 100}); + }); + it('returns a set of covered buffer rows', function() { const h = new Hunk({ ...attrs, @@ -135,9 +203,9 @@ describe('Hunk', function() { assert.strictEqual(inverted.getOldRowCount(), 3); assert.strictEqual(inverted.getNewRowCount(), 2); assert.strictEqual(inverted.getSectionHeading(), 'the-heading'); - assert.lengthOf(inverted.getAdditions(), 1); - assert.lengthOf(inverted.getDeletions(), 2); - assert.isTrue(inverted.getNoNewline().isPresent()); + assert.lengthOf(inverted.getAdditionRanges(), 1); + assert.lengthOf(inverted.getDeletionRanges(), 2); + assert.isTrue(inverted.getNoNewlineRange().isPresent()); }); describe('toStringIn()', function() { From 48382dca2449860c4fd3859d531ce81fe762d5a0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 16 Aug 2018 11:37:48 -0400 Subject: [PATCH 0097/4053] New tests for stage patch generation --- lib/models/patch/patch.js | 166 ++++++++++++++++++------ test/models/patch/patch.test.js | 217 ++++++++++++++++++++++++++++++-- 2 files changed, 335 insertions(+), 48 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index aebdd83a94..161547cff7 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -1,4 +1,65 @@ import Hunk from './hunk'; +import {Addition, Deletion, NoNewline} from './region'; + +class BufferBuilder { + constructor(original) { + this.originalBufferText = original; + this.bufferText = ''; + this.positionOffset = 0; + this.rowOffset = 0; + + this.hunkBufferText = ''; + this.hunkRowCount = 0; + this.hunkStartPositionOffset = 0; + this.hunkStartRowOffset = 0; + + this.lastOffset = 0; + } + + append(rowRange) { + this.hunkBufferText += this.originalBufferText.slice(rowRange.startOffset, rowRange.endOffset); + this.hunkRowCount += rowRange.bufferRowCount(); + } + + remove(rowRange) { + this.rowOffset -= rowRange.bufferRowCount(); + this.positionOffset -= rowRange.endOffset - rowRange.startOffset; + } + + latestHunkWasIncluded() { + this.bufferText += this.hunkBufferText; + + this.hunkBufferText = ''; + this.hunkRowCount = 0; + this.hunkStartPositionOffset = this.positionOffset; + this.hunkStartRowOffset = this.rowOffset; + } + + latestHunkWasDiscarded() { + this.rowOffset -= this.hunkRowCount; + this.positionOffset -= this.hunkBufferText.length; + + this.hunkBufferText = ''; + this.hunkRowCount = 0; + this.hunkStartPositionOffset = this.positionOffset; + this.hunkStartRowOffset = this.rowOffset; + } + + applyOffsetTo(rowRange) { + return rowRange.offsetBy(this.positionOffset, this.rowOffset); + } + + applyHunkOffsetsTo(rowRange) { + return rowRange.offsetBy( + this.hunkStartPositionOffset, this.hunkStartRowOffset, + this.positionOffset, this.rowOffset, + ); + } + + getBufferText() { + return this.bufferText; + } +} export default class Patch { constructor({status, hunks, bufferText}) { @@ -37,58 +98,93 @@ export default class Patch { }); } - getStagePatchForLines(lineSet) { + getStagePatchForLines(rowSet) { + const builder = new BufferBuilder(this.getBufferText()); const hunks = []; - let delta = 0; + + let newRowDelta = 0; for (const hunk of this.getHunks()) { - const additions = []; - const deletions = []; - let notAddedRowCount = 0; - let deletedRowCount = 0; - let notDeletedRowCount = 0; + const changes = []; + let noNewlineChange = null; + let selectedDeletionRowCount = 0; + let noNewlineRowCount = 0; - for (const change of hunk.getAdditions()) { - notAddedRowCount += change.bufferRowCount(); - for (const intersection of change.intersectRowsIn(lineSet, this.getBufferText())) { - notAddedRowCount -= intersection.bufferRowCount(); - additions.push(intersection); + for (const region of hunk.getRegions()) { + for (const {intersection, gap} of region.getRowRange().intersectRowsIn(rowSet, this.getBufferText(), true)) { + region.when({ + addition: () => { + if (gap) { + // Unselected addition: omit from new buffer + builder.remove(intersection); + } else { + // Selected addition: include in new patch + builder.append(intersection); + changes.push(new Addition( + builder.applyOffsetTo(intersection), + )); + } + }, + deletion: () => { + if (gap) { + // Unselected deletion: convert to context row + builder.append(intersection); + } else { + // Selected deletion: include in new patch + builder.append(intersection); + changes.push(new Deletion( + builder.applyOffsetTo(intersection), + )); + selectedDeletionRowCount += intersection.bufferRowCount(); + } + }, + unchanged: () => { + // Untouched context line: include in new patch + builder.append(intersection); + }, + nonewline: () => { + builder.append(intersection); + noNewlineChange = new NoNewline( + builder.applyOffsetTo(intersection), + ); + noNewlineRowCount += intersection.bufferRowCount(); + }, + }); } } - for (const change of hunk.getDeletions()) { - notDeletedRowCount += change.bufferRowCount(); - for (const intersection of change.intersectRowsIn(lineSet, this.getBufferText())) { - deletedRowCount += intersection.bufferRowCount(); - notDeletedRowCount -= intersection.bufferRowCount(); - deletions.push(intersection); + if (changes.length > 0) { + // Hunk contains at least one selected line + if (noNewlineChange !== null) { + changes.push(noNewlineChange); } - } - if (additions.length > 0 || deletions.length > 0) { - // Hunk contains at least one selected line + const rowRange = builder.applyHunkOffsetsTo(hunk.getRowRange()); + const newStartRow = hunk.getNewStartRow() + newRowDelta; + const newRowCount = rowRange.bufferRowCount() - selectedDeletionRowCount - noNewlineRowCount; + hunks.push(new Hunk({ oldStartRow: hunk.getOldStartRow(), - newStartRow: hunk.getNewStartRow() + delta, oldRowCount: hunk.getOldRowCount(), - newRowCount: hunk.bufferRowCount() - deletedRowCount, + newStartRow, + newRowCount, sectionHeading: hunk.getSectionHeading(), - rowRange: hunk.getRowRange(), - additions, - deletions, - noNewline: hunk.getNoNewline(), + rowRange, + changes, })); - } - delta += notDeletedRowCount - notAddedRowCount; - } + newRowDelta += newRowCount - hunk.getNewRowCount(); - if (this.getStatus() === 'deleted') { - // Set status to modified - return this.clone({hunks, status: 'modified'}); - } else { - return this.clone({hunks}); + builder.latestHunkWasIncluded(); + } else { + newRowDelta += hunk.getOldRowCount() - hunk.getNewRowCount(); + + builder.latestHunkWasDiscarded(); + } } + + const status = this.getStatus() === 'deleted' ? 'modified' : this.getStatus(); + return this.clone({hunks, status, bufferText: builder.getBufferText()}); } getUnstagePatchForLines(lineSet) { diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 131ae9faa1..5801e0c873 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -1,6 +1,8 @@ import Patch, {nullPatch} from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; -import IndexedRowRange, {nullIndexedRowRange} from '../../../lib/models/indexed-row-range'; +import IndexedRowRange from '../../../lib/models/indexed-row-range'; +import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; +import {assertInPatch} from '../../helpers'; describe('Patch', function() { it('has some standard accessors', function() { @@ -16,6 +18,39 @@ describe('Patch', function() { assert.strictEqual(p.getByteSize(), 12); }); + it('computes the total changed line count', function() { + const hunks = [ + new Hunk({ + oldStartRow: 0, + newStartRow: 0, + oldRowCount: 1, + newRowCount: 1, + sectionHeading: 'zero', + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [5, 0]], startOffset: 0, endOffset: 30}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25})), + ], + }), + new Hunk({ + oldStartRow: 0, + newStartRow: 0, + oldRowCount: 1, + newRowCount: 1, + sectionHeading: 'one', + rowRange: new IndexedRowRange({bufferRange: [[6, 0], [15, 0]], startOffset: 30, endOffset: 80}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [7, 0]], startOffset: 35, endOffset: 40})), + new Deletion(new IndexedRowRange({bufferRange: [[9, 0], [11, 0]], startOffset: 45, endOffset: 60})), + new Addition(new IndexedRowRange({bufferRange: [[12, 0], [14, 0]], startOffset: 60, endOffset: 75})), + ], + }), + ]; + const p = new Patch({status: 'modified', hunks, bufferText: 'bufferText'}); + + assert.strictEqual(p.getChangedLineCount(), 10); + }); + it('clones itself with optionally overridden properties', function() { const original = new Patch({status: 'modified', hunks: [], bufferText: 'bufferText'}); @@ -31,7 +66,7 @@ describe('Patch', function() { assert.deepEqual(dup1.getHunks(), []); assert.strictEqual(dup1.getBufferText(), 'bufferText'); - const hunks = [new Hunk({})]; + const hunks = [new Hunk({changes: []})]; const dup2 = original.clone({hunks}); assert.notStrictEqual(dup2, original); assert.strictEqual(dup2.getStatus(), 'modified'); @@ -56,7 +91,7 @@ describe('Patch', function() { assert.deepEqual(dup0.getHunks(), []); assert.strictEqual(dup0.getBufferText(), ''); - const hunks = [new Hunk({})]; + const hunks = [new Hunk({changes: []})]; const dup1 = nullPatch.clone({hunks}); assert.notStrictEqual(dup1, nullPatch); assert.isNull(dup1.getStatus()); @@ -70,11 +105,109 @@ describe('Patch', function() { assert.strictEqual(dup2.getBufferText(), 'changed'); }); + describe('stage patch generation', function() { + it('creates a patch that applies selected lines from only a single hunk', function() { + const patch = buildPatchFixture(); + const stagePatch = patch.getStagePatchForLines(new Set([8, 13, 14, 16])); + // buffer rows: 0 1 2 3 4 5 6 7 8 9 + const expectedBufferText = '0007\n0008\n0010\n0011\n0012\n0013\n0014\n0015\n0016\n0018\n'; + assert.strictEqual(stagePatch.getBufferText(), expectedBufferText); + assertInPatch(stagePatch).hunks( + { + startRow: 0, + endRow: 9, + header: '@@ -12,9 +12,7 @@', + changes: [ + {kind: 'addition', string: '+0008\n', range: [[1, 0], [1, 0]]}, + {kind: 'deletion', string: '-0013\n-0014\n', range: [[5, 0], [6, 0]]}, + {kind: 'deletion', string: '-0016\n', range: [[8, 0], [8, 0]]}, + ], + }, + ); + }); + + it('creates a patch that applies selected lines from several hunks', function() { + const patch = buildPatchFixture(); + const stagePatch = patch.getStagePatchForLines(new Set([1, 5, 15, 16, 17, 25])); + const expectedBufferText = + // buffer rows + // 0 1 2 3 4 + '0000\n0001\n0002\n0005\n0006\n' + + // 5 6 7 8 9 10 11 12 13 14 + '0007\n0010\n0011\n0012\n0013\n0014\n0015\n0016\n0017\n0018\n' + + // 15 16 17 + '0024\n0025\n No newline at end of file\n'; + assert.strictEqual(stagePatch.getBufferText(), expectedBufferText); + assertInPatch(stagePatch).hunks( + { + startRow: 0, + endRow: 4, + header: '@@ -3,4 +3,4 @@', + changes: [ + {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, 0]]}, + {kind: 'addition', string: '+0005\n', range: [[3, 0], [3, 0]]}, + ], + }, + { + startRow: 5, + endRow: 14, + header: '@@ -12,9 +12,8 @@', + changes: [ + {kind: 'deletion', string: '-0015\n-0016\n', range: [[11, 0], [12, 0]]}, + {kind: 'addition', string: '+0017\n', range: [[13, 0], [13, 0]]}, + ], + }, + { + startRow: 15, + endRow: 17, + header: '@@ -31,1 +30,2 @@', + changes: [ + {kind: 'addition', string: '+0025\n', range: [[16, 0], [16, 0]]}, + {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[17, 0], [17, 0]]}, + ], + }, + ); + }); + + it('returns a modification patch if original patch is a deletion', function() { + const bufferText = 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\n'; + + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 5, + newStartRow: 1, + newRowCount: 0, + sectionHeading: 'zero', + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [5, 0]], startOffset: 0, endOffset: 43}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[0, 0], [5, 0]], startOffset: 0, endOffset: 43})), + ], + }), + ]; + + const patch = new Patch({status: 'deleted', hunks, bufferText}); + + const stagedPatch = patch.getStagePatchForLines(new Set([1, 3, 4])); + assert.strictEqual(stagedPatch.getStatus(), 'modified'); + assertInPatch(stagedPatch).hunks( + { + startRow: 0, + endRow: 5, + header: '@@ -1,5 +1,3 @@', + changes: [ + {kind: 'deletion', string: '-line-1\n', range: [[1, 0], [1, 0]]}, + {kind: 'deletion', string: '-line-3\n-line-4\n', range: [[3, 0], [4, 0]]}, + ], + }, + ); + }); + }); it('prints itself as an apply-ready string', function() { const bufferText = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999\n'; // old: 0000.2222.3333.4444.5555.6666.7777.8888.9999. // new: 0000.1111.2222.3333.4444.5555.6666.9999. - // 0000.1111.2222.3333.4444.5555.6666.7777.8888.9999. + // patch buffer: 0000.1111.2222.3333.4444.5555.6666.7777.8888.9999. const hunk0 = new Hunk({ oldStartRow: 0, @@ -83,11 +216,9 @@ describe('Patch', function() { newRowCount: 3, sectionHeading: 'zero', rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), - additions: [ - new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), ], - deletions: [], - noNewline: nullIndexedRowRange, }); const hunk1 = new Hunk({ @@ -96,12 +227,10 @@ describe('Patch', function() { oldRowCount: 4, newRowCount: 2, sectionHeading: 'one', - rowRange: new IndexedRowRange({bufferRange: [[6, 0], [10, 0]], startOffset: 30, endOffset: 55}), - additions: [], - deletions: [ - new IndexedRowRange({bufferRange: [[7, 0], [8, 0]], startOffset: 35, endOffset: 45}), + rowRange: new IndexedRowRange({bufferRange: [[6, 0], [9, 0]], startOffset: 30, endOffset: 50}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [8, 0]], startOffset: 35, endOffset: 45})), ], - noNewline: nullIndexedRowRange, }); const p = new Patch({status: 'modified', hunks: [hunk0, hunk1], bufferText}); @@ -127,3 +256,65 @@ describe('Patch', function() { assert.isFalse(nullPatch.isPresent()); }); }); + +function buildPatchFixture() { + const bufferText = + '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n' + + '0010\n0011\n0012\n0013\n0014\n0015\n0016\n0017\n0018\n0019\n' + + '0020\n0021\n0022\n0023\n0024\n0025\n' + + ' No newline at end of file\n'; + + const hunks = [ + new Hunk({ + oldStartRow: 3, + oldRowCount: 4, + newStartRow: 3, + newRowCount: 5, + sectionHeading: 'zero', + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [6, 0]], startOffset: 0, endOffset: 35}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 5, endOffset: 15})), + new Addition(new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 15, endOffset: 30})), + ], + }), + new Hunk({ + oldStartRow: 12, + oldRowCount: 9, + newStartRow: 13, + newRowCount: 7, + sectionHeading: 'one', + rowRange: new IndexedRowRange({bufferRange: [[7, 0], [18, 0]], startOffset: 35, endOffset: 95}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[8, 0], [9, 0]], startOffset: 40, endOffset: 50})), + new Deletion(new IndexedRowRange({bufferRange: [[12, 0], [16, 0]], startOffset: 60, endOffset: 85})), + new Addition(new IndexedRowRange({bufferRange: [[17, 0], [17, 0]], startOffset: 85, endOffset: 90})), + ], + }), + new Hunk({ + oldStartRow: 26, + oldRowCount: 3, + newStartRow: 25, + newRowCount: 4, + sectionHeading: 'two', + rowRange: new IndexedRowRange({bufferRange: [[19, 0], [23, 0]], startOffset: 95, endOffset: 120}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[20, 0], [20, 0]], startOffset: 100, endOffset: 105})), + new Deletion(new IndexedRowRange({bufferRange: [[21, 0], [22, 0]], startOffset: 105, endOffset: 115})), + ], + }), + new Hunk({ + oldStartRow: 31, + oldRowCount: 1, + newStartRow: 31, + newRowCount: 2, + sectionHeading: 'three', + rowRange: new IndexedRowRange({bufferRange: [[24, 0], [26, 0]], startOffset: 120, endOffset: 157}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[25, 0], [25, 0]], startOffset: 125, endOffset: 130})), + new NoNewline(new IndexedRowRange({bufferRange: [[26, 0], [26, 0]], startOffset: 130, endOffset: 157})), + ], + }), + ]; + + return new Patch({status: 'modified', hunks, bufferText}); +} From b8fa5b18e9a26825094c4cf9acd3de12b44f5949 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 16 Aug 2018 15:52:20 -0400 Subject: [PATCH 0098/4053] Unstage patch generation --- lib/models/patch/builder.js | 2 +- lib/models/patch/patch.js | 121 ++++++++++++++++++++---------- test/models/patch/patch.test.js | 126 ++++++++++++++++++++++++++++++-- 3 files changed, 205 insertions(+), 44 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index ac697d304f..457d02ef46 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -2,7 +2,7 @@ import Hunk from './hunk'; import File, {nullFile} from './file'; import Patch, {nullPatch} from './patch'; import IndexedRowRange from '../indexed-row-range'; -import {Addition, Deletion, NoNewline} from './change'; +import {Addition, Deletion, NoNewline} from './region'; import FilePatch from './file-patch'; export default function buildFilePatch(diffs) { diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 161547cff7..b81bf96f44 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -187,58 +187,91 @@ export default class Patch { return this.clone({hunks, status, bufferText: builder.getBufferText()}); } - getUnstagePatchForLines(lineSet) { - let delta = 0; + getUnstagePatchForLines(rowSet) { + const builder = new BufferBuilder(this.getBufferText()); const hunks = []; - let bufferText = this.getBufferText(); - let bufferOffset = 0; + let newRowDelta = 0; for (const hunk of this.getHunks()) { - const additions = []; - const deletions = []; - let notAddedRowCount = 0; - let addedRowCount = 0; - let notDeletedRowCount = 0; - - for (const change of hunk.getAdditions()) { - notDeletedRowCount += change.bufferRowCount(); - for (const intersection of change.intersectRowsIn(lineSet, bufferText)) { - notDeletedRowCount -= intersection.bufferRowCount(); - deletions.push(intersection); - } - } + const changes = []; + let noNewlineChange = null; + let contextRowCount = 0; + let additionRowCount = 0; + let deletionRowCount = 0; - for (const change of hunk.getDeletions()) { - notAddedRowCount = change.bufferRowCount(); - for (const intersection of change.intersectRowsIn(lineSet, bufferText)) { - addedRowCount += intersection.bufferRowCount(); - notAddedRowCount -= intersection.bufferRowCount(); - additions.push(intersection); + for (const region of hunk.getRegions()) { + for (const {intersection, gap} of region.getRowRange().intersectRowsIn(rowSet, this.getBufferText(), true)) { + region.when({ + addition: () => { + if (gap) { + // Unselected addition: become a context line. + builder.append(intersection); + contextRowCount += intersection.bufferRowCount(); + } else { + // Selected addition: become a deletion. + builder.append(intersection); + changes.push(new Deletion( + builder.applyOffsetTo(intersection), + )); + deletionRowCount += intersection.bufferRowCount(); + } + }, + deletion: () => { + if (gap) { + // Non-selected deletion: omit from new buffer. + builder.remove(intersection); + } else { + // Selected deletion: becomes an addition + builder.append(intersection); + changes.push(new Addition( + builder.applyOffsetTo(intersection), + )); + additionRowCount += intersection.bufferRowCount(); + } + }, + unchanged: () => { + // Untouched context line: include in new patch. + builder.append(intersection); + contextRowCount += intersection.bufferRowCount(); + }, + nonewline: () => { + // Nonewline marker: include in new patch. + builder.append(intersection); + noNewlineChange = new NoNewline( + builder.applyOffsetTo(intersection), + ); + }, + }); } } - if (additions.length > 0 || deletions.length > 0) { + if (changes.length > 0) { // Hunk contains at least one selected line + if (noNewlineChange !== null) { + changes.push(noNewlineChange); + } + hunks.push(new Hunk({ - oldStartRow: hunk.getOldStartRow() + delta, - newStartRow: hunk.getNewStartRow(), - oldRowCount: hunk.bufferRowCount() - addedRowCount, - newRowCount: hunk.getNewRowCount(), + oldStartRow: hunk.getNewStartRow(), + oldRowCount: contextRowCount + deletionRowCount, + newStartRow: hunk.getNewStartRow() + newRowDelta, + newRowCount: contextRowCount + additionRowCount, sectionHeading: hunk.getSectionHeading(), - rowRange: hunk.getRowRange(), - additions, - deletions, - noNewline: hunk.getNoNewline(), + rowRange: builder.applyHunkOffsetsTo(hunk.getRowRange()), + changes, })); + + builder.latestHunkWasIncluded(); + } else { + builder.latestHunkWasDiscarded(); } - delta += notAddedRowCount - notDeletedRowCount; - } - if (this.getStatus() === 'added') { - return this.clone({hunks, bufferText, status: 'modified'}); - } else { - return this.clone({hunks, bufferText}); + // (contextRowCount + additionRowCount) - (contextRowCount + deletionRowCount) + newRowDelta += additionRowCount - deletionRowCount; } + + const status = this.getStatus() === 'added' ? 'modified' : this.getStatus(); + return this.clone({hunks, status, bufferText: builder.getBufferText()}); } toString() { @@ -282,6 +315,18 @@ export const nullPatch = { } }, + getStagePatchForLines() { + return this; + }, + + getUnstagePatchForLines() { + return this; + }, + + toString() { + return ''; + }, + isPresent() { return false; }, diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 5801e0c873..c6304f62d5 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -160,7 +160,7 @@ describe('Patch', function() { { startRow: 15, endRow: 17, - header: '@@ -31,1 +30,2 @@', + header: '@@ -32,1 +31,2 @@', changes: [ {kind: 'addition', string: '+0025\n', range: [[16, 0], [16, 0]]}, {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[17, 0], [17, 0]]}, @@ -202,7 +202,122 @@ describe('Patch', function() { }, ); }); + + it('returns a nullPatch as a nullPatch', function() { + assert.strictEqual(nullPatch.getStagePatchForLines(new Set([1, 2, 3])), nullPatch); + }); }); + + describe('unstage patch generation', function() { + it('creates a patch that updates the index to unapply selected lines from a single hunk', function() { + const patch = buildPatchFixture(); + const unstagePatch = patch.getUnstagePatchForLines(new Set([8, 12, 13])); + assert.strictEqual( + unstagePatch.getBufferText(), + // 0 1 2 3 4 5 6 7 8 + '0007\n0008\n0009\n0010\n0011\n0012\n0013\n0017\n0018\n', + ); + assertInPatch(unstagePatch).hunks( + { + startRow: 0, + endRow: 8, + header: '@@ -13,7 +13,8 @@', + changes: [ + {kind: 'deletion', string: '-0008\n', range: [[1, 0], [1, 0]]}, + {kind: 'addition', string: '+0012\n+0013\n', range: [[5, 0], [6, 0]]}, + ], + }, + ); + }); + + it('creates a patch that updates the index to unapply lines from several hunks', function() { + const patch = buildPatchFixture(); + const unstagePatch = patch.getUnstagePatchForLines(new Set([1, 4, 5, 16, 17, 20, 25])); + assert.strictEqual( + unstagePatch.getBufferText(), + // 0 1 2 3 4 5 + '0000\n0001\n0003\n0004\n0005\n0006\n' + + // 6 7 8 9 10 11 12 13 + '0007\n0008\n0009\n0010\n0011\n0016\n0017\n0018\n' + + // 14 15 16 + '0019\n0020\n0023\n' + + // 17 18 19 + '0024\n0025\n No newline at end of file\n', + ); + assertInPatch(unstagePatch).hunks( + { + startRow: 0, + endRow: 5, + header: '@@ -3,5 +3,4 @@', + changes: [ + {kind: 'addition', string: '+0001\n', range: [[1, 0], [1, 0]]}, + {kind: 'deletion', string: '-0004\n-0005\n', range: [[3, 0], [4, 0]]}, + ], + }, + { + startRow: 6, + endRow: 13, + header: '@@ -13,7 +12,7 @@', + changes: [ + {kind: 'addition', string: '+0016\n', range: [[11, 0], [11, 0]]}, + {kind: 'deletion', string: '-0017\n', range: [[12, 0], [12, 0]]}, + ], + }, + { + startRow: 14, + endRow: 16, + header: '@@ -25,3 +24,2 @@', + changes: [ + {kind: 'deletion', string: '-0020\n', range: [[15, 0], [15, 0]]}, + ], + }, + { + startRow: 17, + endRow: 19, + header: '@@ -30,2 +28,1 @@', + changes: [ + {kind: 'deletion', string: '-0025\n', range: [[18, 0], [18, 0]]}, + {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[19, 0], [19, 0]]}, + ], + }, + ); + }); + + it('returns a modification if original patch is an addition', function() { + const bufferText = '0000\n0001\n0002\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 0, + newStartRow: 1, + newRowCount: 3, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + ], + }), + ]; + const patch = new Patch({status: 'added', hunks, bufferText}); + const unstagePatch = patch.getUnstagePatchForLines(new Set([1, 2])); + assert.strictEqual(unstagePatch.getStatus(), 'modified'); + assert.strictEqual(unstagePatch.getBufferText(), '0000\n0001\n0002\n'); + assertInPatch(unstagePatch).hunks( + { + startRow: 0, + endRow: 2, + header: '@@ -1,3 +1,1 @@', + changes: [ + {kind: 'deletion', string: '-0001\n-0002\n', range: [[1, 0], [2, 0]]}, + ], + }, + ); + }); + + it('returns a nullPatch as a nullPatch', function() { + assert.strictEqual(nullPatch.getUnstagePatchForLines(new Set([1, 2, 3])), nullPatch); + }); + }); + it('prints itself as an apply-ready string', function() { const bufferText = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999\n'; // old: 0000.2222.3333.4444.5555.6666.7777.8888.9999. @@ -254,6 +369,7 @@ describe('Patch', function() { assert.strictEqual(nullPatch.getBufferText(), ''); assert.strictEqual(nullPatch.getByteSize(), 0); assert.isFalse(nullPatch.isPresent()); + assert.strictEqual(nullPatch.toString(), ''); }); }); @@ -292,9 +408,9 @@ function buildPatchFixture() { }), new Hunk({ oldStartRow: 26, - oldRowCount: 3, + oldRowCount: 4, newStartRow: 25, - newRowCount: 4, + newRowCount: 3, sectionHeading: 'two', rowRange: new IndexedRowRange({bufferRange: [[19, 0], [23, 0]], startOffset: 95, endOffset: 120}), changes: [ @@ -303,9 +419,9 @@ function buildPatchFixture() { ], }), new Hunk({ - oldStartRow: 31, + oldStartRow: 32, oldRowCount: 1, - newStartRow: 31, + newStartRow: 30, newRowCount: 2, sectionHeading: 'three', rowRange: new IndexedRowRange({bufferRange: [[24, 0], [26, 0]], startOffset: 120, endOffset: 157}), From 15b692504f9f1387038a1c40a7ec342751fc9e10 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 16 Aug 2018 15:54:00 -0400 Subject: [PATCH 0099/4053] Delete some unused files --- lib/models/file-patch.js | 372 --------------------------------------- notes.txt | 66 ------- stage.patch | 20 --- unstage.patch | 15 -- 4 files changed, 473 deletions(-) delete mode 100644 lib/models/file-patch.js delete mode 100644 notes.txt delete mode 100644 stage.patch delete mode 100644 unstage.patch diff --git a/lib/models/file-patch.js b/lib/models/file-patch.js deleted file mode 100644 index 6641974de7..0000000000 --- a/lib/models/file-patch.js +++ /dev/null @@ -1,372 +0,0 @@ -import Hunk from './hunk'; -import {toGitPathSep} from '../helpers'; - -class File { - static empty() { - return new File({path: null, mode: null, symlink: null}); - } - - constructor({path, mode, symlink}) { - this.path = path; - this.mode = mode; - this.symlink = symlink; - } - - getPath() { - return this.path; - } - - getMode() { - return this.mode; - } - - isSymlink() { - return this.getMode() === '120000'; - } - - isRegularFile() { - return this.getMode() === '100644' || this.getMode() === '100755'; - } - - getSymlink() { - return this.symlink; - } - - clone(opts = {}) { - return new File({ - path: opts.path !== undefined ? opts.path : this.path, - mode: opts.mode !== undefined ? opts.mode : this.mode, - symlink: opts.symlink !== undefined ? opts.symlink : this.symlink, - }); - } -} - -class Patch { - constructor({status, hunks}) { - this.status = status; - this.hunks = hunks; - } - - getStatus() { - return this.status; - } - - getHunks() { - return this.hunks; - } - - getByteSize() { - return this.getHunks().reduce((acc, hunk) => acc + hunk.getByteSize(), 0); - } - - clone(opts = {}) { - return new Patch({ - status: opts.status !== undefined ? opts.status : this.status, - hunks: opts.hunks !== undefined ? opts.hunks : this.hunks, - }); - } -} - -export default class FilePatch { - static File = File; - static Patch = Patch; - - constructor(oldFile, newFile, patch) { - this.oldFile = oldFile; - this.newFile = newFile; - this.patch = patch; - - this.changedLineCount = this.getHunks().reduce((acc, hunk) => { - return acc + hunk.getLines().filter(line => line.isChanged()).length; - }, 0); - } - - clone(opts = {}) { - const oldFile = opts.oldFile !== undefined ? opts.oldFile : this.getOldFile(); - const newFile = opts.newFile !== undefined ? opts.newFile : this.getNewFile(); - const patch = opts.patch !== undefined ? opts.patch : this.patch; - return new FilePatch(oldFile, newFile, patch); - } - - getOldFile() { - return this.oldFile; - } - - getNewFile() { - return this.newFile; - } - - getPatch() { - return this.patch; - } - - getOldPath() { - return this.getOldFile().getPath(); - } - - getNewPath() { - return this.getNewFile().getPath(); - } - - getOldMode() { - return this.getOldFile().getMode(); - } - - getNewMode() { - return this.getNewFile().getMode(); - } - - getOldSymlink() { - return this.getOldFile().getSymlink(); - } - - getNewSymlink() { - return this.getNewFile().getSymlink(); - } - - getByteSize() { - return this.getPatch().getByteSize(); - } - - didChangeExecutableMode() { - const oldMode = this.getOldMode(); - const newMode = this.getNewMode(); - - if (!oldMode || !newMode) { - // Addition or deletion - return false; - } - - return oldMode === '100755' && newMode !== '100755' || - oldMode !== '100755' && newMode === '100755'; - } - - didChangeSymlinkMode() { - const oldMode = this.getOldMode(); - const newMode = this.getNewMode(); - return oldMode === '120000' && newMode !== '120000' || - oldMode !== '120000' && newMode === '120000'; - } - - hasSymlink() { - return this.getOldFile().getSymlink() || this.getNewFile().getSymlink(); - } - - hasTypechange() { - const oldFile = this.getOldFile(); - const newFile = this.getNewFile(); - return (oldFile.isSymlink() && newFile.isRegularFile()) || - (newFile.isSymlink() && oldFile.isRegularFile()); - } - - getPath() { - return this.getOldPath() || this.getNewPath(); - } - - getStatus() { - return this.getPatch().getStatus(); - } - - getHunks() { - return this.getPatch().getHunks(); - } - - getStagePatchForHunk(selectedHunk) { - return this.getStagePatchForLines(new Set(selectedHunk.getLines())); - } - - getStagePatchForLines(selectedLines) { - const wholeFileSelected = this.changedLineCount === [...selectedLines].filter(line => line.isChanged()).length; - if (wholeFileSelected) { - if (this.hasTypechange() && this.getStatus() === 'deleted') { - // handle special case when symlink is created where a file was deleted. In order to stage the file deletion, - // we must ensure that the created file patch has no new file - return this.clone({ - newFile: File.empty(), - }); - } else { - return this; - } - } else { - const hunks = this.getStagePatchHunks(selectedLines); - if (this.getStatus() === 'deleted') { - // Set status to modified - return this.clone({ - newFile: this.getOldFile(), - patch: this.getPatch().clone({hunks, status: 'modified'}), - }); - } else { - return this.clone({ - patch: this.getPatch().clone({hunks}), - }); - } - } - } - - getStagePatchHunks(selectedLines) { - let delta = 0; - const hunks = []; - for (const hunk of this.getHunks()) { - const newStartRow = (hunk.getNewStartRow() || 1) + delta; - let newLineNumber = newStartRow; - const lines = []; - let hunkContainsSelectedLines = false; - for (const line of hunk.getLines()) { - if (line.getStatus() === 'nonewline') { - lines.push(line.copy({oldLineNumber: -1, newLineNumber: -1})); - } else if (selectedLines.has(line)) { - hunkContainsSelectedLines = true; - if (line.getStatus() === 'deleted') { - lines.push(line.copy()); - } else { - lines.push(line.copy({newLineNumber: newLineNumber++})); - } - } else if (line.getStatus() === 'deleted') { - lines.push(line.copy({newLineNumber: newLineNumber++, status: 'unchanged'})); - } else if (line.getStatus() === 'unchanged') { - lines.push(line.copy({newLineNumber: newLineNumber++})); - } - } - const newRowCount = newLineNumber - newStartRow; - if (hunkContainsSelectedLines) { - // eslint-disable-next-line max-len - hunks.push(new Hunk(hunk.getOldStartRow(), newStartRow, hunk.getOldRowCount(), newRowCount, hunk.getSectionHeading(), lines)); - } - delta += newRowCount - hunk.getNewRowCount(); - } - return hunks; - } - - getUnstagePatch() { - let invertedStatus; - switch (this.getStatus()) { - case 'modified': - invertedStatus = 'modified'; - break; - case 'added': - invertedStatus = 'deleted'; - break; - case 'deleted': - invertedStatus = 'added'; - break; - default: - // throw new Error(`Unknown Status: ${this.getStatus()}`); - } - const invertedHunks = this.getHunks().map(h => h.invert()); - return this.clone({ - oldFile: this.getNewFile(), - newFile: this.getOldFile(), - patch: this.getPatch().clone({ - status: invertedStatus, - hunks: invertedHunks, - }), - }); - } - - getUnstagePatchForHunk(hunk) { - return this.getUnstagePatchForLines(new Set(hunk.getLines())); - } - - getUnstagePatchForLines(selectedLines) { - if (this.changedLineCount === [...selectedLines].filter(line => line.isChanged()).length) { - if (this.hasTypechange() && this.getStatus() === 'added') { - // handle special case when a file was created after a symlink was deleted. - // In order to unstage the file creation, we must ensure that the unstage patch has no new file, - // so when the patch is applied to the index, there file will be removed from the index - return this.clone({ - oldFile: File.empty(), - }).getUnstagePatch(); - } else { - return this.getUnstagePatch(); - } - } - - const hunks = this.getUnstagePatchHunks(selectedLines); - if (this.getStatus() === 'added') { - return this.clone({ - oldFile: this.getNewFile(), - patch: this.getPatch().clone({hunks, status: 'modified'}), - }).getUnstagePatch(); - } else { - return this.clone({ - patch: this.getPatch().clone({hunks}), - }).getUnstagePatch(); - } - } - - getUnstagePatchHunks(selectedLines) { - let delta = 0; - const hunks = []; - for (const hunk of this.getHunks()) { - const oldStartRow = (hunk.getOldStartRow() || 1) + delta; - let oldLineNumber = oldStartRow; - const lines = []; - let hunkContainsSelectedLines = false; - for (const line of hunk.getLines()) { - if (line.getStatus() === 'nonewline') { - lines.push(line.copy({oldLineNumber: -1, newLineNumber: -1})); - } else if (selectedLines.has(line)) { - hunkContainsSelectedLines = true; - if (line.getStatus() === 'added') { - lines.push(line.copy()); - } else { - lines.push(line.copy({oldLineNumber: oldLineNumber++})); - } - } else if (line.getStatus() === 'added') { - lines.push(line.copy({oldLineNumber: oldLineNumber++, status: 'unchanged'})); - } else if (line.getStatus() === 'unchanged') { - lines.push(line.copy({oldLineNumber: oldLineNumber++})); - } - } - const oldRowCount = oldLineNumber - oldStartRow; - if (hunkContainsSelectedLines) { - // eslint-disable-next-line max-len - hunks.push(new Hunk(oldStartRow, hunk.getNewStartRow(), oldRowCount, hunk.getNewRowCount(), hunk.getSectionHeading(), lines)); - } - delta += oldRowCount - hunk.getOldRowCount(); - } - return hunks; - } - - toString() { - if (this.hasTypechange()) { - const left = this.clone({ - newFile: File.empty(), - patch: this.getOldSymlink() ? new Patch({status: 'deleted', hunks: []}) : this.getPatch(), - }); - const right = this.clone({ - oldFile: File.empty(), - patch: this.getNewSymlink() ? new Patch({status: 'added', hunks: []}) : this.getPatch(), - }); - - return left.toString() + right.toString(); - } else if (this.getStatus() === 'added' && this.getNewFile().isSymlink()) { - const symlinkPath = this.getNewSymlink(); - return this.getHeaderString() + `@@ -0,0 +1 @@\n+${symlinkPath}\n\\ No newline at end of file\n`; - } else if (this.getStatus() === 'deleted' && this.getOldFile().isSymlink()) { - const symlinkPath = this.getOldSymlink(); - return this.getHeaderString() + `@@ -1 +0,0 @@\n-${symlinkPath}\n\\ No newline at end of file\n`; - } else { - return this.getHeaderString() + this.getHunks().map(h => h.toString()).join(''); - } - } - - getHeaderString() { - const fromPath = this.getOldPath() || this.getNewPath(); - const toPath = this.getNewPath() || this.getOldPath(); - let header = `diff --git a/${toGitPathSep(fromPath)} b/${toGitPathSep(toPath)}`; - header += '\n'; - if (this.getStatus() === 'added') { - header += `new file mode ${this.getNewMode()}`; - header += '\n'; - } else if (this.getStatus() === 'deleted') { - header += `deleted file mode ${this.getOldMode()}`; - header += '\n'; - } - header += this.getOldPath() ? `--- a/${toGitPathSep(this.getOldPath())}` : '--- /dev/null'; - header += '\n'; - header += this.getNewPath() ? `+++ b/${toGitPathSep(this.getNewPath())}` : '+++ /dev/null'; - header += '\n'; - return header; - } -} diff --git a/notes.txt b/notes.txt deleted file mode 100644 index c323737af0..0000000000 --- a/notes.txt +++ /dev/null @@ -1,66 +0,0 @@ -file in index: ------------------ -00: aaa -01: line-0 -02: line-1 -03: line-2 -04: bbb -05: ccc -06: ddd -07: line-3 -08: line-6 -09: line-7 -10: line-8 -11: eee -12: fff -13: ggg -14: hhh -15: iii -16: jjj -17: kkk -18: lll -19: mmm -20: nnn -21: line-12 -22: line-13 ------------------ - -file on HEAD: ------------------ -00: aaa -01: line-2 -02: bbb -03: ccc -04: ddd -05: line-3 -06: line-4 -07: line-5 -08: line-9 -09: line-10 -10: eee -11: fff -12: ggg -13: hhh -14: iii -15: jjj -16: kkk -17: lll -18: mmm -19: nnn -20: line-11 -21: line-13 ------------------ - -unstage patch ------------------ -@@ -7,4 +7,4 @@ - line-3 -+line-4 -+line-5 --line-6 --line-7 - line-8 -@@ -21,2 +21,3 @@ -+line-11 - line-12 - line-13 diff --git a/stage.patch b/stage.patch deleted file mode 100644 index d820eaef0d..0000000000 --- a/stage.patch +++ /dev/null @@ -1,20 +0,0 @@ -dif --git a/a.txt b/a.txt ---- a/a.txt -+++ b/a.txt -@@ -2,1 +2,3 @@ -+line-0 -+line-1 - line-2 -@@ -6,5 +8,4 @@ - line-3 --line-4 - --line-5 - -+line-6 - -+line-7 - -+line-8 --line-9 --line-10 -@@ -20,2 +21,2 @@ --line-11 - -+line-12 - - line-13 - diff --git a/unstage.patch b/unstage.patch deleted file mode 100644 index d66cfb3978..0000000000 --- a/unstage.patch +++ /dev/null @@ -1,15 +0,0 @@ -dif --git a/a.txt b/a.txt ---- a/a.txt -+++ b/a.txt -@@ -8,4 +8,4 @@ - line-3 -+line-4 -+line-5 --line-6 --line-7 - line-8 -@@ -22,2 +22,2 @@ -+line-11 --line-12 - line-13 -\ No newline at end of file From 40d345ca5f928795c7d49c85e7b8d24cab767076 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 16 Aug 2018 16:12:00 -0400 Subject: [PATCH 0100/4053] Start FilePatch tests from scratch --- test/models/patch/file-patch.test.js | 285 ++++----------------------- 1 file changed, 42 insertions(+), 243 deletions(-) diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 594beb7732..4be79c00cf 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -1,263 +1,62 @@ -import {buildFilePatch} from '../../../lib/models/patch'; +import FilePatch from '../../../lib/models/patch/file-patch'; +import File from '../../../lib/models/patch/file'; +import Patch from '../../../lib/models/patch/patch'; +import Hunk from '../../../lib/models/patch/hunk'; +import {Addition, Deletion} from '../../../lib/models/patch/region'; +import IndexedRowRange from '../../../lib/models/indexed-row-range'; import {assertInFilePatch} from '../../helpers'; describe('FilePatch', function() { describe('getStagePatchForLines()', function() { it('returns a new FilePatch that applies only the selected lines', function() { - const filePatch = buildFilePatch([{ - oldPath: 'a.txt', - oldMode: '100644', - newPath: 'a.txt', - newMode: '100644', - hunks: [ - { - oldStartLine: 1, - oldLineCount: 1, - newStartLine: 1, - newLineCount: 3, - lines: [ - '+line-0', - '+line-1', - ' line-2', - ], - }, - { - oldStartLine: 5, - oldLineCount: 5, - newStartLine: 7, - newLineCount: 4, - lines: [ - ' line-3', - '-line-4', - '-line-5', - '+line-6', - '+line-7', - '+line-8', - '-line-9', - '-line-10', - ], - }, - { - oldStartLine: 20, - oldLineCount: 2, - newStartLine: 19, - newLineCount: 2, - lines: [ - '-line-11', - '+line-12', - ' line-13', - '\\No newline at end of file', - ], - }, - ], - }]); - - assert.strictEqual( - filePatch.getBufferText(), - 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\nline-6\nline-7\nline-8\nline-9\nline-10\n' + - 'line-11\nline-12\nline-13\nNo newline at end of file\n', - ); - - const stagePatch0 = filePatch.getStagePatchForLines(new Set([4, 5, 6])); - assertInFilePatch(stagePatch0).hunks( - { - startRow: 3, - endRow: 10, - header: '@@ -5,5 +5,6 @@', - deletions: {strings: ['*line-4\n*line-5\n'], ranges: [[[4, 0], [5, 0]]]}, - additions: {strings: ['*line-6\n'], ranges: [[[6, 0], [6, 0]]]}, - }, - ); - - const stagePatch1 = filePatch.getStagePatchForLines(new Set([0, 4, 5, 6, 11])); - assertInFilePatch(stagePatch1).hunks( + const bufferText = '0000\n0001\n0002\n0003\n0004\n'; + const hunks = [ + new Hunk({ + oldStartRow: 5, + oldRowCount: 3, + newStartRow: 5, + newRowCount: 4, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [4, 0]], startOffset: 0, endOffset: 25}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 5, endOffset: 15})), + new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [3, 0]], startOffset: 15, endOffset: 20})), + ], + }), + ]; + const patch = new Patch({status: 'modified', hunks, bufferText}); + const oldFile = new File({path: 'file.txt', mode: '100644'}); + const newFile = new File({path: 'file.txt', mode: '100644'}); + const filePatch = new FilePatch(oldFile, newFile, patch); + + const stagedPatch = filePatch.getStagePatchForLines(new Set([1, 3])); + assert.strictEqual(stagedPatch.getStatus(), 'modified'); + assert.strictEqual(stagedPatch.getOldPath(), 'file.txt'); + assert.strictEqual(stagedPatch.getOldMode(), '100644'); + assert.strictEqual(stagedPatch.getNewPath(), 'file.txt'); + assert.strictEqual(stagedPatch.getNewMode(), '100644'); + assert.strictEqual(stagedPatch.getBufferText(), '0000\n0001\n0003\n0004\n'); + assertInFilePatch(stagedPatch).hunks( { startRow: 0, - endRow: 2, - header: '@@ -1,1 +1,3 @@', - additions: {strings: ['*line-0\n'], ranges: [[[0, 0], [0, 0]]]}, - }, - { - startRow: 3, - endRow: 10, - header: '@@ -5,5 +6,6 @@', - deletions: {strings: ['*line-4\n*line-5\n'], ranges: [[[4, 0], [5, 0]]]}, - additions: {strings: ['*line-6\n'], ranges: [[[6, 0], [6, 0]]]}, - }, - { - startRow: 11, - endRow: 14, - header: '@@ -20,2 +18,3 @@', - deletions: {strings: ['*line-11\n'], ranges: [[[11, 0], [11, 0]]]}, - noNewline: {string: '*No newline at end of file\n', range: [[14, 0], [14, 0]]}, + endRow: 3, + header: '@@ -5,3 +5,3 @@', + changes: [ + {kind: 'addition', string: '+0001\n', range: [[1, 0], [1, 0]]}, + {kind: 'deletion', string: '-0003\n', range: [[2, 0], [2, 0]]}, + ], }, ); }); describe('staging lines from deleted files', function() { - it('handles staging part of the file', function() { - const filePatch = buildFilePatch([{ - oldPath: 'a.txt', - oldMode: '100644', - newPath: null, - newMode: null, - status: 'deleted', - hunks: [ - { - oldStartLine: 1, - newStartLine: 0, - oldLineCount: 3, - newLineCount: 0, - lines: [ - '-line-1', - '-line-2', - '-line-3', - ], - }, - { - oldStartLine: 19, - newStartLine: 21, - oldLineCount: 2, - newLineCount: 2, - lines: [ - '-line-13', - '+line-12', - ' line-14', - '\\No newline at end of file', - ], - }, - ], - }]); + it('handles staging part of the file'); - assert.strictEqual(filePatch.getBufferText(), - 'line-1\nline-2\nline-3\nline-13\nline-12\nline-14\n' + - 'No newline at end of file\n'); - - const stagePatch = filePatch.getStagePatchForLines(new Set([0, 1])); - assertInFilePatch(stagePatch).hunks( - { - startRow: 0, - endRow: 2, - header: '@@ -1,3 +0,1 @@', - deletions: {strings: ['*line-1\n*line-2\n'], ranges: [[[0, 0], [1, 0]]]}, - }, - ); - }); - - it('handles staging all lines, leaving nothing unstaged', function() { - const filePatch = buildFilePatch([{ - oldPath: 'a.txt', - oldMode: '100644', - newPath: null, - newMode: null, - status: 'deleted', - hunks: [ - { - oldStartLine: 1, - oldLineCount: 3, - newStartLine: 1, - newLineCount: 0, - lines: [ - '-line-1', - '-line-2', - '-line-3', - ], - }, - ], - }]); - - assert.strictEqual(filePatch.getBufferText(), 'line-1\nline-2\nline-3\n'); - - const stagePatch = filePatch.getStagePatchForLines(new Set([0, 1, 2])); - assertInFilePatch(stagePatch).hunks( - { - startRow: 0, - endRow: 2, - header: '@@ -1,3 +1,0 @@', - deletions: {strings: ['*line-1\n*line-2\n*line-3\n'], ranges: [[[0, 0], [2, 0]]]}, - }, - ); - }); + it('handles staging all lines, leaving nothing unstaged'); }); }); describe('getUnstagePatchForLines()', function() { - it('returns a new FilePatch that unstages only the specified lines', function() { - const filePatch = buildFilePatch([{ - oldPath: 'a.txt', - oldMode: '100644', - newPath: 'a.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 1, - oldLineCount: 1, - newStartLine: 1, - newLineCount: 3, - lines: [ - '+line-0', - '+line-1', - ' line-2', - ], - }, - { - oldStartLine: 5, - oldLineCount: 5, - newStartLine: 7, - newLineCount: 4, - lines: [ - ' line-3', - '-line-4', - '-line-5', - '+line-6', - '+line-7', - '+line-8', - '-line-9', - '-line-10', - ], - }, - { - oldStartLine: 20, - oldLineCount: 2, - newStartLine: 21, - newLineCount: 2, - lines: [ - '-line-11', - '+line-12', - ' line-13', - '\\No newline at end of file', - ], - }, - ], - }]); - - assert.strictEqual( - filePatch.getBufferText(), - 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\nline-6\nline-7\nline-8\nline-9\nline-10\n' + - 'line-11\nline-12\nline-13\nNo newline at end of file\n', - ); - - const unstagedPatch0 = filePatch.getUnstagePatchForLines(new Set([4, 5, 6, 7, 11, 12, 13])); - console.log(unstagedPatch0.toString()); - assertInFilePatch(unstagedPatch0).hunks( - { - startRow: 3, - endRow: 10, - header: '@@ -7,4 +7,4 @@', - additions: {strings: ['*line-4\n*line-5\n'], ranges: [[[4, 0], [5, 0]]]}, - deletions: {strings: ['*line-6\n*line-7\n'], ranges: [[[5, 0], [6, 0]]]}, - }, - { - startRow: 11, - endRow: 14, - header: '@@ -19,2 +21,2 @@', - additions: {strings: ['*line-11\n'], ranges: [[[11, 0], [11, 0]]]}, - deletions: {strings: ['*line-12\n'], ranges: [[[12, 0], [12, 0]]]}, - noNewline: {string: '*No newline at end of file\n', range: [[14, 0], [14, 0]]}, - }, - ); - }); + it('returns a new FilePatch that unstages only the specified lines'); describe('unstaging lines from an added file', function() { it('handles unstaging part of the file'); From fa47e6624c34ff80b6cf5b176a711c1c5c2c143c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 17 Aug 2018 14:24:35 -0400 Subject: [PATCH 0101/4053] Return Atom Ranges from Hunk .getXyzRange() methods --- lib/models/patch/hunk.js | 22 +++++----------------- test/models/patch/hunk.test.js | 4 ++-- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 1b4290d20e..cf33d955e2 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -1,4 +1,4 @@ -import IndexedRowRange, {nullIndexedRowRange} from '../indexed-row-range'; +import IndexedRowRange from '../indexed-row-range'; import {Unchanged} from './region'; export default class Hunk { @@ -87,19 +87,19 @@ export default class Hunk { } getAdditionRanges() { - return this.changes.filter(change => change.isAddition()).map(change => change.getRowRange()); + return this.changes.filter(change => change.isAddition()).map(change => change.getRowRange().bufferRange); } getDeletionRanges() { - return this.changes.filter(change => change.isDeletion()).map(change => change.getRowRange()); + return this.changes.filter(change => change.isDeletion()).map(change => change.getRowRange().bufferRange); } getNoNewlineRange() { const lastChange = this.changes[this.changes.length - 1]; if (lastChange && lastChange.isNoNewline()) { - return lastChange.getRowRange(); + return lastChange.getRowRange().bufferRange; } else { - return nullIndexedRowRange; + return null; } } @@ -126,18 +126,6 @@ export default class Hunk { }), 0); } - invert() { - return new Hunk({ - oldStartRow: this.getNewStartRow(), - newStartRow: this.getOldStartRow(), - oldRowCount: this.getNewRowCount(), - newRowCount: this.getOldRowCount(), - sectionHeading: this.getSectionHeading(), - rowRange: this.rowRange, - changes: this.getChanges().map(change => change.invert()), - }); - } - toStringIn(bufferText) { let str = this.getHeader() + '\n'; for (const region of this.getRegions()) { diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 671e9232bc..9a07d6e85b 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -54,7 +54,7 @@ describe('Hunk', function() { assert.lengthOf(h.getChanges(), 3); assert.lengthOf(h.getAdditionRanges(), 1); assert.lengthOf(h.getDeletionRanges(), 2); - assert.isFalse(h.getNoNewlineRange().isPresent()); + assert.isNull(h.getNoNewlineRange()); }); it('creates its start range for decoration placement', function() { @@ -205,7 +205,7 @@ describe('Hunk', function() { assert.strictEqual(inverted.getSectionHeading(), 'the-heading'); assert.lengthOf(inverted.getAdditionRanges(), 1); assert.lengthOf(inverted.getDeletionRanges(), 2); - assert.isTrue(inverted.getNoNewlineRange().isPresent()); + assert.deepEqual(inverted.getNoNewlineRange().serialize(), [[12, 0], [12, 0]]); }); describe('toStringIn()', function() { From 8494eec6465ebe1e0d7c5a40acd6b071c7a4ff87 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 17 Aug 2018 14:25:35 -0400 Subject: [PATCH 0102/4053] Return the correct status when staging or unstaging an entire Patch --- lib/models/patch/patch.js | 10 ++++++-- test/models/patch/patch.test.js | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index b81bf96f44..48906ef19b 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -183,7 +183,8 @@ export default class Patch { } } - const status = this.getStatus() === 'deleted' ? 'modified' : this.getStatus(); + const wholeFile = rowSet.size === this.changedLineCount; + const status = this.getStatus() === 'deleted' && !wholeFile ? 'modified' : this.getStatus(); return this.clone({hunks, status, bufferText: builder.getBufferText()}); } @@ -270,7 +271,12 @@ export default class Patch { newRowDelta += additionRowCount - deletionRowCount; } - const status = this.getStatus() === 'added' ? 'modified' : this.getStatus(); + const wholeFile = rowSet.size === this.changedLineCount; + let status = this.getStatus(); + if (this.getStatus() === 'added') { + status = wholeFile ? 'deleted' : 'modified'; + } + return this.clone({hunks, status, bufferText: builder.getBufferText()}); } diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index c6304f62d5..d2f8308695 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -203,6 +203,26 @@ describe('Patch', function() { ); }); + it('returns an deletion when staging an entire deletion patch', function() { + const bufferText = '0000\n0001\n0002\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 3, + newStartRow: 1, + newRowCount: 0, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + ], + }), + ]; + const patch = new Patch({status: 'deleted', hunks, bufferText}); + + const unstagePatch0 = patch.getUnstagePatchForLines(new Set([0, 1, 2])); + assert.strictEqual(unstagePatch0.getStatus(), 'deleted'); + }); + it('returns a nullPatch as a nullPatch', function() { assert.strictEqual(nullPatch.getStagePatchForLines(new Set([1, 2, 3])), nullPatch); }); @@ -313,6 +333,29 @@ describe('Patch', function() { ); }); + it('returns a deletion when unstaging an entire addition patch', function() { + const bufferText = '0000\n0001\n0002\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 0, + newStartRow: 1, + newRowCount: 3, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + ], + }), + ]; + const patch = new Patch({status: 'added', hunks, bufferText}); + + const unstagePatch0 = patch.getUnstagePatchForLines(new Set([0, 1, 2])); + assert.strictEqual(unstagePatch0.getStatus(), 'deleted'); + + const unstagePatch1 = patch.getFullUnstagedPatch(); + assert.strictEqual(unstagePatch1.getStatus(), 'deleted'); + }); + it('returns a nullPatch as a nullPatch', function() { assert.strictEqual(nullPatch.getUnstagePatchForLines(new Set([1, 2, 3])), nullPatch); }); From 485444615ddf45c9182d4d31c0fda5a3a76ad97d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 17 Aug 2018 14:26:01 -0400 Subject: [PATCH 0103/4053] Unstage an entire Patch in one gulp --- lib/models/patch/patch.js | 19 ++++++++++++++ test/models/patch/patch.test.js | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 48906ef19b..4c330feb8c 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -280,6 +280,25 @@ export default class Patch { return this.clone({hunks, status, bufferText: builder.getBufferText()}); } + getFullUnstagedPatch() { + let newRowDelta = 0; + const hunks = this.getHunks().map(hunk => { + const changes = hunk.getChanges().map(change => change.invert()); + const newHunk = new Hunk({ + oldStartRow: hunk.getNewStartRow(), + oldRowCount: hunk.getNewRowCount(), + newStartRow: hunk.getNewStartRow() + newRowDelta, + newRowCount: hunk.getOldRowCount(), + rowRange: hunk.getRowRange(), + changes, + }); + newRowDelta += newHunk.getNewRowCount() - newHunk.getOldRowCount(); + return newHunk; + }); + const status = this.getStatus() === 'added' ? 'deleted' : this.getStatus(); + return this.clone({hunks, status}); + } + toString() { return this.getHunks().reduce((str, hunk) => { str += hunk.toStringIn(this.getBufferText()); diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index d2f8308695..67f46683a6 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -303,6 +303,52 @@ describe('Patch', function() { ); }); + it('unstages an entire patch at once', function() { + const patch = buildPatchFixture(); + const unstagedPatch = patch.getFullUnstagedPatch(); + + assert.strictEqual(unstagedPatch.getBufferText(), patch.getBufferText()); + assertInPatch(unstagedPatch).hunks( + { + startRow: 0, + endRow: 6, + header: '@@ -3,5 +3,4 @@', + changes: [ + {kind: 'addition', string: '+0001\n+0002\n', range: [[1, 0], [2, 0]]}, + {kind: 'deletion', string: '-0003\n-0004\n-0005\n', range: [[3, 0], [5, 0]]}, + ], + }, + { + startRow: 7, + endRow: 18, + header: '@@ -13,7 +12,9 @@', + changes: [ + {kind: 'deletion', string: '-0008\n-0009\n', range: [[8, 0], [9, 0]]}, + {kind: 'addition', string: '+0012\n+0013\n+0014\n+0015\n+0016\n', range: [[12, 0], [16, 0]]}, + {kind: 'deletion', string: '-0017\n', range: [[17, 0], [17, 0]]}, + ], + }, + { + startRow: 19, + endRow: 23, + header: '@@ -25,3 +26,4 @@', + changes: [ + {kind: 'deletion', string: '-0020\n', range: [[20, 0], [20, 0]]}, + {kind: 'addition', string: '+0021\n+0022\n', range: [[21, 0], [22, 0]]}, + ], + }, + { + startRow: 24, + endRow: 26, + header: '@@ -30,2 +32,1 @@', + changes: [ + {kind: 'deletion', string: '-0025\n', range: [[25, 0], [25, 0]]}, + {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[26, 0], [26, 0]]}, + ], + }, + ); + }); + it('returns a modification if original patch is an addition', function() { const bufferText = '0000\n0001\n0002\n'; const hunks = [ From c09829b1ff3a14459c92287296f641dee28651fa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 17 Aug 2018 14:26:16 -0400 Subject: [PATCH 0104/4053] File::isExecutable() for consistency --- lib/models/patch/file.js | 8 ++++++++ test/models/patch/file.test.js | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/lib/models/patch/file.js b/lib/models/patch/file.js index a3eda44239..c9017ec56f 100644 --- a/lib/models/patch/file.js +++ b/lib/models/patch/file.js @@ -25,6 +25,10 @@ export default class File { return this.getMode() === '100644' || this.getMode() === '100755'; } + isExecutable() { + return this.getMode() === '100755'; + } + isPresent() { return true; } @@ -62,6 +66,10 @@ export const nullFile = { return false; }, + isExecutable() { + return false; + }, + isPresent() { return false; }, diff --git a/test/models/patch/file.test.js b/test/models/patch/file.test.js index 38bb449e78..452cb8c6e6 100644 --- a/test/models/patch/file.test.js +++ b/test/models/patch/file.test.js @@ -14,6 +14,13 @@ describe('File', function() { assert.isFalse(nullFile.isRegularFile()); }); + it("detects when it's executable", function() { + assert.isTrue(new File({path: 'path', mode: '100755', symlink: null}).isExecutable()); + assert.isFalse(new File({path: 'path', mode: '100644', symlink: null}).isExecutable()); + assert.isFalse(new File({path: 'path', mode: '120000', symlink: null}).isExecutable()); + assert.isFalse(nullFile.isExecutable()); + }); + it('clones itself with possible overrides', function() { const original = new File({path: 'original', mode: '100644', symlink: null}); From dbb1a92f21568e476fe04e6b0e5410abc7ab5170 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 17 Aug 2018 14:26:33 -0400 Subject: [PATCH 0105/4053] Reimplement and retest FilePatch --- lib/models/patch/file-patch.js | 164 +++---- test/models/patch/file-patch.test.js | 637 ++++++++++++++++++++++++++- 2 files changed, 673 insertions(+), 128 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index d765ca82c7..3b57951624 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -1,5 +1,4 @@ -import Hunk from './hunk'; -import File, {nullFile} from './file'; +import {nullFile} from './file'; import Patch from './patch'; import {toGitPathSep} from '../../helpers'; @@ -54,57 +53,55 @@ export default class FilePatch { return this.getPatch().getBufferText(); } - getHunkStartPositions() { - return this.getHunks().map(hunk => hunk.getBufferStartPosition()); - } - - getAddedBufferPositions() { + getAdditionRanges() { return this.getHunks().reduce((acc, hunk) => { - acc.push(...hunk.getAddedBufferPositions()); + acc.push(...hunk.getAdditionRanges()); return acc; }, []); } - getBufferDeletedPositions() { + getDeletionRanges() { return this.getHunks().reduce((acc, hunk) => { - acc.push(...hunk.getDeletedBufferPositions()); + acc.push(...hunk.getDeletionRanges()); return acc; }, []); } - getBufferNoNewlinePosition() { - return this.getHunks().reduce((acc, hunk) => { - const position = hunk.getBufferNoNewlinePosition(); - if (position.isPresent()) { - acc.push(position); - } - return acc; - }, []); + getNoNewlineRanges() { + const hunks = this.getHunks(); + const lastHunk = hunks[hunks.length - 1]; + if (!lastHunk) { + return []; + } + + const range = lastHunk.getNoNewlineRange(); + if (!range) { + return []; + } + + return [range]; } didChangeExecutableMode() { - const oldMode = this.getOldMode(); - const newMode = this.getNewMode(); - return oldMode === '100755' && newMode !== '100755' || - oldMode !== '100755' && newMode === '100755'; - } + if (!this.oldFile.isPresent() || !this.newFile.isPresent()) { + return false; + } - didChangeSymlinkMode() { - const oldMode = this.getOldMode(); - const newMode = this.getNewMode(); - return oldMode === '120000' && newMode !== '120000' || - oldMode !== '120000' && newMode === '120000'; + return this.oldFile.isExecutable() && !this.newFile.isExecutable() || + !this.oldFile.isExecutable() && this.newFile.isExecutable(); } hasSymlink() { - return this.getOldFile().getSymlink() || this.getNewFile().getSymlink(); + return Boolean(this.getOldFile().getSymlink() || this.getNewFile().getSymlink()); } hasTypechange() { - const oldFile = this.getOldFile(); - const newFile = this.getNewFile(); - return (oldFile.isSymlink() && newFile.isRegularFile()) || - (newFile.isSymlink() && oldFile.isRegularFile()); + if (!this.oldFile.isPresent() || !this.newFile.isPresent()) { + return false; + } + + return this.oldFile.isSymlink() && !this.newFile.isSymlink() || + !this.oldFile.isSymlink() && this.newFile.isSymlink(); } getPath() { @@ -119,7 +116,7 @@ export default class FilePatch { return this.getPatch().getHunks(); } - clone(opts) { + clone(opts = {}) { return new this.constructor( opts.oldFile !== undefined ? opts.oldFile : this.oldFile, opts.newFile !== undefined ? opts.newFile : this.newFile, @@ -127,13 +124,8 @@ export default class FilePatch { ); } - getStagePatchForHunk(selectedHunk) { - return this.getStagePatchForLines(new Set(selectedHunk.getBufferRows())); - } - getStagePatchForLines(selectedLineSet) { - const wholeFileSelected = this.patch.getChangedLineCount() === selectedLineSet.size; - if (wholeFileSelected) { + if (this.patch.getChangedLineCount() === selectedLineSet.size) { if (this.hasTypechange() && this.getStatus() === 'deleted') { // handle special case when symlink is created where a file was deleted. In order to stage the file deletion, // we must ensure that the created file patch has no new file @@ -145,103 +137,51 @@ export default class FilePatch { const patch = this.patch.getStagePatchForLines(selectedLineSet); if (this.getStatus() === 'deleted') { // Populate newFile - return this.clone({ - newFile: this.getOldFile(), - patch, - }); + return this.clone({newFile: this.getOldFile(), patch}); } else { return this.clone({patch}); } } } - getUnstagePatchForHunk(hunk) { - return this.getUnstagePatchForLines(new Set(hunk.getBufferRows())); + getStagePatchForHunk(selectedHunk) { + return this.getStagePatchForLines(new Set(selectedHunk.getBufferRows())); } - getUnstagePatchForLines(selectedRowSet) { - if (this.changedLineCount === selectedRowSet.size) { + getUnstagePatchForLines(selectedLineSet) { + if (this.patch.getChangedLineCount() === selectedLineSet.size) { + const patch = this.patch.getFullUnstagedPatch(); if (this.hasTypechange() && this.getStatus() === 'added') { // handle special case when a file was created after a symlink was deleted. // In order to unstage the file creation, we must ensure that the unstage patch has no new file, - // so when the patch is applied to the index, there file will be removed from the index - return this.clone({ - oldFile: File.empty(), - }).getUnstagePatch(); + // so when the patch is applied to the index, there file will be removed from the index. + return this.clone({oldFile: nullFile, patch}); } else { - return this.getUnstagePatch(); + return this.clone({patch}); } - } - - const hunks = this.getUnstagePatchHunks(selectedRowSet); - if (this.getStatus() === 'added') { - return this.clone({ - oldFile: this.getNewFile(), - patch: this.getPatch().clone({hunks, status: 'modified'}), - }).getUnstagePatch(); } else { - return this.clone({ - patch: this.getPatch().clone({hunks}), - }).getUnstagePatch(); + const patch = this.patch.getUnstagePatchForLines(selectedLineSet); + if (this.getStatus() === 'added') { + return this.clone({oldFile: this.getNewFile(), patch}); + } else { + return this.clone({patch}); + } } } - getUnstagePatchHunks(selectedRowSet) { - let delta = 0; - const hunks = []; - for (const hunk of this.getHunks()) { - const additions = []; - const deletions = []; - let notAddedRowCount = 0; - let addedRowCount = 0; - let notDeletedRowCount = 0; - let deletedRowCount = 0; - - for (const change of hunk.getAdditions()) { - notDeletedRowCount += change.bufferRowCount(); - for (const intersection of change.intersectRowsIn(selectedRowSet, this.getBufferText())) { - deletedRowCount += intersection.bufferRowCount(); - notDeletedRowCount -= intersection.bufferRowCount(); - deletions.push(intersection); - } - } - - for (const change of hunk.getDeletions()) { - notAddedRowCount = change.bufferRowCount(); - for (const intersection of change.intersectRowsIn(selectedRowSet, this.getBufferText())) { - addedRowCount += intersection.bufferRowCount(); - notAddedRowCount -= intersection.bufferRowCount(); - additions.push(intersection); - } - } - - if (additions.length > 0 || deletions.length > 0) { - // Hunk contains at least one selected line - hunks.push(new Hunk({ - oldStartRow: hunk.getOldStartRow() + delta, - newStartRow: hunk.getNewStartRow(), - oldRowCount: hunk.bufferRowCount() - addedRowCount, - newRowCount: hunk.getNewRowCount(), - sectionHeading: hunk.getSectionHeading(), - rowRange: hunk.getRowRange(), - additions, - deletions, - noNewline: hunk.getNoNewline(), - })); - } - delta += notDeletedRowCount - notAddedRowCount; - } - return hunks; + getUnstagePatchForHunk(hunk) { + return this.getUnstagePatchForLines(new Set(hunk.getBufferRows())); } toString() { if (this.hasTypechange()) { const left = this.clone({ - newFile: File.empty(), + newFile: nullFile, patch: this.getOldSymlink() ? new Patch({status: 'deleted', hunks: []}) : this.getPatch(), }); + const right = this.clone({ - oldFile: File.empty(), + oldFile: nullFile, patch: this.getNewSymlink() ? new Patch({status: 'added', hunks: []}) : this.getPatch(), }); diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 4be79c00cf..dc266ada09 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -1,12 +1,210 @@ import FilePatch from '../../../lib/models/patch/file-patch'; -import File from '../../../lib/models/patch/file'; +import File, {nullFile} from '../../../lib/models/patch/file'; import Patch from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; -import {Addition, Deletion} from '../../../lib/models/patch/region'; +import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; import IndexedRowRange from '../../../lib/models/indexed-row-range'; import {assertInFilePatch} from '../../helpers'; describe('FilePatch', function() { + it('delegates methods to its files and patch', function() { + const bufferText = '0000\n0001\n'; + const hunks = [ + new Hunk({ + oldStartRow: 2, + oldRowCount: 1, + newStartRow: 2, + newRowCount: 2, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 10}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + ], + }), + ]; + const patch = new Patch({status: 'modified', hunks, bufferText}); + const oldFile = new File({path: 'a.txt', mode: '120000', symlink: 'dest.txt'}); + const newFile = new File({path: 'b.txt', mode: '100755'}); + const filePatch = new FilePatch(oldFile, newFile, patch); + + assert.strictEqual(filePatch.getOldPath(), 'a.txt'); + assert.strictEqual(filePatch.getOldMode(), '120000'); + assert.strictEqual(filePatch.getOldSymlink(), 'dest.txt'); + + assert.strictEqual(filePatch.getNewPath(), 'b.txt'); + assert.strictEqual(filePatch.getNewMode(), '100755'); + assert.isUndefined(filePatch.getNewSymlink()); + + assert.strictEqual(filePatch.getByteSize(), 10); + assert.strictEqual(filePatch.getBufferText(), bufferText); + }); + + it('accesses a file path from either side of the patch', function() { + const oldFile = new File({path: 'old-file.txt', mode: '100644'}); + const newFile = new File({path: 'new-file.txt', mode: '100644'}); + const patch = new Patch({status: 'modified', hunks: [], bufferText: ''}); + + assert.strictEqual(new FilePatch(oldFile, newFile, patch).getPath(), 'old-file.txt'); + assert.strictEqual(new FilePatch(oldFile, nullFile, patch).getPath(), 'old-file.txt'); + assert.strictEqual(new FilePatch(nullFile, newFile, patch).getPath(), 'new-file.txt'); + assert.isNull(new FilePatch(nullFile, nullFile, patch).getPath()); + }); + + it('iterates addition and deletion ranges from all hunks', function() { + const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 0, + newStartRow: 1, + newRowCount: 0, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [9, 0]], startOffset: 0, endOffset: 50}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + new Addition(new IndexedRowRange({bufferRange: [[3, 0], [3, 0]], startOffset: 15, endOffset: 20})), + new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 20, endOffset: 25})), + new Addition(new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 25, endOffset: 35})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [7, 0]], startOffset: 35, endOffset: 40})), + new Addition(new IndexedRowRange({bufferRange: [[8, 0], [8, 0]], startOffset: 40, endOffset: 45})), + ], + }), + ]; + const patch = new Patch({status: 'modified', hunks, bufferText}); + const oldFile = new File({path: 'a.txt', mode: '100644'}); + const newFile = new File({path: 'a.txt', mode: '100644'}); + const filePatch = new FilePatch(oldFile, newFile, patch); + + const additionRanges = filePatch.getAdditionRanges(); + assert.deepEqual(additionRanges.map(range => range.serialize()), [ + [[1, 0], [1, 0]], + [[3, 0], [3, 0]], + [[5, 0], [6, 0]], + [[8, 0], [8, 0]], + ]); + + const deletionRanges = filePatch.getDeletionRanges(); + assert.deepEqual(deletionRanges.map(range => range.serialize()), [ + [[4, 0], [4, 0]], + [[7, 0], [7, 0]], + ]); + + const noNewlineRanges = filePatch.getNoNewlineRanges(); + assert.lengthOf(noNewlineRanges, 0); + }); + + it('returns an empty nonewline range if no hunks are present', function() { + const patch = new Patch({status: 'modified', hunks: [], bufferText: ''}); + const oldFile = new File({path: 'a.txt', mode: '100644'}); + const newFile = new File({path: 'a.txt', mode: '100644'}); + const filePatch = new FilePatch(oldFile, newFile, patch); + + assert.lengthOf(filePatch.getNoNewlineRanges(), 0); + }); + + it('returns a nonewline range if one is present', function() { + const bufferText = '0000\n No newline at end of file\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 0, + newStartRow: 1, + newRowCount: 0, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 32}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 5})), + new NoNewline(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 32})), + ], + }), + ]; + const patch = new Patch({status: 'modified', hunks, bufferText}); + const oldFile = new File({path: 'a.txt', mode: '100644'}); + const newFile = new File({path: 'a.txt', mode: '100644'}); + const filePatch = new FilePatch(oldFile, newFile, patch); + + const noNewlineRanges = filePatch.getNoNewlineRanges(); + assert.deepEqual(noNewlineRanges.map(range => range.serialize()), [ + [[1, 0], [1, 0]], + ]); + }); + + describe('file-level change detection', function() { + let emptyPatch; + + beforeEach(function() { + emptyPatch = new Patch({status: 'modified', hunks: [], bufferText: ''}); + }); + + it('detects changes in executable mode', function() { + const executableFile = new File({path: 'file.txt', mode: '100755'}); + const nonExecutableFile = new File({path: 'file.txt', mode: '100644'}); + + assert.isTrue(new FilePatch(nonExecutableFile, executableFile, emptyPatch).didChangeExecutableMode()); + assert.isTrue(new FilePatch(executableFile, nonExecutableFile, emptyPatch).didChangeExecutableMode()); + assert.isFalse(new FilePatch(nonExecutableFile, nonExecutableFile, emptyPatch).didChangeExecutableMode()); + assert.isFalse(new FilePatch(executableFile, executableFile, emptyPatch).didChangeExecutableMode()); + assert.isFalse(new FilePatch(nullFile, nonExecutableFile).didChangeExecutableMode()); + assert.isFalse(new FilePatch(nullFile, executableFile).didChangeExecutableMode()); + }); + + it('detects changes in symlink mode', function() { + const symlinkFile = new File({path: 'file.txt', mode: '120000', symlink: 'dest.txt'}); + const nonSymlinkFile = new File({path: 'file.txt', mode: '100644'}); + + assert.isTrue(new FilePatch(nonSymlinkFile, symlinkFile, emptyPatch).hasTypechange()); + assert.isTrue(new FilePatch(symlinkFile, nonSymlinkFile, emptyPatch).hasTypechange()); + assert.isFalse(new FilePatch(nonSymlinkFile, nonSymlinkFile, emptyPatch).hasTypechange()); + assert.isFalse(new FilePatch(symlinkFile, symlinkFile, emptyPatch).hasTypechange()); + assert.isFalse(new FilePatch(nullFile, nonSymlinkFile).hasTypechange()); + assert.isFalse(new FilePatch(nullFile, symlinkFile).hasTypechange()); + }); + + it('detects when either file has a symlink destination', function() { + const symlinkFile = new File({path: 'file.txt', mode: '120000', symlink: 'dest.txt'}); + const nonSymlinkFile = new File({path: 'file.txt', mode: '100644'}); + + assert.isTrue(new FilePatch(nonSymlinkFile, symlinkFile, emptyPatch).hasSymlink()); + assert.isTrue(new FilePatch(symlinkFile, nonSymlinkFile, emptyPatch).hasSymlink()); + assert.isFalse(new FilePatch(nonSymlinkFile, nonSymlinkFile, emptyPatch).hasSymlink()); + assert.isTrue(new FilePatch(symlinkFile, symlinkFile, emptyPatch).hasSymlink()); + assert.isFalse(new FilePatch(nullFile, nonSymlinkFile).hasSymlink()); + assert.isTrue(new FilePatch(nullFile, symlinkFile).hasSymlink()); + }); + }); + + it('clones itself and overrides select properties', function() { + const file00 = new File({path: 'file-00.txt', mode: '100644'}); + const file01 = new File({path: 'file-01.txt', mode: '100644'}); + const file10 = new File({path: 'file-10.txt', mode: '100644'}); + const file11 = new File({path: 'file-11.txt', mode: '100644'}); + const patch0 = new Patch({status: 'modified', hunks: [], bufferText: '0'}); + const patch1 = new Patch({status: 'modified', hunks: [], bufferText: '1'}); + + const original = new FilePatch(file00, file01, patch0); + + const clone0 = original.clone(); + assert.notStrictEqual(clone0, original); + assert.strictEqual(clone0.getOldFile(), file00); + assert.strictEqual(clone0.getNewFile(), file01); + assert.strictEqual(clone0.getPatch(), patch0); + + const clone1 = original.clone({oldFile: file10}); + assert.notStrictEqual(clone1, original); + assert.strictEqual(clone1.getOldFile(), file10); + assert.strictEqual(clone1.getNewFile(), file01); + assert.strictEqual(clone1.getPatch(), patch0); + + const clone2 = original.clone({newFile: file11}); + assert.notStrictEqual(clone2, original); + assert.strictEqual(clone2.getOldFile(), file00); + assert.strictEqual(clone2.getNewFile(), file11); + assert.strictEqual(clone2.getPatch(), patch0); + + const clone3 = original.clone({patch: patch1}); + assert.notStrictEqual(clone3, original); + assert.strictEqual(clone3.getOldFile(), file00); + assert.strictEqual(clone3.getNewFile(), file01); + assert.strictEqual(clone3.getPatch(), patch1); + }); + describe('getStagePatchForLines()', function() { it('returns a new FilePatch that applies only the selected lines', function() { const bufferText = '0000\n0001\n0002\n0003\n0004\n'; @@ -49,38 +247,445 @@ describe('FilePatch', function() { }); describe('staging lines from deleted files', function() { - it('handles staging part of the file'); + let deletionPatch; + + beforeEach(function() { + const bufferText = '0000\n0001\n0002\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 3, + newStartRow: 1, + newRowCount: 0, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + ], + }), + ]; + const patch = new Patch({status: 'deleted', hunks, bufferText}); + const oldFile = new File({path: 'file.txt', mode: '100644'}); + deletionPatch = new FilePatch(oldFile, nullFile, patch); + }); + + it('handles staging part of the file', function() { + const stagedPatch = deletionPatch.getStagePatchForLines(new Set([1, 2])); - it('handles staging all lines, leaving nothing unstaged'); + assert.strictEqual(stagedPatch.getStatus(), 'modified'); + assert.strictEqual(stagedPatch.getOldPath(), 'file.txt'); + assert.strictEqual(stagedPatch.getOldMode(), '100644'); + assert.strictEqual(stagedPatch.getNewPath(), 'file.txt'); + assert.strictEqual(stagedPatch.getNewMode(), '100644'); + assert.strictEqual(stagedPatch.getBufferText(), '0000\n0001\n0002\n'); + assertInFilePatch(stagedPatch).hunks( + { + startRow: 0, + endRow: 2, + header: '@@ -1,3 +1,1 @@', + changes: [ + {kind: 'deletion', string: '-0001\n-0002\n', range: [[1, 0], [2, 0]]}, + ], + }, + ); + }); + + it('handles staging all lines, leaving nothing unstaged', function() { + const stagedPatch = deletionPatch.getStagePatchForLines(new Set([1, 2, 3])); + assert.strictEqual(stagedPatch.getStatus(), 'deleted'); + assert.strictEqual(stagedPatch.getOldPath(), 'file.txt'); + assert.strictEqual(stagedPatch.getOldMode(), '100644'); + assert.isFalse(stagedPatch.getNewFile().isPresent()); + assert.strictEqual(stagedPatch.getBufferText(), '0000\n0001\n0002\n'); + assertInFilePatch(stagedPatch).hunks( + { + startRow: 0, + endRow: 2, + header: '@@ -1,3 +1,0 @@', + changes: [ + {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 0]]}, + ], + }, + ); + }); + + it('unsets the newFile when a symlink is created where a file was deleted', function() { + const bufferText = '0000\n0001\n0002\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 3, + newStartRow: 1, + newRowCount: 0, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + ], + }), + ]; + const patch = new Patch({status: 'deleted', hunks, bufferText}); + const oldFile = new File({path: 'file.txt', mode: '100644'}); + const newFile = new File({path: 'file.txt', mode: '120000'}); + const replacePatch = new FilePatch(oldFile, newFile, patch); + + const stagedPatch = replacePatch.getStagePatchForLines(new Set([0, 1, 2])); + assert.isTrue(stagedPatch.getOldFile().isPresent()); + assert.isFalse(stagedPatch.getNewFile().isPresent()); + }); }); }); + it('stages an entire hunk at once', function() { + const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n'; + const hunks = [ + new Hunk({ + oldStartRow: 10, + oldRowCount: 2, + newStartRow: 10, + newRowCount: 3, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + ], + }), + new Hunk({ + oldStartRow: 20, + oldRowCount: 3, + newStartRow: 19, + newRowCount: 2, + rowRange: new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 15, endOffset: 35}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 20, endOffset: 25})), + ], + }), + ]; + const patch = new Patch({status: 'modified', hunks, bufferText}); + const oldFile = new File({path: 'file.txt', mode: '100644'}); + const newFile = new File({path: 'file.txt', mode: '100644'}); + const filePatch = new FilePatch(oldFile, newFile, patch); + + const stagedPatch = filePatch.getStagePatchForHunk(hunks[1]); + assert.strictEqual(stagedPatch.getBufferText(), '0003\n0004\n0005\n'); + assertInFilePatch(stagedPatch).hunks( + { + startRow: 0, + endRow: 2, + header: '@@ -20,3 +18,2 @@', + changes: [ + {kind: 'deletion', string: '-0004\n', range: [[1, 0], [1, 0]]}, + ], + }, + ); + }); + describe('getUnstagePatchForLines()', function() { - it('returns a new FilePatch that unstages only the specified lines'); + it('returns a new FilePatch that unstages only the specified lines', function() { + const bufferText = '0000\n0001\n0002\n0003\n0004\n'; + const hunks = [ + new Hunk({ + oldStartRow: 5, + oldRowCount: 3, + newStartRow: 5, + newRowCount: 4, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [4, 0]], startOffset: 0, endOffset: 25}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 5, endOffset: 15})), + new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [3, 0]], startOffset: 15, endOffset: 20})), + ], + }), + ]; + const patch = new Patch({status: 'modified', hunks, bufferText}); + const oldFile = new File({path: 'file.txt', mode: '100644'}); + const newFile = new File({path: 'file.txt', mode: '100644'}); + const filePatch = new FilePatch(oldFile, newFile, patch); + + const unstagedPatch = filePatch.getUnstagePatchForLines(new Set([1, 3])); + assert.strictEqual(unstagedPatch.getStatus(), 'modified'); + assert.strictEqual(unstagedPatch.getOldPath(), 'file.txt'); + assert.strictEqual(unstagedPatch.getOldMode(), '100644'); + assert.strictEqual(unstagedPatch.getNewPath(), 'file.txt'); + assert.strictEqual(unstagedPatch.getNewMode(), '100644'); + assert.strictEqual(unstagedPatch.getBufferText(), '0000\n0001\n0002\n0003\n0004\n'); + assertInFilePatch(unstagedPatch).hunks( + { + startRow: 0, + endRow: 4, + header: '@@ -5,4 +5,4 @@', + changes: [ + {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, 0]]}, + {kind: 'addition', string: '+0003\n', range: [[3, 0], [3, 0]]}, + ], + }, + ); + }); describe('unstaging lines from an added file', function() { - it('handles unstaging part of the file'); + let newFile, addedPatch, addedFilePatch; + + beforeEach(function() { + const bufferText = '0000\n0001\n0002\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 0, + newStartRow: 1, + newRowCount: 3, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + ], + }), + ]; + newFile = new File({path: 'file.txt', mode: '100644'}); + addedPatch = new Patch({status: 'added', hunks, bufferText}); + addedFilePatch = new FilePatch(nullFile, newFile, addedPatch); + }); + + it('handles unstaging part of the file', function() { + const unstagePatch = addedFilePatch.getUnstagePatchForLines(new Set([2])); + assert.strictEqual(unstagePatch.getStatus(), 'modified'); + assertInFilePatch(unstagePatch).hunks( + { + startRow: 0, + endRow: 2, + header: '@@ -1,3 +1,2 @@', + changes: [ + {kind: 'deletion', string: '-0002\n', range: [[2, 0], [2, 0]]}, + ], + }, + ); + }); - it('handles unstaging all lines, leaving nothing staged'); + it('handles unstaging all lines, leaving nothing staged', function() { + const unstagePatch = addedFilePatch.getUnstagePatchForLines(new Set([0, 1, 2])); + assert.strictEqual(unstagePatch.getStatus(), 'deleted'); + assertInFilePatch(unstagePatch).hunks( + { + startRow: 0, + endRow: 2, + header: '@@ -1,3 +1,0 @@', + changes: [ + {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 0]]}, + ], + }, + ); + }); + + it('unsets the oldFile when a symlink is deleted and a file is created in its place', function() { + const oldSymlink = new File({path: 'file.txt', mode: '120000', symlink: 'wat.txt'}); + const patch = new FilePatch(oldSymlink, newFile, addedPatch); + const unstagePatch = patch.getUnstagePatchForLines(new Set([0, 1, 2])); + assert.isFalse(unstagePatch.getOldFile().isPresent()); + assertInFilePatch(unstagePatch).hunks( + { + startRow: 0, + endRow: 2, + header: '@@ -1,3 +1,0 @@', + changes: [ + {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 0]]}, + ], + }, + ); + }); }); }); - it('handles newly added files'); + it('unstages an entire hunk at once', function() { + const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n'; + const hunks = [ + new Hunk({ + oldStartRow: 10, + oldRowCount: 2, + newStartRow: 10, + newRowCount: 3, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + ], + }), + new Hunk({ + oldStartRow: 20, + oldRowCount: 3, + newStartRow: 19, + newRowCount: 2, + rowRange: new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 15, endOffset: 35}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 20, endOffset: 25})), + ], + }), + ]; + const patch = new Patch({status: 'modified', hunks, bufferText}); + const oldFile = new File({path: 'file.txt', mode: '100644'}); + const newFile = new File({path: 'file.txt', mode: '100644'}); + const filePatch = new FilePatch(oldFile, newFile, patch); + + const unstagedPatch = filePatch.getUnstagePatchForHunk(hunks[0]); + assert.strictEqual(unstagedPatch.getBufferText(), '0000\n0001\n0002\n'); + assertInFilePatch(unstagedPatch).hunks( + { + startRow: 0, + endRow: 2, + header: '@@ -10,3 +10,2 @@', + changes: [ + {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, 0]]}, + ], + }, + ); + }); describe('toString()', function() { - it('converts the patch to the standard textual format'); + it('converts the patch to the standard textual format', function() { + const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n'; + const hunks = [ + new Hunk({ + oldStartRow: 10, + oldRowCount: 4, + newStartRow: 10, + newRowCount: 3, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [4, 0]], startOffset: 0, endOffset: 25}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + new Deletion(new IndexedRowRange({bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20})), + ], + }), + new Hunk({ + oldStartRow: 20, + oldRowCount: 2, + newStartRow: 20, + newRowCount: 3, + rowRange: new IndexedRowRange({bufferRange: [[5, 0], [7, 0]], startOffset: 25, endOffset: 40}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[6, 0], [6, 0]], startOffset: 30, endOffset: 35})), + ], + }), + ]; + const patch = new Patch({status: 'modified', hunks, bufferText}); + const oldFile = new File({path: 'a.txt', mode: '100644'}); + const newFile = new File({path: 'b.txt', mode: '100755'}); + const filePatch = new FilePatch(oldFile, newFile, patch); - it('correctly formats new files with no newline at the end'); + const expectedString = + 'diff --git a/a.txt b/b.txt\n' + + '--- a/a.txt\n' + + '+++ b/b.txt\n' + + '@@ -10,4 +10,3 @@\n' + + ' 0000\n' + + '+0001\n' + + '-0002\n' + + '-0003\n' + + ' 0004\n' + + '@@ -20,2 +20,3 @@\n' + + ' 0005\n' + + '+0006\n' + + ' 0007\n'; + assert.strictEqual(filePatch.toString(), expectedString); + }); - describe('typechange file patches', function() { - it('handles typechange patches for a symlink replaced with a file'); + it('correctly formats a file with no newline at the end', function() { + const bufferText = '0000\n0001\n No newline at end of file\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 1, + newStartRow: 1, + newRowCount: 2, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 37}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + new NoNewline(new IndexedRowRange({bufferRange: [[2, 0], [2, 0]], startOffset: 10, endOffset: 37})), + ], + }), + ]; + const patch = new Patch({status: 'modified', hunks, bufferText}); + const oldFile = new File({path: 'a.txt', mode: '100644'}); + const newFile = new File({path: 'b.txt', mode: '100755'}); + const filePatch = new FilePatch(oldFile, newFile, patch); - it('handles typechange patches for a file replaced with a symlink'); + const expectedString = + 'diff --git a/a.txt b/b.txt\n' + + '--- a/a.txt\n' + + '+++ b/b.txt\n' + + '@@ -1,1 +1,2 @@\n' + + ' 0000\n' + + '+0001\n' + + '\\ No newline at end of file\n'; + assert.strictEqual(filePatch.toString(), expectedString); }); - }); - describe('getHeaderString()', function() { - it('formats paths with git path separators'); + describe('typechange file patches', function() { + it('handles typechange patches for a symlink replaced with a file', function() { + const bufferText = '0000\n0001\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 0, + newStartRow: 1, + newRowCount: 2, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 10}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 10})), + ], + }), + ]; + const patch = new Patch({status: 'added', hunks, bufferText}); + const oldFile = new File({path: 'a.txt', mode: '120000', symlink: 'dest.txt'}); + const newFile = new File({path: 'a.txt', mode: '100644'}); + const filePatch = new FilePatch(oldFile, newFile, patch); + + const expectedString = + 'diff --git a/a.txt b/a.txt\n' + + 'deleted file mode 120000\n' + + '--- a/a.txt\n' + + '+++ /dev/null\n' + + '@@ -1 +0,0 @@\n' + + '-dest.txt\n' + + '\\ No newline at end of file\n' + + 'diff --git a/a.txt b/a.txt\n' + + 'new file mode 100644\n' + + '--- /dev/null\n' + + '+++ b/a.txt\n' + + '@@ -1,0 +1,2 @@\n' + + '+0000\n' + + '+0001\n'; + assert.strictEqual(filePatch.toString(), expectedString); + }); + + it('handles typechange patches for a file replaced with a symlink', function() { + const bufferText = '0000\n0001\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 2, + newStartRow: 1, + newRowCount: 0, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 10}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 10})), + ], + }), + ]; + const patch = new Patch({status: 'deleted', hunks, bufferText}); + const oldFile = new File({path: 'a.txt', mode: '100644'}); + const newFile = new File({path: 'a.txt', mode: '120000', symlink: 'dest.txt'}); + const filePatch = new FilePatch(oldFile, newFile, patch); + + const expectedString = + 'diff --git a/a.txt b/a.txt\n' + + 'deleted file mode 100644\n' + + '--- a/a.txt\n' + + '+++ /dev/null\n' + + '@@ -1,2 +1,0 @@\n' + + '-0000\n' + + '-0001\n' + + 'diff --git a/a.txt b/a.txt\n' + + 'new file mode 120000\n' + + '--- /dev/null\n' + + '+++ b/a.txt\n' + + '@@ -0,0 +1 @@\n' + + '+dest.txt\n' + + '\\ No newline at end of file\n'; + assert.strictEqual(filePatch.toString(), expectedString); + }); + }); }); it('getByteSize() returns the size in bytes'); From 3a67c2c4c2d5d89501b4b6e6b6cbf81f560e79f6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 17 Aug 2018 14:34:30 -0400 Subject: [PATCH 0106/4053] nullPatch method parity --- lib/models/patch/patch.js | 4 ++++ test/models/patch/patch.test.js | 1 + 2 files changed, 5 insertions(+) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 4c330feb8c..f10c942434 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -348,6 +348,10 @@ export const nullPatch = { return this; }, + getFullUnstagedPatch() { + return this; + }, + toString() { return ''; }, diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 67f46683a6..f54237b022 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -404,6 +404,7 @@ describe('Patch', function() { it('returns a nullPatch as a nullPatch', function() { assert.strictEqual(nullPatch.getUnstagePatchForLines(new Set([1, 2, 3])), nullPatch); + assert.strictEqual(nullPatch.getFullUnstagedPatch(), nullPatch); }); }); From 0e77fb0de76598e5876546156a330eddeeeb93d5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 17 Aug 2018 15:19:23 -0400 Subject: [PATCH 0107/4053] nullPatch completeness --- lib/models/patch/patch.js | 4 ++++ test/models/patch/patch.test.js | 1 + 2 files changed, 5 insertions(+) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index f10c942434..1b9be2bbe3 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -328,6 +328,10 @@ export const nullPatch = { return 0; }, + getChangedLineCount() { + return 0; + }, + clone(opts = {}) { if (opts.status === undefined && opts.hunks === undefined && opts.bufferText === undefined) { return this; diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index f54237b022..d4b4746f0c 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -460,6 +460,7 @@ describe('Patch', function() { assert.strictEqual(nullPatch.getByteSize(), 0); assert.isFalse(nullPatch.isPresent()); assert.strictEqual(nullPatch.toString(), ''); + assert.strictEqual(nullPatch.getChangedLineCount(), 0); }); }); From e27b2dd7e8bb70b0ccea466371f0db515742824a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 17 Aug 2018 15:19:51 -0400 Subject: [PATCH 0108/4053] Implement a nullFilePatch --- lib/models/patch/file-patch.js | 12 +++++++++- test/models/patch/file-patch.test.js | 35 ++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 3b57951624..188cea71d3 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -1,5 +1,5 @@ import {nullFile} from './file'; -import Patch from './patch'; +import Patch, {nullPatch} from './patch'; import {toGitPathSep} from '../../helpers'; export default class FilePatch { @@ -9,6 +9,10 @@ export default class FilePatch { this.patch = patch; } + isPresent() { + return this.oldFile.isPresent() || this.newFile.isPresent() || this.patch.isPresent(); + } + getOldFile() { return this.oldFile; } @@ -174,6 +178,10 @@ export default class FilePatch { } toString() { + if (!this.isPresent()) { + return ''; + } + if (this.hasTypechange()) { const left = this.clone({ newFile: nullFile, @@ -216,3 +224,5 @@ export default class FilePatch { return header; } } + +export const nullFilePatch = new FilePatch(nullFile, nullFile, nullPatch); diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index dc266ada09..c21204f4a8 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -1,4 +1,4 @@ -import FilePatch from '../../../lib/models/patch/file-patch'; +import FilePatch, {nullFilePatch} from '../../../lib/models/patch/file-patch'; import File, {nullFile} from '../../../lib/models/patch/file'; import Patch from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; @@ -26,6 +26,8 @@ describe('FilePatch', function() { const newFile = new File({path: 'b.txt', mode: '100755'}); const filePatch = new FilePatch(oldFile, newFile, patch); + assert.isTrue(filePatch.isPresent()); + assert.strictEqual(filePatch.getOldPath(), 'a.txt'); assert.strictEqual(filePatch.getOldMode(), '120000'); assert.strictEqual(filePatch.getOldSymlink(), 'dest.txt'); @@ -688,5 +690,34 @@ describe('FilePatch', function() { }); }); - it('getByteSize() returns the size in bytes'); + it('has a nullFilePatch that stubs all FilePatch methods', function() { + const rowRange = new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 10}); + + assert.isFalse(nullFilePatch.isPresent()); + assert.isFalse(nullFilePatch.getOldFile().isPresent()); + assert.isFalse(nullFilePatch.getNewFile().isPresent()); + assert.isFalse(nullFilePatch.getPatch().isPresent()); + assert.isNull(nullFilePatch.getOldPath()); + assert.isNull(nullFilePatch.getNewPath()); + assert.isNull(nullFilePatch.getOldMode()); + assert.isNull(nullFilePatch.getNewMode()); + assert.isNull(nullFilePatch.getOldSymlink()); + assert.isNull(nullFilePatch.getNewSymlink()); + assert.strictEqual(nullFilePatch.getByteSize(), 0); + assert.strictEqual(nullFilePatch.getBufferText(), ''); + assert.lengthOf(nullFilePatch.getAdditionRanges(), 0); + assert.lengthOf(nullFilePatch.getDeletionRanges(), 0); + assert.lengthOf(nullFilePatch.getNoNewlineRanges(), 0); + assert.isFalse(nullFilePatch.didChangeExecutableMode()); + assert.isFalse(nullFilePatch.hasSymlink()); + assert.isFalse(nullFilePatch.hasTypechange()); + assert.isNull(nullFilePatch.getPath()); + assert.isNull(nullFilePatch.getStatus()); + assert.lengthOf(nullFilePatch.getHunks(), 0); + assert.isFalse(nullFilePatch.getStagePatchForLines(new Set([0])).isPresent()); + assert.isFalse(nullFilePatch.getStagePatchForHunk(new Hunk({changes: [], rowRange})).isPresent()); + assert.isFalse(nullFilePatch.getUnstagePatchForLines(new Set([0])).isPresent()); + assert.isFalse(nullFilePatch.getUnstagePatchForHunk(new Hunk({changes: [], rowRange})).isPresent()); + assert.strictEqual(nullFilePatch.toString(), ''); + }); }); From 6e11034e076be601247d33402c3719a9e77d2f9e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 17 Aug 2018 15:33:03 -0400 Subject: [PATCH 0109/4053] Return nullFilePatch from getFilePatchForPath() --- lib/models/repository-states/state.js | 3 ++- test/models/repository.test.js | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 9c66928e7b..c9d5c82b55 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -2,6 +2,7 @@ import {nullCommit} from '../commit'; import BranchSet from '../branch-set'; import RemoteSet from '../remote-set'; import {nullOperationStates} from '../operation-states'; +import {nullFilePatch} from '../patch/file-patch'; /** * Map of registered subclasses to allow states to transition to one another without circular dependencies. @@ -274,7 +275,7 @@ export default class State { } getFilePatchForPath(filePath, options = {}) { - return Promise.resolve(null); + return Promise.resolve(nullFilePatch); } readFileFromIndex(filePath) { diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 3003496211..be105cd803 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -71,7 +71,7 @@ describe('Repository', function() { // Methods that resolve to null for (const method of [ - 'getFilePatchForPath', 'getAheadCount', 'getBehindCount', 'getConfig', 'getLastHistorySnapshots', 'getCache', + 'getAheadCount', 'getBehindCount', 'getConfig', 'getLastHistorySnapshots', 'getCache', ]) { assert.isNull(await repository[method]()); } @@ -110,6 +110,7 @@ describe('Repository', function() { assert.strictEqual(await repository.getHeadDescription(), '(no repository)'); assert.strictEqual(await repository.getOperationStates(), nullOperationStates); assert.strictEqual(await repository.getCommitMessage(), ''); + assert.isFalse((await repository.getFilePatchForPath('anything.txt')).isPresent()); }); it('returns a rejecting promise', async function() { @@ -402,6 +403,15 @@ describe('Repository', function() { assert.notEqual(await repo.getFilePatchForPath('a.txt'), filePatchA); assert.deepEqual(await repo.getFilePatchForPath('a.txt'), filePatchA); }); + + it('returns a nullFilePatch for unknown paths', async function() { + const workingDirPath = await cloneRepository('multiple-commits'); + const repo = new Repository(workingDirPath); + await repo.getLoadPromise(); + + const patch = await repo.getFilePatchForPath('no.txt'); + assert.isFalse(patch.isPresent()); + }); }); describe('isPartiallyStaged(filePath)', function() { @@ -468,7 +478,7 @@ describe('Repository', function() { assert.deepEqual(unstagedChanges, [path.join('subdir-1', 'a.txt')]); assert.deepEqual(stagedChanges, [path.join('subdir-1', 'a.txt')]); - await repo.applyPatchToIndex(unstagedPatch1.getUnstagePatch()); + await repo.applyPatchToIndex(unstagedPatch1.getUnstagePatchForLines(new Set([0, 1, 2]))); repo.refresh(); const unstagedPatch3 = await repo.getFilePatchForPath(path.join('subdir-1', 'a.txt')); assert.deepEqual(unstagedPatch3, unstagedPatch2); @@ -1513,7 +1523,7 @@ describe('Repository', function() { await repository.getLoadPromise(); await fs.writeFile(path.join(workdir, 'a.txt'), 'foo\nfoo-1\n', {encoding: 'utf8'}); - const patch = (await repository.getFilePatchForPath('a.txt')).getUnstagePatch(); + const patch = (await repository.getFilePatchForPath('a.txt')).getUnstagePatchForLines(new Set([0, 1])); await assertCorrectInvalidation({repository}, async () => { await repository.applyPatchToWorkdir(patch); @@ -1792,7 +1802,7 @@ describe('Repository', function() { const {repository, observer} = await wireUpObserver(); await fs.writeFile(path.join(workdir, 'a.txt'), 'boop\n', {encoding: 'utf8'}); - const patch = (await repository.getFilePatchForPath('a.txt')).getUnstagePatch(); + const patch = (await repository.getFilePatchForPath('a.txt')).getUnstagePatchForLines(new Set([0])); await assertCorrectInvalidation({repository}, async () => { await observer.start(); From 75ec2cb1a689f147abaee65fa780d55c98a79628 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 17 Aug 2018 15:59:06 -0400 Subject: [PATCH 0110/4053] Capture MarkerLayers as well as IDs --- lib/atom/marker-layer.js | 6 ++++++ test/atom/marker-layer.test.js | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/atom/marker-layer.js b/lib/atom/marker-layer.js index 9c483d7582..8cfc381daa 100644 --- a/lib/atom/marker-layer.js +++ b/lib/atom/marker-layer.js @@ -20,10 +20,12 @@ class BareMarkerLayer extends React.Component { editor: PropTypes.object, children: PropTypes.node, handleID: PropTypes.func, + handleLayer: PropTypes.func, }; static defaultProps = { handleID: () => {}, + handleLayer: () => {}, } constructor(props) { @@ -75,6 +77,8 @@ class BareMarkerLayer extends React.Component { componentWillUnmount() { this.layerHolder.map(layer => layer.destroy()); + this.layerHolder.setter(null); + this.props.handleLayer(undefined); this.sub.dispose(); } @@ -91,6 +95,8 @@ class BareMarkerLayer extends React.Component { this.layerHolder.setter( this.state.editorHolder.map(editor => editor.addMarkerLayer(options)).getOr(null), ); + + this.props.handleLayer(this.layerHolder.getOr(null)); this.props.handleID(this.getID()); } diff --git a/test/atom/marker-layer.test.js b/test/atom/marker-layer.test.js index 6fe699f2a1..1f140c3098 100644 --- a/test/atom/marker-layer.test.js +++ b/test/atom/marker-layer.test.js @@ -5,7 +5,7 @@ import MarkerLayer from '../../lib/atom/marker-layer'; import AtomTextEditor from '../../lib/atom/atom-text-editor'; describe('MarkerLayer', function() { - let atomEnv, editor, layerID; + let atomEnv, editor, layer, layerID; beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); @@ -16,6 +16,10 @@ describe('MarkerLayer', function() { atomEnv.destroy(); }); + function setLayer(object) { + layer = object; + } + function setLayerID(id) { layerID = id; } @@ -27,20 +31,24 @@ describe('MarkerLayer', function() { maintainHistory={true} persistent={true} handleID={setLayerID} + handleLayer={setLayer} />, ); - const layer = editor.getMarkerLayer(layerID); - assert.isTrue(layer.bufferMarkerLayer.maintainHistory); - assert.isTrue(layer.bufferMarkerLayer.persistent); + const theLayer = editor.getMarkerLayer(layerID); + assert.strictEqual(theLayer, layer); + assert.isTrue(theLayer.bufferMarkerLayer.maintainHistory); + assert.isTrue(theLayer.bufferMarkerLayer.persistent); }); it('removes its layer on unmount', function() { - const wrapper = mount(); + const wrapper = mount(); assert.isDefined(editor.getMarkerLayer(layerID)); + assert.isDefined(layer); wrapper.unmount(); assert.isUndefined(editor.getMarkerLayer(layerID)); + assert.isUndefined(layer); }); it('inherits an editor from a parent node', function() { From 26f7cbec8e5c10415f233f8594ac5fb6add49a3b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 17 Aug 2018 20:32:08 -0400 Subject: [PATCH 0111/4053] Get the Marker model --- lib/atom/marker.js | 5 +++++ test/atom/marker.test.js | 42 +++++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/lib/atom/marker.js b/lib/atom/marker.js index 1ebef279e6..05cbb3d989 100644 --- a/lib/atom/marker.js +++ b/lib/atom/marker.js @@ -29,11 +29,13 @@ class BareMarker extends React.Component { children: PropTypes.node, onDidChange: PropTypes.func, handleID: PropTypes.func, + handleMarker: PropTypes.func, } static defaultProps = { onDidChange: () => {}, handleID: () => {}, + handleMarker: () => {}, } constructor(props) { @@ -43,6 +45,9 @@ class BareMarker extends React.Component { this.subs = new CompositeDisposable(); this.markerHolder = new RefHolder(); + this.markerHolder.observe(marker => { + this.props.handleMarker(marker); + }); this.decorable = { holder: this.markerHolder, diff --git a/test/atom/marker.test.js b/test/atom/marker.test.js index 9d304bd2ee..7bfc30f626 100644 --- a/test/atom/marker.test.js +++ b/test/atom/marker.test.js @@ -7,7 +7,7 @@ import AtomTextEditor from '../../lib/atom/atom-text-editor'; import MarkerLayer from '../../lib/atom/marker-layer'; describe('Marker', function() { - let atomEnv, editor, markerID; + let atomEnv, editor, marker, markerID; beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); @@ -18,19 +18,29 @@ describe('Marker', function() { atomEnv.destroy(); }); + function setMarker(m) { + marker = m; + } + function setMarkerID(id) { markerID = id; } it('adds its marker on mount with default properties', function() { mount( - , + , ); - const marker = editor.getMarker(markerID); - assert.isTrue(marker.getBufferRange().isEqual([[0, 0], [10, 0]])); - assert.strictEqual(marker.bufferMarker.invalidate, 'overlap'); - assert.isFalse(marker.isReversed()); + const theMarker = editor.getMarker(markerID); + assert.strictEqual(theMarker, marker); + assert.isTrue(theMarker.getBufferRange().isEqual([[0, 0], [10, 0]])); + assert.strictEqual(theMarker.bufferMarker.invalidate, 'overlap'); + assert.isFalse(theMarker.isReversed()); }); it('configures its marker', function() { @@ -45,10 +55,10 @@ describe('Marker', function() { />, ); - const marker = editor.getMarker(markerID); - assert.isTrue(marker.getBufferRange().isEqual([[1, 2], [4, 5]])); - assert.isTrue(marker.isReversed()); - assert.strictEqual(marker.bufferMarker.invalidate, 'never'); + const theMarker = editor.getMarker(markerID); + assert.isTrue(theMarker.getBufferRange().isEqual([[1, 2], [4, 5]])); + assert.isTrue(theMarker.isReversed()); + assert.strictEqual(theMarker.bufferMarker.invalidate, 'never'); }); it('prefers marking a MarkerLayer to a TextEditor', function() { @@ -63,8 +73,8 @@ describe('Marker', function() { />, ); - const marker = layer.getMarker(markerID); - assert.strictEqual(marker.layer, layer); + const theMarker = layer.getMarker(markerID); + assert.strictEqual(theMarker.layer, layer); }); it('destroys its marker on unmount', function() { @@ -85,8 +95,8 @@ describe('Marker', function() { ); const theEditor = wrapper.instance().getModel(); - const marker = theEditor.getMarker(markerID); - assert.isTrue(marker.getBufferRange().isEqual([[0, 0], [0, 0]])); + const theMarker = theEditor.getMarker(markerID); + assert.isTrue(theMarker.getBufferRange().isEqual([[0, 0], [0, 0]])); }); it('marks a marker layer from a parent node', function() { @@ -101,7 +111,7 @@ describe('Marker', function() { const theEditor = wrapper.instance().getModel(); const layer = theEditor.getMarkerLayer(layerID); - const marker = layer.getMarker(markerID); - assert.isTrue(marker.getBufferRange().isEqual([[0, 0], [0, 0]])); + const theMarker = layer.getMarker(markerID); + assert.isTrue(theMarker.getBufferRange().isEqual([[0, 0], [0, 0]])); }); }); From 97d4ee23a84f40fd3151d03e8d89fbabea5c7d48 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 09:08:19 -0400 Subject: [PATCH 0112/4053] IndexedRowRange.includesRow() --- lib/models/indexed-row-range.js | 8 ++++++++ test/models/indexed-row-range.test.js | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js index 87b9b6d673..58089ac7be 100644 --- a/lib/models/indexed-row-range.js +++ b/lib/models/indexed-row-range.js @@ -23,6 +23,10 @@ export default class IndexedRowRange { return this.bufferRange.getRowCount(); } + includesRow(bufferRow) { + return this.bufferRange.intersectsRow(bufferRow); + } + toStringIn(buffer, prefix) { return buffer.slice(this.startOffset, this.endOffset).replace(/(^|\n)(?!$)/g, '$&' + prefix); } @@ -115,6 +119,10 @@ export const nullIndexedRowRange = { return 0; }, + includesRow() { + return false; + }, + toStringIn() { return ''; }, diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index 27daef0d27..bfda49a929 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -20,6 +20,20 @@ describe('IndexedRowRange', function() { assert.sameMembers(range.getBufferRows(), [2, 3, 4, 5, 6, 7, 8]); }); + it('has a buffer row inclusion predicate', function() { + const range = new IndexedRowRange({ + bufferRange: [[2, 0], [4, 0]], + startOffset: 0, + endOffset: 10, + }); + + assert.isFalse(range.includesRow(1)); + assert.isTrue(range.includesRow(2)); + assert.isTrue(range.includesRow(3)); + assert.isTrue(range.includesRow(4)); + assert.isFalse(range.includesRow(5)); + }); + it('creates a Range from its first line', function() { const range = new IndexedRowRange({ bufferRange: [[2, 0], [8, 0]], @@ -184,6 +198,7 @@ describe('IndexedRowRange', function() { assert.deepEqual(nullIndexedRowRange.intersectRowsIn(new Set([0, 1, 2]), ''), []); assert.strictEqual(nullIndexedRowRange.toStringIn('', '+'), ''); assert.strictEqual(nullIndexedRowRange.bufferRowCount(), 0); + assert.isFalse(nullIndexedRowRange.includesRow(4)); assert.isFalse(nullIndexedRowRange.isPresent()); }); }); From a271293846d1cb7a9555164df39cb841476046bf Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 09:08:43 -0400 Subject: [PATCH 0113/4053] nullIndexedRowRange should implement getBufferRows --- lib/models/indexed-row-range.js | 4 ++++ test/models/indexed-row-range.test.js | 1 + 2 files changed, 5 insertions(+) diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js index 58089ac7be..40bdd01ec8 100644 --- a/lib/models/indexed-row-range.js +++ b/lib/models/indexed-row-range.js @@ -119,6 +119,10 @@ export const nullIndexedRowRange = { return 0; }, + getBufferRows() { + return []; + }, + includesRow() { return false; }, diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index bfda49a929..81999d2bd6 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -198,6 +198,7 @@ describe('IndexedRowRange', function() { assert.deepEqual(nullIndexedRowRange.intersectRowsIn(new Set([0, 1, 2]), ''), []); assert.strictEqual(nullIndexedRowRange.toStringIn('', '+'), ''); assert.strictEqual(nullIndexedRowRange.bufferRowCount(), 0); + assert.lengthOf(nullIndexedRowRange.getBufferRows(), 0); assert.isFalse(nullIndexedRowRange.includesRow(4)); assert.isFalse(nullIndexedRowRange.isPresent()); }); From b3a1b4ab1fdf599cd3af01342b3f10e26cf3864a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 09:09:12 -0400 Subject: [PATCH 0114/4053] Hunk.includesBufferRow() predicate --- lib/models/patch/hunk.js | 4 ++++ test/models/patch/hunk.test.js | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index cf33d955e2..fcce5a2e84 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -119,6 +119,10 @@ export default class Hunk { return this.rowRange.bufferRowCount(); } + includesBufferRow(row) { + return this.rowRange.includesRow(row); + } + changedLineCount() { return this.changes.reduce((count, change) => change.when({ nonewline: () => count, diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 9a07d6e85b..147cb14a08 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -162,6 +162,23 @@ describe('Hunk', function() { assert.sameMembers(Array.from(h.getBufferRows()), [6, 7, 8, 9, 10]); }); + it('determines if a buffer row is part of this hunk', function() { + const h = new Hunk({ + ...attrs, + rowRange: new IndexedRowRange({ + bufferRange: [[3, 0], [5, 0]], + startOffset: 30, + endOffset: 55, + }), + }); + + assert.isFalse(h.includesBufferRow(2)); + assert.isTrue(h.includesBufferRow(3)); + assert.isTrue(h.includesBufferRow(4)); + assert.isTrue(h.includesBufferRow(5)); + assert.isFalse(h.includesBufferRow(6)); + }); + it('computes the total number of changed lines', function() { const h0 = new Hunk({ ...attrs, From 7436a7a1976d2f81699a64af087e2ec853266642 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 09:09:32 -0400 Subject: [PATCH 0115/4053] Cover the other branch of Hunk.getNoNewlineRange() --- test/models/patch/hunk.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 147cb14a08..16d7ed8261 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -57,6 +57,21 @@ describe('Hunk', function() { assert.isNull(h.getNoNewlineRange()); }); + it('returns the range of a no-newline region', function() { + const h = new Hunk({ + ...attrs, + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 6, endOffset: 7})), + new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [5, 0]], startOffset: 8, endOffset: 9})), + new NoNewline(new IndexedRowRange({bufferRange: [[10, 0], [10, 0]], startOffset: 100, endOffset: 120})), + ], + }); + + const nl = h.getNoNewlineRange(); + assert.isNotNull(nl); + assert.deepEqual(nl.serialize(), [[10, 0], [10, 0]]); + }); + it('creates its start range for decoration placement', function() { const h = new Hunk({ ...attrs, From af6bd60839e53ded988cc1b5ebd4a7a8f33ca629 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 09:09:53 -0400 Subject: [PATCH 0116/4053] We didn't need Hunk::invert() any more --- test/models/patch/hunk.test.js | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 16d7ed8261..67574067b4 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -213,33 +213,6 @@ describe('Hunk', function() { assert.strictEqual(h1.changedLineCount(), 0); }); - it('computes an inverted hunk', function() { - const original = new Hunk({ - ...attrs, - oldStartRow: 0, - newStartRow: 1, - oldRowCount: 2, - newRowCount: 3, - sectionHeading: 'the-heading', - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[2, 0], [4, 0]], startOffset: 0, endOffset: 0})), - new Addition(new IndexedRowRange({bufferRange: [[6, 0], [6, 0]], startOffset: 0, endOffset: 0})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [10, 0]], startOffset: 0, endOffset: 0})), - new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0})), - ], - }); - - const inverted = original.invert(); - assert.strictEqual(inverted.getOldStartRow(), 1); - assert.strictEqual(inverted.getNewStartRow(), 0); - assert.strictEqual(inverted.getOldRowCount(), 3); - assert.strictEqual(inverted.getNewRowCount(), 2); - assert.strictEqual(inverted.getSectionHeading(), 'the-heading'); - assert.lengthOf(inverted.getAdditionRanges(), 1); - assert.lengthOf(inverted.getDeletionRanges(), 2); - assert.deepEqual(inverted.getNoNewlineRange().serialize(), [[12, 0], [12, 0]]); - }); - describe('toStringIn()', function() { it('prints its header', function() { const h = new Hunk({ From 57c2db07f4dedfc67ccf031d4fadef22e6978a80 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 09:12:41 -0400 Subject: [PATCH 0117/4053] We want to decorate the full row range of a Hunk --- lib/models/patch/hunk.js | 4 ++-- test/models/patch/hunk.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index fcce5a2e84..cfb9c38822 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -107,8 +107,8 @@ export default class Hunk { return this.rowRange; } - getStartRange() { - return this.rowRange.getStartRange(); + getBufferRange() { + return this.rowRange.bufferRange; } getBufferRows() { diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 67574067b4..f04129bef3 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -72,7 +72,7 @@ describe('Hunk', function() { assert.deepEqual(nl.serialize(), [[10, 0], [10, 0]]); }); - it('creates its start range for decoration placement', function() { + it('creates its row range for decoration placement', function() { const h = new Hunk({ ...attrs, rowRange: new IndexedRowRange({ @@ -82,7 +82,7 @@ describe('Hunk', function() { }), }); - assert.deepEqual(h.getStartRange().serialize(), [[3, 0], [3, 0]]); + assert.deepEqual(h.getBufferRange().serialize(), [[3, 0], [6, 0]]); }); it('generates a patch section header', function() { From 7b530df5810dfac7ccc2abcdc0a4024bf111120a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 09:21:51 -0400 Subject: [PATCH 0118/4053] Compute old and new diff rows corresponding to a buffer row --- lib/models/patch/hunk.js | 8 +++++++ test/models/patch/hunk.test.js | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index cfb9c38822..c9c1495e5a 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -123,6 +123,14 @@ export default class Hunk { return this.rowRange.includesRow(row); } + getOldRowAt(row) { + return this.oldStartRow + (row - this.rowRange.bufferRange.start.row); + } + + getNewRowAt(row) { + return this.newStartRow + (row - this.rowRange.bufferRange.start.row); + } + changedLineCount() { return this.changes.reduce((count, change) => change.when({ nonewline: () => count, diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index f04129bef3..77c21cc407 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -194,6 +194,44 @@ describe('Hunk', function() { assert.isFalse(h.includesBufferRow(6)); }); + it('computes the old file row for a buffer row', function() { + const h = new Hunk({ + ...attrs, + rowRange: new IndexedRowRange({ + bufferRange: [[2, 0], [10, 0]], + startOffset: 10, + endOffset: 100, + }), + oldStartRow: 10, + oldRowCount: 4, + newStartRow: 20, + newRowCount: 6, + }); + + assert.strictEqual(h.getOldRowAt(2), 10); + assert.strictEqual(h.getOldRowAt(3), 11); + assert.strictEqual(h.getOldRowAt(10), 18); + }); + + it('computes the new file row for a buffer row', function() { + const h = new Hunk({ + ...attrs, + rowRange: new IndexedRowRange({ + bufferRange: [[2, 0], [10, 0]], + startOffset: 10, + endOffset: 100, + }), + oldStartRow: 10, + oldRowCount: 4, + newStartRow: 20, + newRowCount: 6, + }); + + assert.strictEqual(h.getNewRowAt(2), 20); + assert.strictEqual(h.getNewRowAt(3), 21); + assert.strictEqual(h.getNewRowAt(10), 28); + }); + it('computes the total number of changed lines', function() { const h0 = new Hunk({ ...attrs, From 0e9b51593c49e84b04c8742f97be084a16823e9e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 09:38:53 -0400 Subject: [PATCH 0119/4053] Measure the maximum number of digits in a diff line number --- lib/models/patch/file-patch.js | 4 ++++ lib/models/patch/hunk.js | 7 +++++++ lib/models/patch/patch.js | 5 +++++ test/models/patch/file-patch.test.js | 1 + test/models/patch/hunk.test.js | 20 ++++++++++++++++++++ test/models/patch/patch.test.js | 28 ++++++++++++++++++++++++++++ 6 files changed, 65 insertions(+) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 188cea71d3..df93c80eed 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -57,6 +57,10 @@ export default class FilePatch { return this.getPatch().getBufferText(); } + getMaxLineNumberWidth() { + return this.getPatch().getMaxLineNumberWidth(); + } + getAdditionRanges() { return this.getHunks().reduce((acc, hunk) => { acc.push(...hunk.getAdditionRanges()); diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index c9c1495e5a..3236d7a9cd 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -131,6 +131,13 @@ export default class Hunk { return this.newStartRow + (row - this.rowRange.bufferRange.start.row); } + getMaxLineNumberWidth() { + return Math.max( + (this.oldStartRow + this.oldRowCount).toString().length, + (this.newStartRow + this.newRowCount).toString().length, + ); + } + changedLineCount() { return this.changes.reduce((count, change) => change.when({ nonewline: () => count, diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 1b9be2bbe3..3e23f94955 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -90,6 +90,11 @@ export default class Patch { return this.changedLineCount; } + getMaxLineNumberWidth() { + const lastHunk = this.hunks[this.hunks.length - 1]; + return lastHunk ? lastHunk.getMaxLineNumberWidth() : 0; + } + clone(opts = {}) { return new this.constructor({ status: opts.status !== undefined ? opts.status : this.getStatus(), diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index c21204f4a8..81ad43e0a9 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -38,6 +38,7 @@ describe('FilePatch', function() { assert.strictEqual(filePatch.getByteSize(), 10); assert.strictEqual(filePatch.getBufferText(), bufferText); + assert.strictEqual(filePatch.getMaxLineNumberWidth(), 1); }); it('accesses a file path from either side of the patch', function() { diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 77c21cc407..63610a8fb9 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -251,6 +251,26 @@ describe('Hunk', function() { assert.strictEqual(h1.changedLineCount(), 0); }); + it('determines the maximum number of digits necessary to represent a diff line number', function() { + const h0 = new Hunk({ + ...attrs, + oldStartRow: 200, + oldRowCount: 10, + newStartRow: 999, + newRowCount: 1, + }); + assert.strictEqual(h0.getMaxLineNumberWidth(), 4); + + const h1 = new Hunk({ + ...attrs, + oldStartRow: 5000, + oldRowCount: 10, + newStartRow: 20000, + newRowCount: 20, + }); + assert.strictEqual(h1.getMaxLineNumberWidth(), 5); + }); + describe('toStringIn()', function() { it('prints its header', function() { const h = new Hunk({ diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index d4b4746f0c..398bb95b01 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -51,6 +51,34 @@ describe('Patch', function() { assert.strictEqual(p.getChangedLineCount(), 10); }); + it('computes the maximum number of digits needed to display a diff line number', function() { + const hunks = [ + new Hunk({ + oldStartRow: 0, + oldRowCount: 1, + newStartRow: 0, + newRowCount: 1, + sectionHeading: 'zero', + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [5, 0]], startOffset: 0, endOffset: 30}), + changes: [], + }), + new Hunk({ + oldStartRow: 98, + oldRowCount: 5, + newStartRow: 95, + newRowCount: 3, + sectionHeading: 'one', + rowRange: new IndexedRowRange({bufferRange: [[6, 0], [15, 0]], startOffset: 30, endOffset: 80}), + changes: [], + }), + ]; + const p0 = new Patch({status: 'modified', hunks, bufferText: 'bufferText'}); + assert.strictEqual(p0.getMaxLineNumberWidth(), 3); + + const p1 = new Patch({status: 'deleted', hunks: [], bufferText: ''}); + assert.strictEqual(p1.getMaxLineNumberWidth(), 0); + }); + it('clones itself with optionally overridden properties', function() { const original = new Patch({status: 'modified', hunks: [], bufferText: 'bufferText'}); From 8db5e339801e778b3a5004de2ef74bbc6d132338 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 10:47:48 -0400 Subject: [PATCH 0120/4053] Remove unused IndexedRowRange::getStartRange() --- lib/models/indexed-row-range.js | 3 --- test/models/indexed-row-range.test.js | 9 --------- 2 files changed, 12 deletions(-) diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js index 40bdd01ec8..c5fb21f0ca 100644 --- a/lib/models/indexed-row-range.js +++ b/lib/models/indexed-row-range.js @@ -14,9 +14,6 @@ export default class IndexedRowRange { return this.bufferRange.getRows(); } - getStartRange() { - const start = this.bufferRange.start; - return new Range(start, start); } bufferRowCount() { diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index 81999d2bd6..6aca39c5e7 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -34,15 +34,6 @@ describe('IndexedRowRange', function() { assert.isFalse(range.includesRow(5)); }); - it('creates a Range from its first line', function() { - const range = new IndexedRowRange({ - bufferRange: [[2, 0], [8, 0]], - startOffset: 0, - endOffset: 10, - }); - assert.deepEqual(range.getStartRange().serialize(), [[2, 0], [2, 0]]); - }); - it('extracts its offset range from buffer text with toStringIn()', function() { const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n'; const range = new IndexedRowRange({ From 27e60ab7f8ccb912ecb7263f8d5ee31811809172 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 10:48:09 -0400 Subject: [PATCH 0121/4053] IndexedRowRange accessor for the starting buffer row --- lib/models/indexed-row-range.js | 14 ++++++++++++-- test/models/indexed-row-range.test.js | 17 ++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js index c5fb21f0ca..aa080338a0 100644 --- a/lib/models/indexed-row-range.js +++ b/lib/models/indexed-row-range.js @@ -10,10 +10,12 @@ export default class IndexedRowRange { this.endOffset = endOffset; } - getBufferRows() { - return this.bufferRange.getRows(); + getStartBufferRow() { + return this.bufferRange.start.row; } + getBufferRows() { + return this.bufferRange.getRows(); } bufferRowCount() { @@ -112,6 +114,10 @@ export const nullIndexedRowRange = { endOffset: Infinity, + getStartBufferRow() { + return null; + }, + bufferRowCount() { return 0; }, @@ -136,6 +142,10 @@ export const nullIndexedRowRange = { return this; }, + serialize() { + return null; + }, + isPresent() { return false; }, diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index 6aca39c5e7..99730df4c9 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -11,6 +11,15 @@ describe('IndexedRowRange', function() { assert.deepEqual(range.bufferRowCount(), 2); }); + it('returns its starting buffer row', function() { + const range = new IndexedRowRange({ + bufferRange: [[2, 0], [8, 0]], + startOffset: 0, + endOffset: 10, + }); + assert.strictEqual(range.getStartBufferRow(), 2); + }); + it('returns an array of the covered rows', function() { const range = new IndexedRowRange({ bufferRange: [[2, 0], [8, 0]], @@ -186,11 +195,13 @@ describe('IndexedRowRange', function() { }); it('returns appropriate values from nullIndexedRowRange methods', function() { - assert.deepEqual(nullIndexedRowRange.intersectRowsIn(new Set([0, 1, 2]), ''), []); - assert.strictEqual(nullIndexedRowRange.toStringIn('', '+'), ''); - assert.strictEqual(nullIndexedRowRange.bufferRowCount(), 0); + assert.isNull(nullIndexedRowRange.getStartBufferRow()); assert.lengthOf(nullIndexedRowRange.getBufferRows(), 0); + assert.strictEqual(nullIndexedRowRange.bufferRowCount(), 0); assert.isFalse(nullIndexedRowRange.includesRow(4)); + assert.strictEqual(nullIndexedRowRange.toStringIn('', '+'), ''); + assert.deepEqual(nullIndexedRowRange.intersectRowsIn(new Set([0, 1, 2]), ''), []); + assert.isNull(nullIndexedRowRange.serialize()); assert.isFalse(nullIndexedRowRange.isPresent()); }); }); From fc64d1f6b30db14827ab2e642aef578fe411ea64 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 10:48:31 -0400 Subject: [PATCH 0122/4053] Delegate some Region methods to its IndexedRowRange --- lib/models/patch/region.js | 12 ++++++++---- test/models/patch/region.test.js | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index 4535ae9a07..ce021b1185 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -7,6 +7,14 @@ class Region { return this.range; } + getStartBufferRow() { + return this.range.getStartBufferRow(); + } + + includesBufferRow(row) { + return this.range.includesRow(row); + } + isAddition() { return false; } @@ -27,10 +35,6 @@ class Region { return this.range.getBufferRows(); } - getStartRange() { - return this.range.getStartRange(); - } - bufferRowCount() { return this.range.bufferRowCount(); } diff --git a/test/models/patch/region.test.js b/test/models/patch/region.test.js index c7ca5dd6b6..5be6612b12 100644 --- a/test/models/patch/region.test.js +++ b/test/models/patch/region.test.js @@ -16,14 +16,15 @@ describe('Regions', function() { addition = new Addition(range); }); - it('has a range accessor', function() { + it('has range accessors', function() { assert.strictEqual(addition.getRowRange(), range); + assert.strictEqual(addition.getStartBufferRow(), 1); }); it('delegates some methods to its row range', function() { assert.sameMembers(Array.from(addition.getBufferRows()), [1, 2, 3]); - assert.deepEqual(addition.getStartRange().serialize(), [[1, 0], [1, 0]]); assert.strictEqual(addition.bufferRowCount(), 3); + assert.isTrue(addition.includesBufferRow(2)); }); it('can be recognized by the isAddition predicate', function() { From 940711c90e1e4ecfce1a6a858b24b2663ad57de3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 10:48:59 -0400 Subject: [PATCH 0123/4053] Skip regions when computing old and new diff line numbers --- lib/models/patch/hunk.js | 48 +++++++++++++++++++++++++++-- test/models/patch/hunk.test.js | 56 +++++++++++++++++++++++----------- 2 files changed, 85 insertions(+), 19 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 3236d7a9cd..3f577e5643 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -124,11 +124,55 @@ export default class Hunk { } getOldRowAt(row) { - return this.oldStartRow + (row - this.rowRange.bufferRange.start.row); + let current = this.oldStartRow; + + for (const region of this.getRegions()) { + if (region.range.includesRow(row)) { + const offset = row - region.getStartBufferRow(); + + return region.when({ + unchanged: () => current + offset, + addition: () => null, + deletion: () => current + offset, + nonewline: () => null, + }); + } else { + current += region.when({ + unchanged: () => region.bufferRowCount(), + addition: () => 0, + deletion: () => region.bufferRowCount(), + nonewline: () => 0, + }); + } + } + + return null; } getNewRowAt(row) { - return this.newStartRow + (row - this.rowRange.bufferRange.start.row); + let current = this.newStartRow; + + for (const region of this.getRegions()) { + if (region.includesBufferRow(row)) { + const offset = row - region.getStartBufferRow(); + + return region.when({ + unchanged: () => current + offset, + addition: () => current + offset, + deletion: () => null, + nonewline: () => null, + }); + } else { + current += region.when({ + unchanged: () => region.bufferRowCount(), + addition: () => region.bufferRowCount(), + deletion: () => 0, + nonewline: () => 0, + }); + } + } + + return null; } getMaxLineNumberWidth() { diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 63610a8fb9..faf10cc104 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -197,39 +197,61 @@ describe('Hunk', function() { it('computes the old file row for a buffer row', function() { const h = new Hunk({ ...attrs, - rowRange: new IndexedRowRange({ - bufferRange: [[2, 0], [10, 0]], - startOffset: 10, - endOffset: 100, - }), oldStartRow: 10, - oldRowCount: 4, + oldRowCount: 6, newStartRow: 20, - newRowCount: 6, + newRowCount: 7, + rowRange: new IndexedRowRange({bufferRange: [[2, 0], [12, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 0, endOffset: 0})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, 0]], startOffset: 0, endOffset: 0})), + new Addition(new IndexedRowRange({bufferRange: [[11, 0], [11, 0]], startOffset: 0, endOffset: 0})), + new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0})), + ], }); assert.strictEqual(h.getOldRowAt(2), 10); - assert.strictEqual(h.getOldRowAt(3), 11); - assert.strictEqual(h.getOldRowAt(10), 18); + assert.isNull(h.getOldRowAt(3)); + assert.isNull(h.getOldRowAt(4)); + assert.isNull(h.getOldRowAt(5)); + assert.strictEqual(h.getOldRowAt(6), 11); + assert.strictEqual(h.getOldRowAt(7), 12); + assert.strictEqual(h.getOldRowAt(8), 13); + assert.strictEqual(h.getOldRowAt(9), 14); + assert.strictEqual(h.getOldRowAt(10), 15); + assert.isNull(h.getOldRowAt(11)); + assert.isNull(h.getOldRowAt(12)); + assert.isNull(h.getOldRowAt(13)); }); it('computes the new file row for a buffer row', function() { const h = new Hunk({ ...attrs, - rowRange: new IndexedRowRange({ - bufferRange: [[2, 0], [10, 0]], - startOffset: 10, - endOffset: 100, - }), oldStartRow: 10, - oldRowCount: 4, + oldRowCount: 6, newStartRow: 20, - newRowCount: 6, + newRowCount: 7, + rowRange: new IndexedRowRange({bufferRange: [[2, 0], [12, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 0, endOffset: 0})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, 0]], startOffset: 0, endOffset: 0})), + new Addition(new IndexedRowRange({bufferRange: [[11, 0], [11, 0]], startOffset: 0, endOffset: 0})), + new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0})), + ], }); assert.strictEqual(h.getNewRowAt(2), 20); assert.strictEqual(h.getNewRowAt(3), 21); - assert.strictEqual(h.getNewRowAt(10), 28); + assert.strictEqual(h.getNewRowAt(4), 22); + assert.strictEqual(h.getNewRowAt(5), 23); + assert.strictEqual(h.getNewRowAt(6), 24); + assert.isNull(h.getNewRowAt(7)); + assert.isNull(h.getNewRowAt(8)); + assert.isNull(h.getNewRowAt(9)); + assert.strictEqual(h.getNewRowAt(10), 25); + assert.strictEqual(h.getNewRowAt(11), 26); + assert.isNull(h.getNewRowAt(12)); + assert.isNull(h.getNewRowAt(13)); }); it('computes the total number of changed lines', function() { From 4133f2dda10a6d672d11b1641310559d82f06516 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 12:27:09 -0400 Subject: [PATCH 0124/4053] getRepositoryForWorkdir is no longer required for RootController --- lib/controllers/root-controller.js | 7 +------ test/controllers/root-controller.test.js | 4 +--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 5184328e4b..bd61553dfe 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -40,7 +40,6 @@ export default class RootController extends React.Component { project: PropTypes.object.isRequired, loginModel: PropTypes.object.isRequired, confirm: PropTypes.func.isRequired, - getRepositoryForWorkdir: PropTypes.func.isRequired, createRepositoryForProjectPath: PropTypes.func, cloneRepositoryForProjectPath: PropTypes.func, repository: PropTypes.object.isRequired, @@ -63,7 +62,7 @@ export default class RootController extends React.Component { super(props, context); autobind( this, - 'installReactDevTools', 'getRepositoryForWorkdir', 'clearGithubToken', 'initializeRepo', 'showOpenIssueishDialog', + 'installReactDevTools', 'clearGithubToken', 'initializeRepo', 'showOpenIssueishDialog', 'showWaterfallDiagnostics', 'showCacheDiagnostics', 'acceptClone', 'cancelClone', 'acceptInit', 'cancelInit', 'acceptOpenIssueish', 'cancelOpenIssueish', 'surfaceFromFileAtPath', 'destroyFilePatchPaneItems', 'destroyEmptyFilePatchPaneItems', 'openCloneDialog', 'quietlySelectItem', 'viewUnstagedChangesForCurrentFile', @@ -377,10 +376,6 @@ export default class RootController extends React.Component { devTools.default(devTools.REACT_DEVELOPER_TOOLS); } - getRepositoryForWorkdir(workdir) { - return this.props.getRepositoryForWorkdir(workdir); - } - componentWillUnmount() { this.subscription.dispose(); } diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index fd6d134dfa..59bf57c7b3 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -22,7 +22,7 @@ import RootController from '../../lib/controllers/root-controller'; describe('RootController', function() { let atomEnv, app; let workspace, commandRegistry, notificationManager, tooltips, config, confirm, deserializers, grammars, project; - let workdirContextPool, getRepositoryForWorkdir; + let workdirContextPool; beforeEach(function() { atomEnv = global.buildAtomEnvironment(); @@ -36,7 +36,6 @@ describe('RootController', function() { project = atomEnv.project; workdirContextPool = new WorkdirContextPool(); - getRepositoryForWorkdir = sinon.stub(); const loginModel = new GithubLoginModel(InMemoryStrategy); const absentRepository = Repository.absent(); @@ -60,7 +59,6 @@ describe('RootController', function() { startOpen={false} startRevealed={false} workdirContextPool={workdirContextPool} - getRepositoryForWorkdir={getRepositoryForWorkdir} /> ); }); From 71c22771677d48ef167e2e558346f74133c71c60 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 12:27:43 -0400 Subject: [PATCH 0125/4053] Update RootController to use getBufferRows() instead of getLines() --- test/controllers/root-controller.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 59bf57c7b3..55ded4449e 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -503,7 +503,7 @@ describe('RootController', function() { sinon.stub(repository, 'applyPatchToWorkdir'); sinon.stub(notificationManager, 'addError'); // unmodified buffer - const hunkLines = unstagedFilePatch.getHunks()[0].getLines(); + const hunkLines = unstagedFilePatch.getHunks()[0].getBufferRows(); await wrapper.instance().discardLines(unstagedFilePatch, new Set([hunkLines[0]])); assert.isTrue(repository.applyPatchToWorkdir.calledOnce); assert.isFalse(notificationManager.addError.called); @@ -511,7 +511,7 @@ describe('RootController', function() { // modified buffer repository.applyPatchToWorkdir.reset(); editor.setText('modify contents'); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getLines())); + await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows())); assert.isFalse(repository.applyPatchToWorkdir.called); const notificationArgs = notificationManager.addError.args[0]; assert.equal(notificationArgs[0], 'Cannot discard lines.'); @@ -573,14 +573,14 @@ describe('RootController', function() { it('reverses last discard for file path', async () => { const contents1 = fs.readFileSync(absFilePath, 'utf8'); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getLines().slice(0, 2))); + await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); const contents2 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents1, contents2); await repository.refresh(); unstagedFilePatch = await repository.getFilePatchForPath('sample.js'); wrapper.setState({filePatch: unstagedFilePatch}); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getLines().slice(2, 4))); + await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(2, 4))); const contents3 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents2, contents3); @@ -592,7 +592,7 @@ describe('RootController', function() { it('does not undo if buffer is modified', async () => { const contents1 = fs.readFileSync(absFilePath, 'utf8'); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getLines().slice(0, 2))); + await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); const contents2 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents1, contents2); @@ -616,7 +616,7 @@ describe('RootController', function() { describe('when file content has changed since last discard', () => { it('successfully undoes discard if changes do not conflict', async () => { const contents1 = fs.readFileSync(absFilePath, 'utf8'); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getLines().slice(0, 2))); + await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); const contents2 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents1, contents2); @@ -636,7 +636,7 @@ describe('RootController', function() { await repository.git.exec(['config', 'merge.conflictstyle', 'diff3']); const contents1 = fs.readFileSync(absFilePath, 'utf8'); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getLines().slice(0, 2))); + await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); const contents2 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents1, contents2); @@ -699,11 +699,11 @@ describe('RootController', function() { it('clears the discard history if the last blob is no longer valid', async () => { // this would occur in the case of garbage collection cleaning out the blob - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getLines().slice(0, 2))); + await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); await repository.refresh(); unstagedFilePatch = await repository.getFilePatchForPath('sample.js'); wrapper.setState({filePatch: unstagedFilePatch}); - const {beforeSha} = await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getLines().slice(2, 4))); + const {beforeSha} = await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(2, 4))); // remove blob from git object store fs.unlinkSync(path.join(repository.getGitDirectoryPath(), 'objects', beforeSha.slice(0, 2), beforeSha.slice(2))); From 47542e5a95110dd3f04d904c467f70df9c2f6161 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 12:38:20 -0400 Subject: [PATCH 0126/4053] Pass additional props to render the FilePatchItem --- test/items/file-patch-item.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/items/file-patch-item.test.js b/test/items/file-patch-item.test.js index d588233824..57960ca33b 100644 --- a/test/items/file-patch-item.test.js +++ b/test/items/file-patch-item.test.js @@ -29,7 +29,10 @@ describe('FilePatchItem', function() { function buildPaneApp(overrideProps = {}) { const props = { workdirContextPool: pool, + workspace: atomEnv.workspace, tooltips: atomEnv.tooltips, + discardLines: () => {}, + undoLastDiscard: () => {}, ...overrideProps, }; From 33cc8c17242979412bc566c59e5c124a1272c8b5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 20 Aug 2018 16:04:19 -0400 Subject: [PATCH 0127/4053] Pass additional required props in FilePatchContainer tests --- test/containers/file-patch-container.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/containers/file-patch-container.test.js b/test/containers/file-patch-container.test.js index 02fdd93eed..0fcaee7717 100644 --- a/test/containers/file-patch-container.test.js +++ b/test/containers/file-patch-container.test.js @@ -34,6 +34,11 @@ describe('FilePatchContainer', function() { repository, stagingStatus: 'unstaged', relPath: 'a.txt', + workspace: atomEnv.workspace, + tooltips: atomEnv.tooltips, + discardLines: () => {}, + undoLastDiscard: () => {}, + destroy: () => {}, ...overrideProps, }; @@ -45,11 +50,6 @@ describe('FilePatchContainer', function() { assert.isTrue(wrapper.find('LoadingView').exists()); }); - it('renders a message for an empty patch', async function() { - const wrapper = mount(buildApp({relPath: 'c.txt'})); - await assert.async.isTrue(wrapper.update().find('span.icon-info').exists()); - }); - it('renders a FilePatchView', async function() { const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); await assert.async.isTrue(wrapper.update().find('FilePatchView').exists()); From c6e982c2066e1abb12ca2a56dc8a2c3662d6d1df Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 21 Aug 2018 09:55:20 -0400 Subject: [PATCH 0128/4053] Revamp FilePatchSelection to understand line selections by buffer row --- lib/models/file-patch-selection.js | 91 +- test/models/file-patch-selection.test.js | 778 ++++++++++++++ .../file-patch-selection.test.pending.js | 965 ------------------ 3 files changed, 844 insertions(+), 990 deletions(-) create mode 100644 test/models/file-patch-selection.test.js delete mode 100644 test/models/file-patch-selection.test.pending.js diff --git a/lib/models/file-patch-selection.js b/lib/models/file-patch-selection.js index 091bd27fd8..d84110b860 100644 --- a/lib/models/file-patch-selection.js +++ b/lib/models/file-patch-selection.js @@ -9,19 +9,23 @@ export default class FilePatchSelection { this.mode = 'hunk'; this.hunksByLine = new Map(); + this.changedLines = new Set(); + const lines = []; for (const hunk of hunks) { - for (const line of hunk.lines) { - lines.push(line); - this.hunksByLine.set(line, hunk); + for (const region of hunk.getRegions()) { + for (const line of region.getBufferRows()) { + lines.push(line); + this.hunksByLine.set(line, hunk); + if (region.isChange()) { + this.changedLines.add(line); + } + } } } this.hunksSelection = new ListSelection({items: hunks}); - this.linesSelection = new ListSelection({ - items: lines, - isItemSelectable: line => line.isChanged(), - }); + this.linesSelection = new ListSelection({items: lines, isItemSelectable: line => this.changedLines.has(line)}); this.resolveNextUpdatePromise = () => {}; } else { // Copy from options. *Only* reachable from the copy() method because no other module has visibility to @@ -33,6 +37,7 @@ export default class FilePatchSelection { this.linesSelection = options.linesSelection; this.resolveNextUpdatePromise = options.resolveNextUpdatePromise; this.hunksByLine = options.hunksByLine; + this.changedLines = options.changedLines; } } @@ -42,6 +47,7 @@ export default class FilePatchSelection { let linesSelection = options.linesSelection || this.linesSelection; let hunksByLine = null; + let changedLines = null; if (options.hunks) { // Update hunks const oldHunks = this.hunksSelection.getItems(); @@ -66,10 +72,16 @@ export default class FilePatchSelection { const newLines = []; hunksByLine = new Map(); + changedLines = new Set(); for (const hunk of newHunks) { - for (const line of hunk.lines) { - newLines.push(line); - hunksByLine.set(line, hunk); + for (const region of hunk.getRegions()) { + for (const line of region.getBufferRows()) { + newLines.push(line); + hunksByLine.set(line, hunk); + if (region.isChange()) { + changedLines.add(line); + } + } } } @@ -79,14 +91,18 @@ export default class FilePatchSelection { const oldSelectionStartIndex = this.linesSelection.getMostRecentSelectionStartIndex(); let changedLineCount = 0; for (let i = 0; i < oldSelectionStartIndex; i++) { - if (oldLines[i].isChanged()) { changedLineCount++; } + if (this.changedLines.has(oldLines[i])) { + changedLineCount++; + } } for (let i = 0; i < newLines.length; i++) { const line = newLines[i]; - if (line.isChanged()) { + if (changedLines.has(line)) { newSelectedLine = line; - if (changedLineCount === 0) { break; } + if (changedLineCount === 0) { + break; + } changedLineCount--; } } @@ -100,8 +116,9 @@ export default class FilePatchSelection { this.resolveNextUpdatePromise(); } } else { - // Hunks are unchanged. Don't recompute hunksByLine. + // Hunks are unchanged. Don't recompute hunksByLine or changedLines. hunksByLine = this.hunksByLine; + changedLines = this.changedLines; } return new FilePatchSelection({ @@ -110,15 +127,16 @@ export default class FilePatchSelection { hunksSelection, linesSelection, hunksByLine, + changedLines, resolveNextUpdatePromise: options.resolveNextUpdatePromise || this.resolveNextUpdatePromise, }); } toggleMode() { if (this.mode === 'hunk') { - const firstLineOfSelectedHunk = this.getHeadHunk().lines[0]; + const firstLineOfSelectedHunk = this.getHeadHunk().getBufferRange().start.row; const selection = this.selectLine(firstLineOfSelectedHunk); - if (!firstLineOfSelectedHunk.isChanged()) { + if (!this.changedLines.has(firstLineOfSelectedHunk)) { return selection.selectNextLine(); } else { return selection; @@ -236,8 +254,9 @@ export default class FilePatchSelection { getSelectedHunks() { if (this.mode === 'line') { const selectedHunks = new Set(); - const selectedLines = this.getSelectedLines(); - selectedLines.forEach(line => selectedHunks.add(this.hunksByLine.get(line))); + for (const line of this.getSelectedLines()) { + selectedHunks.add(this.hunksByLine.get(line)); + } return selectedHunks; } else { return this.hunksSelection.getSelectedItems(); @@ -304,11 +323,13 @@ export default class FilePatchSelection { getSelectedLines() { if (this.mode === 'hunk') { const selectedLines = new Set(); - this.getSelectedHunks().forEach(hunk => { - for (const line of hunk.lines) { - if (line.isChanged()) { selectedLines.add(line); } + for (const hunk of this.getSelectedHunks()) { + for (const change of hunk.getChanges()) { + for (const line of change.getBufferRows()) { + selectedLines.add(line); + } } - }); + } return selectedLines; } else { return this.linesSelection.getSelectedItems(); @@ -341,6 +362,7 @@ export default class FilePatchSelection { } goToDiffLine(lineNumber) { + // console.log(`<<< finding closest line to ${lineNumber}`); const lines = this.linesSelection.getItems(); let closestLine; @@ -348,20 +370,39 @@ export default class FilePatchSelection { for (let i = 0; i < lines.length; i++) { const line = lines[i]; - if (!this.linesSelection.isItemSelectable(line)) { continue; } - if (line.newLineNumber === lineNumber) { + // console.log(`considering line = ${line}`); + if (!this.linesSelection.isItemSelectable(line)) { + // console.log('... not selectable'); + continue; + } + + const hunk = this.hunksByLine.get(line); + const newLineNumber = hunk.getNewRowAt(line); + if (newLineNumber === null) { + // console.log('... deleted line'); + continue; + } + + // console.log(` new line number = ${newLineNumber}`); + + if (newLineNumber === lineNumber) { + // console.log('>>> exact match'); return this.selectLine(line); } else { - const newDistance = Math.abs(line.newLineNumber - lineNumber); + const newDistance = Math.abs(newLineNumber - lineNumber); + // console.log(` distance = ${newDistance} vs. closest = ${closestLineDistance}`); if (newDistance < closestLineDistance) { closestLineDistance = newDistance; closestLine = line; + // console.log(` new closest line = ${closestLine}`); } else { + // console.log(`>>> increasing distance. choosing previous closest line = ${closestLine}`); return this.selectLine(closestLine); } } } + // console.log(`>>> choosing closest line = ${closestLine}`); return this.selectLine(closestLine); } } diff --git a/test/models/file-patch-selection.test.js b/test/models/file-patch-selection.test.js new file mode 100644 index 0000000000..cb0c6679d2 --- /dev/null +++ b/test/models/file-patch-selection.test.js @@ -0,0 +1,778 @@ +import FilePatchSelection from '../../lib/models/file-patch-selection'; +import buildFilePatch from '../../lib/models/patch/builder'; +import IndexedRowRange from '../../lib/models/indexed-row-range'; +import Hunk from '../../lib/models/patch/hunk'; +import {Addition, Deletion} from '../../lib/models/patch/region'; + +describe('FilePatchSelection', function() { + describe('line selection', function() { + it('starts a new line selection with selectLine and updates an existing selection when preserveTail is true', function() { + const patch = buildPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()); + + const selection1 = selection0.selectLine(2); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [2]); + + const selection2 = selection1.selectLine(7, true); + assert.sameMembers(Array.from(selection2.getSelectedLines()), [2, 3, 6, 7]); + + const selection3 = selection2.selectLine(6, true); + assert.sameMembers(Array.from(selection3.getSelectedLines()), [2, 3, 6]); + + const selection4 = selection3.selectLine(1, true); + assert.sameMembers(Array.from(selection4.getSelectedLines()), [1, 2]); + + const selection5 = selection4.selectLine(7); + assert.sameMembers(Array.from(selection5.getSelectedLines()), [7]); + }); + + it('adds a new line selection when calling addOrSubtractLineSelection with an unselected line and always updates the head of the most recent line selection', function() { + const patch = buildPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()) + .selectLine(2) + .selectLine(3, true) + .addOrSubtractLineSelection(7) + .selectLine(8, true); + + assert.sameMembers(Array.from(selection0.getSelectedLines()), [2, 3, 7, 8]); + + const selection1 = selection0.selectLine(1, true); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [1, 2, 3, 6, 7]); + }); + + it('subtracts from existing selections when calling addOrSubtractLineSelection with a selected line', function() { + const patch = buildPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()) + .selectLine(2) + .selectLine(7, true); + + assert.sameMembers(Array.from(selection0.getSelectedLines()), [2, 3, 6, 7]); + + const selection1 = selection0.addOrSubtractLineSelection(6); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [2, 3, 7]); + + const selection2 = selection1.selectLine(8, true); + assert.sameMembers(Array.from(selection2.getSelectedLines()), [2, 3]); + + const selection3 = selection2.selectLine(2, true); + assert.sameMembers(Array.from(selection3.getSelectedLines()), [7]); + }); + + it('allows the next or previous line to be selected', function() { + const patch = buildPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()) + .selectLine(1) + .selectNextLine(); + assert.sameMembers(Array.from(selection0.getSelectedLines()), [2]); + + const selection1 = selection0.selectNextLine(); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [3]); + + const selection2 = selection1.selectNextLine(); + assert.sameMembers(Array.from(selection2.getSelectedLines()), [6]); + + const selection3 = selection2.selectPreviousLine(); + assert.sameMembers(Array.from(selection3.getSelectedLines()), [3]); + + const selection4 = selection3.selectPreviousLine(); + assert.sameMembers(Array.from(selection4.getSelectedLines()), [2]); + + const selection5 = selection4.selectPreviousLine(); + assert.sameMembers(Array.from(selection5.getSelectedLines()), [1]); + + const selection6 = selection5.selectNextLine(true); + assert.sameMembers(Array.from(selection6.getSelectedLines()), [1, 2]); + + const selection7 = selection6.selectNextLine().selectNextLine().selectPreviousLine(true); + assert.sameMembers(Array.from(selection7.getSelectedLines()), [3, 6]); + }); + + it('allows the first/last changed line to be selected', function() { + const patch = buildPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()).selectLastLine(); + assert.sameMembers(Array.from(selection0.getSelectedLines()), [8]); + + const selection1 = selection0.selectFirstLine(); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [1]); + + const selection2 = selection1.selectLastLine(true); + assert.sameMembers(Array.from(selection2.getSelectedLines()), [1, 2, 3, 6, 7, 8]); + + const selection3 = selection2.selectLastLine(); + assert.sameMembers(Array.from(selection3.getSelectedLines()), [8]); + + const selection4 = selection3.selectFirstLine(true); + assert.sameMembers(Array.from(selection4.getSelectedLines()), [1, 2, 3, 6, 7, 8]); + + const selection5 = selection4.selectFirstLine(); + assert.sameMembers(Array.from(selection5.getSelectedLines()), [1]); + }); + + it('allows all lines to be selected', function() { + const patch = buildPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()).selectAllLines(); + assert.sameMembers(Array.from(selection0.getSelectedLines()), [1, 2, 3, 6, 7, 8]); + }); + + it('defaults to the first/last changed line when selecting next / previous with no current selection', function() { + const patch = buildPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()) + .selectLine(1) + .addOrSubtractLineSelection(1) + .coalesce(); + assert.sameMembers(Array.from(selection0.getSelectedLines()), []); + + const selection1 = selection0.selectNextLine(); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [1]); + + const selection2 = selection1.addOrSubtractLineSelection(1).coalesce(); + assert.sameMembers(Array.from(selection2.getSelectedLines()), []); + + const selection3 = selection2.selectPreviousLine(); + assert.sameMembers(Array.from(selection3.getSelectedLines()), [8]); + }); + + it('collapses multiple selections down to one line when selecting next or previous', function() { + const patch = buildPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()) + .selectLine(1) + .addOrSubtractLineSelection(2) + .selectNextLine(true); + assert.sameMembers(Array.from(selection0.getSelectedLines()), [1, 2, 3]); + + const selection1 = selection0.selectNextLine(); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [6]); + + const selection2 = selection1.selectLine(1) + .addOrSubtractLineSelection(2) + .selectPreviousLine(true); + assert.sameMembers(Array.from(selection2.getSelectedLines()), [1, 2]); + + const selection3 = selection2.selectPreviousLine(); + assert.sameMembers(Array.from(selection3.getSelectedLines()), [1]); + }); + + describe('coalescing', function() { + it('merges overlapping selections', function() { + const patch = buildAllAddedPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()) + .selectLine(3) + .selectLine(5, true) + .addOrSubtractLineSelection(0) + .selectLine(4, true) + .coalesce() + .selectPreviousLine(true); + assert.sameMembers(Array.from(selection0.getSelectedLines()), [0, 1, 2, 3, 4]); + + const selection1 = selection0.addOrSubtractLineSelection(7) + .selectLine(3, true) + .coalesce() + .selectNextLine(true); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [1, 2, 3, 4, 5, 6, 7]); + }); + + it('merges adjacent selections', function() { + const patch = buildAllAddedPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()) + .selectLine(3) + .selectLine(5, true) + .addOrSubtractLineSelection(1) + .selectLine(2, true) + .coalesce() + .selectPreviousLine(true); + assert.sameMembers(Array.from(selection0.getSelectedLines()), [1, 2, 3, 4]); + + const selection1 = selection0.addOrSubtractLineSelection(6) + .selectLine(5, true) + .coalesce() + .selectNextLine(true); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [2, 3, 4, 5, 6]); + }); + + it('expands selections to contain all adjacent context lines', function() { + const patch = buildPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()) + .selectLine(7) + .selectLine(6, true) + .addOrSubtractLineSelection(2) + .selectLine(1, true) + .coalesce() + .selectNext(true); + + assert.sameMembers(Array.from(selection0.getSelectedLines()), [2, 6, 7]); + }); + + it('truncates or splits selections where they overlap a negative selection', function() { + const patch = buildAllAddedPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()) + .selectLine(0) + .selectLine(7, true) + .addOrSubtractLineSelection(3) + .selectLine(4, true) + .coalesce() + .selectPrevious(true); + assert.sameMembers(Array.from(selection0.getSelectedLines()), [0, 1, 2, 5, 6]); + }); + + it('does not blow up when coalescing with no selections', function() { + const patch = buildAllAddedPatchFixture(); + + const selection0 = new FilePatchSelection(patch.getHunks()) + .selectLine(0) + .addOrSubtractLineSelection(0); + assert.lengthOf(Array.from(selection0.getSelectedLines()), 0); + + const selection1 = selection0.coalesce(); + assert.lengthOf(Array.from(selection1.getSelectedLines()), 0); + }); + }); + }); + + describe('hunk selection', function() { + it('selects the first hunk by default', function() { + const hunks = buildPatchFixture().getHunks(); + const selection0 = new FilePatchSelection(hunks); + assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[0]]); + }); + + it('starts a new hunk selection with selectHunk and updates an existing selection when preserveTail is true', function() { + const hunks = buildFourHunksPatchFixture().getHunks(); + const selection0 = new FilePatchSelection(hunks) + .selectHunk(hunks[1]); + assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[1]]); + + const selection1 = selection0.selectHunk(hunks[3], true); + assert.sameMembers(Array.from(selection1.getSelectedHunks()), [hunks[1], hunks[2], hunks[3]]); + + const selection2 = selection1.selectHunk(hunks[0], true); + assert.sameMembers(Array.from(selection2.getSelectedHunks()), [hunks[0], hunks[1]]); + }); + + it('adds a new hunk selection with addOrSubtractHunkSelection and always updates the head of the most recent hunk selection', function() { + const hunks = buildFourHunksPatchFixture().getHunks(); + const selection0 = new FilePatchSelection(hunks) + .addOrSubtractHunkSelection(hunks[2]); + assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[0], hunks[2]]); + + const selection1 = selection0.selectHunk(hunks[3], true); + assert.sameMembers(Array.from(selection1.getSelectedHunks()), [hunks[0], hunks[2], hunks[3]]); + + const selection2 = selection1.selectHunk(hunks[1], true); + assert.sameMembers(Array.from(selection2.getSelectedHunks()), [hunks[0], hunks[1], hunks[2]]); + }); + + it('allows the next or previous hunk to be selected', function() { + const hunks = buildFourHunksPatchFixture().getHunks(); + const selection0 = new FilePatchSelection(hunks) + .selectNextHunk(); + assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[1]]); + + const selection1 = selection0.selectNextHunk(); + assert.sameMembers(Array.from(selection1.getSelectedHunks()), [hunks[2]]); + + const selection2 = selection1.selectNextHunk() + .selectNextHunk(); + assert.sameMembers(Array.from(selection2.getSelectedHunks()), [hunks[3]]); + + const selection3 = selection2.selectPreviousHunk(); + assert.sameMembers(Array.from(selection3.getSelectedHunks()), [hunks[2]]); + + const selection4 = selection3.selectPreviousHunk(); + assert.sameMembers(Array.from(selection4.getSelectedHunks()), [hunks[1]]); + + const selection5 = selection4.selectPreviousHunk() + .selectPreviousHunk(); + assert.sameMembers(Array.from(selection5.getSelectedHunks()), [hunks[0]]); + + const selection6 = selection5.selectNextHunk() + .selectNextHunk(true); + assert.sameMembers(Array.from(selection6.getSelectedHunks()), [hunks[1], hunks[2]]); + + const selection7 = selection6.selectPreviousHunk(true); + assert.sameMembers(Array.from(selection7.getSelectedHunks()), [hunks[1]]); + + const selection8 = selection7.selectPreviousHunk(true); + assert.sameMembers(Array.from(selection8.getSelectedHunks()), [hunks[0], hunks[1]]); + }); + + it('allows all hunks to be selected', function() { + const hunks = buildFourHunksPatchFixture().getHunks(); + + const selection0 = new FilePatchSelection(hunks) + .selectAllHunks(); + assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[0], hunks[1], hunks[2], hunks[3]]); + }); + }); + + describe('selection modes', function() { + it('allows the selection mode to be toggled between hunks and lines', function() { + const hunks = buildPatchFixture().getHunks(); + const selection0 = new FilePatchSelection(hunks); + + assert.strictEqual(selection0.getMode(), 'hunk'); + assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[0]]); + assert.sameMembers(Array.from(selection0.getSelectedLines()), [1, 2, 3]); + + const selection1 = selection0.selectNext(); + assert.strictEqual(selection1.getMode(), 'hunk'); + assert.sameMembers(Array.from(selection1.getSelectedHunks()), [hunks[1]]); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [6, 7, 8]); + + const selection2 = selection1.toggleMode(); + assert.strictEqual(selection2.getMode(), 'line'); + assert.sameMembers(Array.from(selection2.getSelectedHunks()), [hunks[1]]); + assert.sameMembers(Array.from(selection2.getSelectedLines()), [6]); + + const selection3 = selection2.selectNext(); + assert.sameMembers(Array.from(selection3.getSelectedHunks()), [hunks[1]]); + assert.sameMembers(Array.from(selection3.getSelectedLines()), [7]); + + const selection4 = selection3.toggleMode(); + assert.strictEqual(selection4.getMode(), 'hunk'); + assert.sameMembers(Array.from(selection4.getSelectedHunks()), [hunks[1]]); + assert.sameMembers(Array.from(selection4.getSelectedLines()), [6, 7, 8]); + + const selection5 = selection4.selectLine(1); + assert.strictEqual(selection5.getMode(), 'line'); + assert.sameMembers(Array.from(selection5.getSelectedHunks()), [hunks[0]]); + assert.sameMembers(Array.from(selection5.getSelectedLines()), [1]); + + const selection6 = selection5.selectHunk(hunks[1]); + assert.strictEqual(selection6.getMode(), 'hunk'); + assert.sameMembers(Array.from(selection6.getSelectedHunks()), [hunks[1]]); + assert.sameMembers(Array.from(selection6.getSelectedLines()), [6, 7, 8]); + }); + }); + + describe('updateHunks(hunks)', function() { + it('collapses the line selection to a single line following the previous selected range with the highest start index', function() { + const oldHunks = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 3, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 0})), + ], + }), + new Hunk({ + oldStartRow: 5, oldRowCount: 7, newStartRow: 5, newRowCount: 4, + rowRange: new IndexedRowRange({bufferRange: [[3, 0], [10, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [5, 0]], startOffset: 0, endOffset: 0})), + new Addition(new IndexedRowRange({bufferRange: [[6, 0], [8, 0]], startOffset: 0, endOffset: 0})), + new Deletion(new IndexedRowRange({bufferRange: [[9, 0], [10, 0]], startOffset: 0, endOffset: 0})), + ], + }), + ]; + + const selection0 = new FilePatchSelection(oldHunks) + .selectLine(5) + .selectLine(7, true); + + const newHunks = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 3, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 0})), + ], + }), + new Hunk({ + oldStartRow: 5, oldRowCount: 7, newStartRow: 3, newRowCount: 2, + rowRange: new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 0, endOffset: 0})), + ], + }), + new Hunk({ + oldStartRow: 9, oldRowCount: 10, newStartRow: 3, newRowCount: 2, + rowRange: new IndexedRowRange({bufferRange: [[6, 0], [9, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[7, 0], [7, 0]], startOffset: 0, endOffset: 0})), + new Deletion(new IndexedRowRange({bufferRange: [[8, 0], [9, 0]], startOffset: 0, endOffset: 0})), + ], + }), + ]; + const selection1 = selection0.updateHunks(newHunks); + + assert.sameMembers(Array.from(selection1.getSelectedLines()), [7]); + }); + + it('collapses the line selection to the line preceding the previous selected line if it was the *last* line', function() { + const oldHunks = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 4, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [3, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 0})), + ], + }), + ]; + + const selection0 = new FilePatchSelection(oldHunks) + .selectLine(2); + + const newHunks = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 4, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [3, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 0})), + ], + }), + ]; + const selection1 = selection0.updateHunks(newHunks); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [1]); + }); + + it('updates the hunk selection if it exceeds the new length of the hunks list', function() { + const oldHunks = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 1, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0})), + ], + }), + new Hunk({ + oldStartRow: 5, oldRowCount: 0, newStartRow: 6, newRowCount: 1, + rowRange: new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 0, endOffset: 0})), + ], + }), + ]; + const selection0 = new FilePatchSelection(oldHunks) + .selectHunk(oldHunks[1]); + + const newHunks = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 1, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0})), + ], + }), + ]; + const selection1 = selection0.updateHunks(newHunks); + assert.sameMembers(Array.from(selection1.getSelectedHunks()), [newHunks[0]]); + }); + + it('deselects if updating with an empty hunk array', function() { + const oldHunks = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 3, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 0})), + ], + }), + ]; + + const selection0 = new FilePatchSelection(oldHunks) + .selectLine(1) + .updateHunks([]); + assert.lengthOf(Array.from(selection0.getSelectedLines()), []); + }); + + it('resolves the getNextUpdatePromise the next time hunks are changed', async function() { + const hunk0 = new Hunk({ + oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 1, + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0})), + ], + }); + const hunk1 = new Hunk({ + oldStartRow: 5, oldRowCount: 0, newStartRow: 6, newRowCount: 1, + rowRange: new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 0, endOffset: 0})), + ], + }); + const existingHunks = [hunk0, hunk1]; + const selection0 = new FilePatchSelection(existingHunks); + + let wasResolved = false; + const promise = selection0.getNextUpdatePromise().then(() => { wasResolved = true; }); + + const unchangedHunks = [hunk0, hunk1]; + const selection1 = selection0.updateHunks(unchangedHunks); + + assert.isFalse(wasResolved); + + const hunk2 = new Hunk({ + oldStartRow: 6, oldRowCount: 1, newStartRow: 4, newRowCount: 3, + rowRange: new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 0, endOffset: 0}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 0, endOffset: 0})), + ], + }); + const changedHunks = [hunk0, hunk2]; + selection1.updateHunks(changedHunks); + + await promise; + assert.isTrue(wasResolved); + }); + }); + + describe('jumpToNextHunk() and jumpToPreviousHunk()', function() { + it('selects the next/previous hunk', function() { + const hunks = buildThreeHunkPatchFixture().getHunks(); + const selection0 = new FilePatchSelection(hunks); + + // in hunk mode, selects the entire next/previous hunk + assert.strictEqual(selection0.getMode(), 'hunk'); + assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[0]]); + + const selection1 = selection0.jumpToNextHunk(); + assert.sameMembers(Array.from(selection1.getSelectedHunks()), [hunks[1]]); + + const selection2 = selection1.jumpToNextHunk(); + assert.sameMembers(Array.from(selection2.getSelectedHunks()), [hunks[2]]); + + const selection3 = selection2.jumpToNextHunk(); + assert.sameMembers(Array.from(selection3.getSelectedHunks()), [hunks[2]]); + + const selection4 = selection3.jumpToPreviousHunk(); + assert.sameMembers(Array.from(selection4.getSelectedHunks()), [hunks[1]]); + + const selection5 = selection4.jumpToPreviousHunk(); + assert.sameMembers(Array.from(selection5.getSelectedHunks()), [hunks[0]]); + + const selection6 = selection5.jumpToPreviousHunk(); + assert.sameMembers(Array.from(selection6.getSelectedHunks()), [hunks[0]]); + + // in line selection mode, the first changed line of the next/previous hunk is selected + const selection7 = selection6.toggleMode(); + assert.strictEqual(selection7.getMode(), 'line'); + assert.sameMembers(Array.from(selection7.getSelectedLines()), [1]); + + const selection8 = selection7.jumpToNextHunk(); + assert.sameMembers(Array.from(selection8.getSelectedLines()), [6]); + + const selection9 = selection8.jumpToNextHunk(); + assert.sameMembers(Array.from(selection9.getSelectedLines()), [12]); + + const selection10 = selection9.jumpToNextHunk(); + assert.sameMembers(Array.from(selection10.getSelectedLines()), [12]); + + const selection11 = selection10.jumpToPreviousHunk(); + assert.sameMembers(Array.from(selection11.getSelectedLines()), [6]); + + const selection12 = selection11.jumpToPreviousHunk(); + assert.sameMembers(Array.from(selection12.getSelectedLines()), [1]); + + const selection13 = selection12.jumpToPreviousHunk(); + assert.sameMembers(Array.from(selection13.getSelectedLines()), [1]); + }); + }); + + describe('goToDiffLine(lineNumber)', function() { + it('selects the closest selectable hunk line', function() { + const hunks = buildPatchFixture().getHunks(); + + const selection0 = new FilePatchSelection(hunks); + const selection1 = selection0.goToDiffLine(11); + assert.sameMembers(Array.from(selection1.getSelectedLines()), [1]); + + const selection2 = selection1.goToDiffLine(26); + assert.sameMembers(Array.from(selection2.getSelectedLines()), [7]); + + // selects closest added hunk line + const selection3 = selection2.goToDiffLine(27); + assert.sameMembers(Array.from(selection3.getSelectedLines()), [7]); + + const selection4 = selection3.goToDiffLine(18); + assert.sameMembers(Array.from(selection4.getSelectedLines()), [1]); + + const selection5 = selection4.goToDiffLine(19); + assert.sameMembers(Array.from(selection5.getSelectedLines()), [6]); + }); + }); +}); + +function buildPatchFixture() { + return buildFilePatch([{ + oldPath: 'a.txt', + oldMode: '100644', + newPath: 'a.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 10, + oldLineCount: 4, + newStartLine: 10, + newLineCount: 3, + lines: [ + ' 0000', + '+0001', + '-0002', + '-0003', + ' 0004', + ], + }, + { + oldStartLine: 25, + oldLineCount: 3, + newStartLine: 24, + newLineCount: 4, + lines: [ + ' 0005', + '+0006', + '+0007', + '-0008', + ' 0009', + ], + }, + ], + }]); +} + +function buildThreeHunkPatchFixture() { + return buildFilePatch([{ + oldPath: 'a.txt', + oldMode: '100644', + newPath: 'a.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 10, + oldLineCount: 4, + newStartLine: 10, + newLineCount: 3, + lines: [ + ' 0000', + '+0001', + '-0002', + '-0003', + ' 0004', + ], + }, + { + oldStartLine: 25, + oldLineCount: 3, + newStartLine: 24, + newLineCount: 4, + lines: [ + ' 0005', + '+0006', + '+0007', + '-0008', + ' 0009', + ], + }, + { + oldStartLine: 40, + oldLineCount: 5, + newStartLine: 44, + newLineCount: 3, + lines: [ + ' 0010', + ' 0011', + '-0012', + '-0013', + ' 0014', + ], + }, + ], + }]); +} + +function buildAllAddedPatchFixture() { + return buildFilePatch([{ + oldPath: 'a.txt', + oldMode: '100644', + newPath: 'a.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 1, + oldLineCount: 0, + newStartLine: 1, + newLineCount: 4, + lines: [ + '+0000', + '+0001', + '+0002', + '+0003', + ], + }, + { + oldStartLine: 1, + oldLineCount: 0, + newStartLine: 5, + newLineCount: 4, + lines: [ + '+0004', + '+0005', + '+0006', + '+0007', + ], + }, + ], + }]); +} + +function buildFourHunksPatchFixture() { + return buildFilePatch([{ + oldPath: 'a.txt', + oldMode: '100644', + newPath: 'a.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 1, + oldLineCount: 0, + newStartLine: 1, + newLineCount: 1, + lines: [ + '+0000', + ], + }, + { + oldStartLine: 2, + oldLineCount: 0, + newStartLine: 2, + newLineCount: 1, + lines: [ + '+0001', + ], + }, + { + oldStartLine: 3, + oldLineCount: 0, + newStartLine: 3, + newLineCount: 1, + lines: [ + '+0002', + ], + }, + { + oldStartLine: 4, + oldLineCount: 0, + newStartLine: 4, + newLineCount: 1, + lines: [ + '+0004', + ], + }, + ], + }]); +} diff --git a/test/models/file-patch-selection.test.pending.js b/test/models/file-patch-selection.test.pending.js deleted file mode 100644 index 6ea83f436f..0000000000 --- a/test/models/file-patch-selection.test.pending.js +++ /dev/null @@ -1,965 +0,0 @@ -import FilePatchSelection from '../../lib/models/file-patch-selection'; -import Hunk from '../../lib/models/hunk'; -import HunkLine from '../../lib/models/hunk-line'; -import {assertEqualSets} from '../helpers'; - -describe('FilePatchSelection', function() { - describe('line selection', function() { - it('starts a new line selection with selectLine and updates an existing selection when preserveTail is true', function() { - const hunks = [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 8), - new HunkLine('line-8', 'added', -1, 9), - new HunkLine('line-9', 'added', -1, 10), - new HunkLine('line-10', 'deleted', 8, -1), - new HunkLine('line-11', 'deleted', 9, -1), - ]), - ]; - const selection0 = new FilePatchSelection(hunks); - - const selection1 = selection0.selectLine(hunks[0].lines[1]); - assertEqualSets(selection1.getSelectedLines(), new Set([ - hunks[0].lines[1], - ])); - - const selection2 = selection1.selectLine(hunks[1].lines[2], true); - assertEqualSets(selection2.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[1].lines[1], - hunks[1].lines[2], - ])); - - const selection3 = selection2.selectLine(hunks[1].lines[1], true); - assertEqualSets(selection3.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[1].lines[1], - ])); - - const selection4 = selection3.selectLine(hunks[0].lines[0], true); - assertEqualSets(selection4.getSelectedLines(), new Set([ - hunks[0].lines[0], - hunks[0].lines[1], - ])); - - const selection5 = selection4.selectLine(hunks[1].lines[2]); - assertEqualSets(selection5.getSelectedLines(), new Set([ - hunks[1].lines[2], - ])); - }); - - it('adds a new line selection when calling addOrSubtractLineSelection with an unselected line and always updates the head of the most recent line selection', function() { - const hunks = [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 8), - new HunkLine('line-8', 'added', -1, 9), - new HunkLine('line-9', 'added', -1, 10), - new HunkLine('line-10', 'deleted', 8, -1), - new HunkLine('line-11', 'deleted', 9, -1), - ]), - ]; - const selection0 = new FilePatchSelection(hunks) - .selectLine(hunks[0].lines[1]) - .selectLine(hunks[1].lines[1], true) - .addOrSubtractLineSelection(hunks[1].lines[3]) - .selectLine(hunks[1].lines[4], true); - - assertEqualSets(selection0.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[1].lines[1], - hunks[1].lines[3], - hunks[1].lines[4], - ])); - - const selection1 = selection0.selectLine(hunks[0].lines[0], true); - assertEqualSets(selection1.getSelectedLines(), new Set([ - hunks[0].lines[0], - hunks[0].lines[1], - hunks[1].lines[1], - hunks[1].lines[2], - hunks[1].lines[3], - ])); - }); - - it('subtracts from existing selections when calling addOrSubtractLineSelection with a selected line', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'unchanged', 2, 4), - ]), - new Hunk(5, 7, 1, 4, '', [ - new HunkLine('line-5', 'unchanged', 5, 7), - new HunkLine('line-6', 'added', -1, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'added', -1, 10), - ]), - ]; - const selection0 = new FilePatchSelection(hunks) - .selectLine(hunks[0].lines[2]) - .selectLine(hunks[1].lines[2], true); - assertEqualSets(selection0.getSelectedLines(), new Set([ - hunks[0].lines[2], - hunks[1].lines[1], - hunks[1].lines[2], - ])); - - const selection1 = selection0.addOrSubtractLineSelection(hunks[1].lines[1]); - assertEqualSets(selection1.getSelectedLines(), new Set([ - hunks[0].lines[2], - hunks[1].lines[2], - ])); - - const selection2 = selection1.selectLine(hunks[1].lines[3], true); - assertEqualSets(selection2.getSelectedLines(), new Set([ - hunks[0].lines[2], - ])); - - const selection3 = selection2.selectLine(hunks[0].lines[1], true); - assertEqualSets(selection3.getSelectedLines(), new Set([ - hunks[1].lines[2], - ])); - }); - - it('allows the next or previous line to be selected', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'unchanged', 2, 4), - ]), - new Hunk(5, 7, 3, 4, '', [ - new HunkLine('line-5', 'unchanged', 5, 7), - new HunkLine('line-6', 'unchanged', 6, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'unchanged', 7, 10), - ]), - ]; - const selection0 = new FilePatchSelection(hunks) - .selectLine(hunks[0].lines[1]) - .selectNextLine(); - assertEqualSets(selection0.getSelectedLines(), new Set([ - hunks[0].lines[2], - ])); - - const selection1 = selection0.selectNextLine(); - assertEqualSets(selection1.getSelectedLines(), new Set([ - hunks[1].lines[2], - ])); - - const selection2 = selection1.selectNextLine(); - assertEqualSets(selection2.getSelectedLines(), new Set([ - hunks[1].lines[2], - ])); - - const selection3 = selection2.selectPreviousLine(); - assertEqualSets(selection3.getSelectedLines(), new Set([ - hunks[0].lines[2], - ])); - - const selection4 = selection3.selectPreviousLine(); - assertEqualSets(selection4.getSelectedLines(), new Set([ - hunks[0].lines[1], - ])); - - const selection5 = selection4.selectPreviousLine(); - assertEqualSets(selection5.getSelectedLines(), new Set([ - hunks[0].lines[1], - ])); - - const selection6 = selection5.selectNextLine(true); - assertEqualSets(selection6.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[0].lines[2], - ])); - - const selection7 = selection6.selectNextLine().selectPreviousLine(true); - assertEqualSets(selection7.getSelectedLines(), new Set([ - hunks[0].lines[2], - hunks[1].lines[2], - ])); - }); - - it('allows the first/last changed line to be selected', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'unchanged', 2, 4), - ]), - new Hunk(5, 7, 3, 4, '', [ - new HunkLine('line-5', 'unchanged', 5, 7), - new HunkLine('line-6', 'unchanged', 6, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'unchanged', 7, 10), - ]), - ]; - - const selection0 = new FilePatchSelection(hunks).selectLastLine(); - assertEqualSets(selection0.getSelectedLines(), new Set([ - hunks[1].lines[2], - ])); - - const selection1 = selection0.selectFirstLine(); - assertEqualSets(selection1.getSelectedLines(), new Set([ - hunks[0].lines[1], - ])); - - const selection2 = selection1.selectLastLine(true); - assertEqualSets(selection2.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[0].lines[2], - hunks[1].lines[2], - ])); - - const selection3 = selection2.selectLastLine(); - assertEqualSets(selection3.getSelectedLines(), new Set([ - hunks[1].lines[2], - ])); - - const selection4 = selection3.selectFirstLine(true); - assertEqualSets(selection4.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[0].lines[2], - hunks[1].lines[2], - ])); - - const selection5 = selection4.selectFirstLine(); - assertEqualSets(selection5.getSelectedLines(), new Set([ - hunks[0].lines[1], - ])); - }); - - it('allows all lines to be selected', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'unchanged', 2, 4), - ]), - new Hunk(5, 7, 3, 4, '', [ - new HunkLine('line-5', 'unchanged', 5, 7), - new HunkLine('line-6', 'unchanged', 6, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'unchanged', 7, 10), - ]), - ]; - - const selection0 = new FilePatchSelection(hunks).selectAllLines(); - assertEqualSets(selection0.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[0].lines[2], - hunks[1].lines[2], - ])); - }); - - it('defaults to the first/last changed line when selecting next / previous with no current selection', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'unchanged', 2, 4), - ]), - ]; - - const selection0 = new FilePatchSelection(hunks) - .selectLine(hunks[0].lines[1]) - .addOrSubtractLineSelection(hunks[0].lines[1]) - .coalesce(); - assertEqualSets(selection0.getSelectedLines(), new Set()); - - const selection1 = selection0.selectNextLine(); - assertEqualSets(selection1.getSelectedLines(), new Set([hunks[0].lines[1]])); - - const selection2 = selection1.addOrSubtractLineSelection(hunks[0].lines[1]).coalesce(); - assertEqualSets(selection2.getSelectedLines(), new Set()); - - const selection3 = selection2.selectPreviousLine(); - assertEqualSets(selection3.getSelectedLines(), new Set([hunks[0].lines[2]])); - }); - - it('collapses multiple selections down to one line when selecting next or previous', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'unchanged', 2, 4), - ]), - new Hunk(5, 7, 3, 4, '', [ - new HunkLine('line-5', 'unchanged', 5, 7), - new HunkLine('line-6', 'unchanged', 6, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'unchanged', 7, 10), - ]), - ]; - - const selection0 = new FilePatchSelection(hunks) - .selectLine(hunks[0].lines[1]) - .addOrSubtractLineSelection(hunks[0].lines[2]) - .selectNextLine(true); - assertEqualSets(selection0.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[0].lines[2], - hunks[1].lines[2], - ])); - - const selection1 = selection0.selectNextLine(); - assertEqualSets(selection1.getSelectedLines(), new Set([ - hunks[1].lines[2], - ])); - - const selection2 = selection1.selectLine(hunks[0].lines[1]) - .addOrSubtractLineSelection(hunks[0].lines[2]) - .selectPreviousLine(true); - assertEqualSets(selection2.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[0].lines[2], - ])); - - const selection3 = selection2.selectPreviousLine(); - assertEqualSets(selection3.getSelectedLines(), new Set([ - hunks[0].lines[1], - ])); - }); - - describe('coalescing', function() { - it('merges overlapping selections', function() { - const hunks = [ - new Hunk(1, 1, 0, 4, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'added', -1, 4), - ]), - new Hunk(5, 7, 0, 4, '', [ - new HunkLine('line-5', 'added', -1, 7), - new HunkLine('line-6', 'added', -1, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'added', -1, 10), - ]), - ]; - - const selection0 = new FilePatchSelection(hunks) - .selectLine(hunks[0].lines[2]) - .selectLine(hunks[1].lines[1], true) - .addOrSubtractLineSelection(hunks[0].lines[0]) - .selectLine(hunks[1].lines[0], true) - .coalesce() - .selectPreviousLine(true); - assertEqualSets(selection0.getSelectedLines(), new Set([ - hunks[0].lines[0], - hunks[0].lines[1], - hunks[0].lines[2], - hunks[0].lines[3], - hunks[1].lines[0], - ])); - - const selection1 = selection0.addOrSubtractLineSelection(hunks[1].lines[3]) - .selectLine(hunks[0].lines[3], true) - .coalesce() - .selectNextLine(true); - assertEqualSets(selection1.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[0].lines[2], - hunks[0].lines[3], - hunks[1].lines[0], - hunks[1].lines[1], - hunks[1].lines[2], - hunks[1].lines[3], - ])); - }); - - it('merges adjacent selections', function() { - const hunks = [ - new Hunk(1, 1, 0, 4, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'added', -1, 4), - ]), - new Hunk(5, 7, 0, 4, '', [ - new HunkLine('line-5', 'added', -1, 7), - new HunkLine('line-6', 'added', -1, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'added', -1, 10), - ]), - ]; - - const selection0 = new FilePatchSelection(hunks) - .selectLine(hunks[0].lines[3]) - .selectLine(hunks[1].lines[1], true) - .addOrSubtractLineSelection(hunks[0].lines[1]) - .selectLine(hunks[0].lines[2], true) - .coalesce() - .selectPreviousLine(true); - assertEqualSets(selection0.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[0].lines[2], - hunks[0].lines[3], - hunks[1].lines[0], - ])); - - const selection1 = selection0.addOrSubtractLineSelection(hunks[1].lines[2]) - .selectLine(hunks[1].lines[1], true) - .coalesce() - .selectNextLine(true); - assertEqualSets(selection1.getSelectedLines(), new Set([ - hunks[0].lines[2], - hunks[0].lines[3], - hunks[1].lines[0], - hunks[1].lines[1], - hunks[1].lines[2], - ])); - }); - - it('expands selections to contain all adjacent context lines', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - new HunkLine('line-4', 'unchanged', 2, 4), - ]), - new Hunk(5, 7, 2, 4, '', [ - new HunkLine('line-5', 'unchanged', 5, 7), - new HunkLine('line-6', 'unchanged', 6, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'added', -1, 10), - ]), - ]; - - const selection0 = new FilePatchSelection(hunks) - .selectLine(hunks[1].lines[3]) - .selectLine(hunks[1].lines[2], true) - .addOrSubtractLineSelection(hunks[0].lines[1]) - .selectLine(hunks[0].lines[0], true) - .coalesce() - .selectNext(true); - assertEqualSets(selection0.getSelectedLines(), new Set([ - hunks[0].lines[1], - hunks[1].lines[2], - hunks[1].lines[3], - ])); - }); - - it('truncates or splits selections where they overlap a negative selection', function() { - const hunks = [ - new Hunk(1, 1, 0, 4, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'added', -1, 4), - ]), - new Hunk(5, 7, 0, 4, '', [ - new HunkLine('line-5', 'added', -1, 7), - new HunkLine('line-6', 'added', -1, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'added', -1, 10), - ]), - ]; - - const selection0 = new FilePatchSelection(hunks) - .selectLine(hunks[0].lines[0]) - .selectLine(hunks[1].lines[3], true) - .addOrSubtractLineSelection(hunks[0].lines[3]) - .selectLine(hunks[1].lines[0], true) - .coalesce() - .selectPrevious(true); - assertEqualSets(selection0.getSelectedLines(), new Set([ - hunks[0].lines[0], - hunks[0].lines[1], - hunks[0].lines[2], - hunks[1].lines[1], - hunks[1].lines[2], - ])); - }); - - it('does not blow up when coalescing with no selections', function() { - const hunks = [ - new Hunk(1, 1, 0, 1, '', [ - new HunkLine('line-1', 'added', -1, 1), - ]), - ]; - const selection0 = new FilePatchSelection(hunks) - .selectLine(hunks[0].lines[0]) - .addOrSubtractLineSelection(hunks[0].lines[0]); - assertEqualSets(selection0.getSelectedLines(), new Set()); - - selection0.coalesce(); - }); - }); - }); - - describe('hunk selection', function() { - it('selects the first hunk by default', function() { - const hunks = [ - new Hunk(1, 1, 0, 1, '', [ - new HunkLine('line-1', 'added', -1, 1), - ]), - new Hunk(5, 6, 0, 1, '', [ - new HunkLine('line-2', 'added', -1, 6), - ]), - ]; - const selection0 = new FilePatchSelection(hunks); - assertEqualSets(selection0.getSelectedHunks(), new Set([hunks[0]])); - }); - - it('starts a new hunk selection with selectHunk and updates an existing selection when preserveTail is true', function() { - const hunks = [ - new Hunk(1, 1, 0, 1, '', [ - new HunkLine('line-1', 'added', -1, 1), - ]), - new Hunk(5, 6, 0, 1, '', [ - new HunkLine('line-2', 'added', -1, 6), - ]), - new Hunk(10, 12, 0, 1, '', [ - new HunkLine('line-3', 'added', -1, 12), - ]), - new Hunk(15, 18, 0, 1, '', [ - new HunkLine('line-4', 'added', -1, 18), - ]), - ]; - const selection0 = new FilePatchSelection(hunks) - .selectHunk(hunks[1]); - assertEqualSets(selection0.getSelectedHunks(), new Set([hunks[1]])); - - const selection1 = selection0.selectHunk(hunks[3], true); - assertEqualSets(selection1.getSelectedHunks(), new Set([hunks[1], hunks[2], hunks[3]])); - - const selection2 = selection1.selectHunk(hunks[0], true); - assertEqualSets(selection2.getSelectedHunks(), new Set([hunks[0], hunks[1]])); - }); - - it('adds a new hunk selection with addOrSubtractHunkSelection and always updates the head of the most recent hunk selection', function() { - const hunks = [ - new Hunk(1, 1, 0, 1, '', [ - new HunkLine('line-1', 'added', -1, 1), - ]), - new Hunk(5, 6, 0, 1, '', [ - new HunkLine('line-2', 'added', -1, 6), - ]), - new Hunk(10, 12, 0, 1, '', [ - new HunkLine('line-3', 'added', -1, 12), - ]), - new Hunk(15, 18, 0, 1, '', [ - new HunkLine('line-4', 'added', -1, 18), - ]), - ]; - const selection0 = new FilePatchSelection(hunks) - .addOrSubtractHunkSelection(hunks[2]); - assertEqualSets(selection0.getSelectedHunks(), new Set([hunks[0], hunks[2]])); - - const selection1 = selection0.selectHunk(hunks[3], true); - assertEqualSets(selection1.getSelectedHunks(), new Set([hunks[0], hunks[2], hunks[3]])); - - const selection2 = selection1.selectHunk(hunks[1], true); - assertEqualSets(selection2.getSelectedHunks(), new Set([hunks[0], hunks[1], hunks[2]])); - }); - - it('allows the next or previous hunk to be selected', function() { - const hunks = [ - new Hunk(1, 1, 0, 1, '', [ - new HunkLine('line-1', 'added', -1, 1), - ]), - new Hunk(5, 6, 0, 1, '', [ - new HunkLine('line-2', 'added', -1, 6), - ]), - new Hunk(10, 12, 0, 1, '', [ - new HunkLine('line-3', 'added', -1, 12), - ]), - new Hunk(15, 18, 0, 1, '', [ - new HunkLine('line-4', 'added', -1, 18), - ]), - ]; - - const selection0 = new FilePatchSelection(hunks) - .selectNextHunk(); - assertEqualSets(selection0.getSelectedHunks(), new Set([hunks[1]])); - - const selection1 = selection0.selectNextHunk(); - assertEqualSets(selection1.getSelectedHunks(), new Set([hunks[2]])); - - const selection2 = selection1.selectNextHunk() - .selectNextHunk(); - assertEqualSets(selection2.getSelectedHunks(), new Set([hunks[3]])); - - const selection3 = selection2.selectPreviousHunk(); - assertEqualSets(selection3.getSelectedHunks(), new Set([hunks[2]])); - - const selection4 = selection3.selectPreviousHunk(); - assertEqualSets(selection4.getSelectedHunks(), new Set([hunks[1]])); - - const selection5 = selection4.selectPreviousHunk() - .selectPreviousHunk(); - assertEqualSets(selection5.getSelectedHunks(), new Set([hunks[0]])); - - const selection6 = selection5.selectNextHunk() - .selectNextHunk(true); - assertEqualSets(selection6.getSelectedHunks(), new Set([hunks[1], hunks[2]])); - - const selection7 = selection6.selectPreviousHunk(true); - assertEqualSets(selection7.getSelectedHunks(), new Set([hunks[1]])); - - const selection8 = selection7.selectPreviousHunk(true); - assertEqualSets(selection8.getSelectedHunks(), new Set([hunks[0], hunks[1]])); - }); - - it('allows all hunks to be selected', function() { - const hunks = [ - new Hunk(1, 1, 0, 1, '', [ - new HunkLine('line-1', 'added', -1, 1), - ]), - new Hunk(5, 6, 0, 1, '', [ - new HunkLine('line-2', 'added', -1, 6), - ]), - new Hunk(10, 12, 0, 1, '', [ - new HunkLine('line-3', 'added', -1, 12), - ]), - new Hunk(15, 18, 0, 1, '', [ - new HunkLine('line-4', 'added', -1, 18), - ]), - ]; - - const selection0 = new FilePatchSelection(hunks) - .selectAllHunks(); - assertEqualSets(selection0.getSelectedHunks(), new Set([hunks[0], hunks[1], hunks[2], hunks[3]])); - }); - }); - - describe('selection modes', function() { - it('allows the selection mode to be toggled between hunks and lines', function() { - const hunks = [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 8), - new HunkLine('line-8', 'added', -1, 9), - new HunkLine('line-9', 'added', -1, 10), - new HunkLine('line-10', 'deleted', 8, -1), - new HunkLine('line-11', 'deleted', 9, -1), - ]), - ]; - const selection0 = new FilePatchSelection(hunks); - - assert.equal(selection0.getMode(), 'hunk'); - assertEqualSets(selection0.getSelectedHunks(), new Set([hunks[0]])); - assertEqualSets(selection0.getSelectedLines(), getChangedLines(hunks[0])); - - const selection1 = selection0.selectNext(); - assert.equal(selection1.getMode(), 'hunk'); - assertEqualSets(selection1.getSelectedHunks(), new Set([hunks[1]])); - assertEqualSets(selection1.getSelectedLines(), getChangedLines(hunks[1])); - - const selection2 = selection1.toggleMode(); - assert.equal(selection2.getMode(), 'line'); - assertEqualSets(selection2.getSelectedHunks(), new Set([hunks[1]])); - assertEqualSets(selection2.getSelectedLines(), new Set([hunks[1].lines[1]])); - - const selection3 = selection2.selectNext(); - assertEqualSets(selection3.getSelectedHunks(), new Set([hunks[1]])); - assertEqualSets(selection3.getSelectedLines(), new Set([hunks[1].lines[2]])); - - const selection4 = selection3.toggleMode(); - assert.equal(selection4.getMode(), 'hunk'); - assertEqualSets(selection4.getSelectedHunks(), new Set([hunks[1]])); - assertEqualSets(selection4.getSelectedLines(), getChangedLines(hunks[1])); - - const selection5 = selection4.selectLine(hunks[0].lines[1]); - assert.equal(selection5.getMode(), 'line'); - assertEqualSets(selection5.getSelectedHunks(), new Set([hunks[0]])); - assertEqualSets(selection5.getSelectedLines(), new Set([hunks[0].lines[1]])); - - const selection6 = selection5.selectHunk(hunks[1]); - assert.equal(selection6.getMode(), 'hunk'); - assertEqualSets(selection6.getSelectedHunks(), new Set([hunks[1]])); - assertEqualSets(selection6.getSelectedLines(), getChangedLines(hunks[1])); - }); - }); - - describe('updateHunks(hunks)', function() { - it('collapses the line selection to a single line following the previous selected range with the highest start index', function() { - const oldHunks = [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 8), - new HunkLine('line-8', 'added', -1, 9), - new HunkLine('line-9', 'added', -1, 10), - new HunkLine('line-10', 'deleted', 8, -1), - new HunkLine('line-11', 'deleted', 9, -1), - ]), - ]; - const selection0 = new FilePatchSelection(oldHunks) - .selectLine(oldHunks[1].lines[2]) - .selectLine(oldHunks[1].lines[4], true); - - const newHunks = [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 3, 2, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'unchanged', 7, 8), - ]), - new Hunk(9, 10, 3, 2, '', [ - new HunkLine('line-8', 'unchanged', 9, 10), - new HunkLine('line-9', 'added', -1, 11), - new HunkLine('line-10', 'deleted', 10, -1), - new HunkLine('line-11', 'deleted', 11, -1), - ]), - ]; - const selection1 = selection0.updateHunks(newHunks); - - assertEqualSets(selection1.getSelectedLines(), new Set([ - newHunks[2].lines[1], - ])); - }); - - it('collapses the line selection to the line preceding the previous selected line if it was the *last* line', function() { - const oldHunks = [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - ]; - - const selection0 = new FilePatchSelection(oldHunks); - selection0.selectLine(oldHunks[0].lines[1]); - - const newHunks = [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'unchanged', 1, 2), - new HunkLine('line-3', 'unchanged', 2, 3), - ]), - ]; - const selection1 = selection0.updateHunks(newHunks); - - assertEqualSets(selection1.getSelectedLines(), new Set([ - newHunks[0].lines[0], - ])); - }); - - it('updates the hunk selection if it exceeds the new length of the hunks list', function() { - const oldHunks = [ - new Hunk(1, 1, 0, 1, '', [ - new HunkLine('line-1', 'added', -1, 1), - ]), - new Hunk(5, 6, 0, 1, '', [ - new HunkLine('line-2', 'added', -1, 6), - ]), - ]; - const selection0 = new FilePatchSelection(oldHunks) - .selectHunk(oldHunks[1]); - - const newHunks = [ - new Hunk(1, 1, 0, 1, '', [ - new HunkLine('line-1', 'added', -1, 1), - ]), - ]; - const selection1 = selection0.updateHunks(newHunks); - - assertEqualSets(selection1.getSelectedHunks(), new Set([newHunks[0]])); - }); - - it('deselects if updating with an empty hunk array', function() { - const oldHunks = [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - ]), - ]; - - const selection0 = new FilePatchSelection(oldHunks) - .selectLine(oldHunks[0], oldHunks[0].lines[1]) - .updateHunks([]); - assertEqualSets(selection0.getSelectedLines(), new Set()); - }); - - it('resolves the getNextUpdatePromise the next time hunks are changed', async function() { - const hunk0 = new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - ]); - const hunk1 = new Hunk(4, 4, 1, 3, '', [ - new HunkLine('line-4', 'added', -1, 1), - new HunkLine('line-7', 'added', -1, 2), - ]); - - const existingHunks = [hunk0, hunk1]; - const selection0 = new FilePatchSelection(existingHunks); - - let wasResolved = false; - selection0.getNextUpdatePromise().then(() => { wasResolved = true; }); - - const unchangedHunks = [hunk0, hunk1]; - const selection1 = selection0.updateHunks(unchangedHunks); - - assert.isFalse(wasResolved); - - const hunk2 = new Hunk(6, 4, 1, 3, '', [ - new HunkLine('line-12', 'added', -1, 1), - new HunkLine('line-77', 'added', -1, 2), - ]); - const changedHunks = [hunk0, hunk2]; - selection1.updateHunks(changedHunks); - - await assert.async.isTrue(wasResolved); - }); - }); - - describe('jumpToNextHunk() and jumpToPreviousHunk()', function() { - it('selects the next/previous hunk', function() { - const hunks = [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 3, 2, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'unchanged', 7, 8), - ]), - new Hunk(9, 10, 3, 2, '', [ - new HunkLine('line-8', 'unchanged', 9, 10), - new HunkLine('line-9', 'added', -1, 11), - new HunkLine('line-10', 'deleted', 10, -1), - new HunkLine('line-11', 'deleted', 11, -1), - ]), - ]; - const selection0 = new FilePatchSelection(hunks); - - // in hunk mode, selects the entire next/previous hunk - assert.equal(selection0.getMode(), 'hunk'); - assertEqualSets(selection0.getSelectedHunks(), new Set([hunks[0]])); - - const selection1 = selection0.jumpToNextHunk(); - assertEqualSets(selection1.getSelectedHunks(), new Set([hunks[1]])); - - const selection2 = selection1.jumpToNextHunk(); - assertEqualSets(selection2.getSelectedHunks(), new Set([hunks[2]])); - - const selection3 = selection2.jumpToNextHunk(); - assertEqualSets(selection3.getSelectedHunks(), new Set([hunks[2]])); - - const selection4 = selection3.jumpToPreviousHunk(); - assertEqualSets(selection4.getSelectedHunks(), new Set([hunks[1]])); - - const selection5 = selection4.jumpToPreviousHunk(); - assertEqualSets(selection5.getSelectedHunks(), new Set([hunks[0]])); - - const selection6 = selection5.jumpToPreviousHunk(); - assertEqualSets(selection6.getSelectedHunks(), new Set([hunks[0]])); - - // in line selection mode, the first changed line of the next/previous hunk is selected - const selection7 = selection6.toggleMode(); - assert.equal(selection7.getMode(), 'line'); - assertEqualSets(selection7.getSelectedLines(), new Set([getFirstChangedLine(hunks[0])])); - - const selection8 = selection7.jumpToNextHunk(); - assertEqualSets(selection8.getSelectedLines(), new Set([getFirstChangedLine(hunks[1])])); - - const selection9 = selection8.jumpToNextHunk(); - assertEqualSets(selection9.getSelectedLines(), new Set([getFirstChangedLine(hunks[2])])); - - const selection10 = selection9.jumpToNextHunk(); - assertEqualSets(selection10.getSelectedLines(), new Set([getFirstChangedLine(hunks[2])])); - - const selection11 = selection10.jumpToPreviousHunk(); - assertEqualSets(selection11.getSelectedLines(), new Set([getFirstChangedLine(hunks[1])])); - - const selection12 = selection11.jumpToPreviousHunk(); - assertEqualSets(selection12.getSelectedLines(), new Set([getFirstChangedLine(hunks[0])])); - - const selection13 = selection12.jumpToPreviousHunk(); - assertEqualSets(selection13.getSelectedLines(), new Set([getFirstChangedLine(hunks[0])])); - }); - }); - - describe('goToDiffLine(lineNumber)', function() { - it('selects the closest selectable hunk line', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'unchanged', 2, 4), - ]), - new Hunk(5, 7, 3, 4, '', [ - new HunkLine('line-7', 'unchanged', 5, 7), - new HunkLine('line-8', 'unchanged', 6, 8), - new HunkLine('line-9', 'added', -1, 9), - new HunkLine('line-10', 'unchanged', 7, 10), - ]), - ]; - - const selection0 = new FilePatchSelection(hunks); - const selection1 = selection0.goToDiffLine(2); - assert.equal(Array.from(selection1.getSelectedLines())[0].getText(), 'line-2'); - assertEqualSets(selection1.getSelectedLines(), new Set([hunks[0].lines[1]])); - - const selection2 = selection1.goToDiffLine(9); - assert.equal(Array.from(selection2.getSelectedLines())[0].getText(), 'line-9'); - assertEqualSets(selection2.getSelectedLines(), new Set([hunks[1].lines[2]])); - - // selects closest added hunk line - const selection3 = selection2.goToDiffLine(5); - assert.equal(Array.from(selection3.getSelectedLines())[0].getText(), 'line-3'); - assertEqualSets(selection3.getSelectedLines(), new Set([hunks[0].lines[2]])); - - const selection4 = selection3.goToDiffLine(8); - assert.equal(Array.from(selection4.getSelectedLines())[0].getText(), 'line-9'); - assertEqualSets(selection4.getSelectedLines(), new Set([hunks[1].lines[2]])); - - const selection5 = selection4.goToDiffLine(11); - assert.equal(Array.from(selection5.getSelectedLines())[0].getText(), 'line-9'); - assertEqualSets(selection5.getSelectedLines(), new Set([hunks[1].lines[2]])); - }); - }); -}); - -function getChangedLines(hunk) { - return new Set(hunk.getLines().filter(l => l.isChanged())); -} - -function getFirstChangedLine(hunk) { - return hunk.getLines().find(l => l.isChanged()); -} From bce2f0d3200053939b07e046a76bdd3268986007 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 21 Aug 2018 10:56:25 -0400 Subject: [PATCH 0129/4053] Use nullFiles when constructing FilePatches --- lib/models/patch/builder.js | 8 ++- test/models/patch/builder.test.js | 87 +++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 457d02ef46..5270aeebe5 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -37,8 +37,12 @@ function singleDiffFilePatch(diff) { newSymlink = diff.hunks[0].lines[2].slice(1); } - const oldFile = new File({path: diff.oldPath, mode: diff.oldMode, symlink: oldSymlink}); - const newFile = new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink}); + const oldFile = diff.oldPath !== null || diff.oldMode !== null + ? new File({path: diff.oldPath, mode: diff.oldMode, symlink: oldSymlink}) + : nullFile; + const newFile = diff.newPath !== null || diff.newMode !== null + ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink}) + : nullFile; const patch = new Patch({status: diff.status, hunks, bufferText}); return new FilePatch(oldFile, newFile, patch); diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 3bd1db0bbb..6539a4e824 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -176,6 +176,93 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getNewSymlink(), 'new/destination'); }); + it('assembles a patch from a file deletion', function() { + const p = buildFilePatch([{ + oldPath: 'old/path', + oldMode: '100644', + newPath: null, + newMode: null, + status: 'deleted', + hunks: [ + { + oldStartLine: 1, + oldLineCount: 4, + newStartLine: 0, + newLineCount: 0, + lines: [ + '-line-0', + '-line-1', + '-line-2', + '-line-3', + ], + }, + ], + }]); + + assert.isTrue(p.getOldFile().isPresent()); + assert.strictEqual(p.getOldPath(), 'old/path'); + assert.strictEqual(p.getOldMode(), '100644'); + assert.isFalse(p.getNewFile().isPresent()); + assert.strictEqual(p.getPatch().getStatus(), 'deleted'); + + const buffer = 'line-0\nline-1\nline-2\nline-3\n'; + assert.strictEqual(p.getBufferText(), buffer); + + assertInPatch(p).hunks( + { + startRow: 0, + endRow: 3, + header: '@@ -1,4 +0,0 @@', + changes: [ + {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n-line-3\n', range: [[0, 0], [3, 0]]}, + ], + }, + ); + }); + + it('assembles a patch from a file addition', function() { + const p = buildFilePatch([{ + oldPath: null, + oldMode: null, + newPath: 'new/path', + newMode: '100755', + status: 'added', + hunks: [ + { + oldStartLine: 0, + oldLineCount: 0, + newStartLine: 1, + newLineCount: 3, + lines: [ + '+line-0', + '+line-1', + '+line-2', + ], + }, + ], + }]); + + assert.isFalse(p.getOldFile().isPresent()); + assert.isTrue(p.getNewFile().isPresent()); + assert.strictEqual(p.getNewPath(), 'new/path'); + assert.strictEqual(p.getNewMode(), '100755'); + assert.strictEqual(p.getPatch().getStatus(), 'added'); + + const buffer = 'line-0\nline-1\nline-2\n'; + assert.strictEqual(p.getBufferText(), buffer); + + assertInPatch(p).hunks( + { + startRow: 0, + endRow: 2, + header: '@@ -0,0 +1,3 @@', + changes: [ + {kind: 'addition', string: '+line-0\n+line-1\n+line-2\n', range: [[0, 0], [2, 0]]}, + ], + }, + ); + }); + it('throws an error with an unknown diff status character', function() { assert.throws(() => { buildFilePatch([{ From 44745833556eaeba519a045cf120ea10eb14028d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 22 Aug 2018 09:48:17 -0400 Subject: [PATCH 0130/4053] Bypass readOnly when setting TextEditor contents --- lib/atom/atom-text-editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index c8af6dbb22..fef2e5ca7b 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -82,7 +82,7 @@ export default class AtomTextEditor extends React.PureComponent { this.refElement.map(element => { const editor = element.getModel(); - editor.setText(this.props.text); + editor.setText(this.props.text, {bypassReadOnly: true}); this.refModel.setter(editor); From 6b21f428378e662a600a25db7a92bacf5bb15f03 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 22 Aug 2018 09:48:33 -0400 Subject: [PATCH 0131/4053] nullPatch needs to implement getMaxLineNumberWidth() --- lib/models/patch/patch.js | 4 ++++ test/models/patch/patch.test.js | 1 + 2 files changed, 5 insertions(+) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 3e23f94955..0e5289ba85 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -361,6 +361,10 @@ export const nullPatch = { return this; }, + getMaxLineNumberWidth() { + return 0; + }, + toString() { return ''; }, diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 398bb95b01..e3b3350d21 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -489,6 +489,7 @@ describe('Patch', function() { assert.isFalse(nullPatch.isPresent()); assert.strictEqual(nullPatch.toString(), ''); assert.strictEqual(nullPatch.getChangedLineCount(), 0); + assert.strictEqual(nullPatch.getMaxLineNumberWidth(), 0); }); }); From 890c6f6b55460030754846eef4df1625de4b7455 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 22 Aug 2018 16:24:32 -0400 Subject: [PATCH 0132/4053] Pass missing props in FilePatchController tests --- test/controllers/file-patch-controller.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index ad18b3cb4d..03410e9bdc 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -27,9 +27,16 @@ describe('FilePatchController', function() { function buildApp(overrideProps = {}) { const props = { + repository, stagingStatus: 'unstaged', + relPath: 'a.txt', isPartiallyStaged: false, filePatch, + workspace: atomEnv.workspace, + tooltips: atomEnv.tooltips, + destroy: () => {}, + discardLines: () => {}, + undoLastDiscard: () => {}, ...overrideProps, }; From 1cc0665f9a2a19e3c93533ac1593142d892d041c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 22 Aug 2018 16:25:04 -0400 Subject: [PATCH 0133/4053] Adapt FilePatchView to the model changes --- lib/views/file-patch-view.js | 235 ++++++++++++++++++++++------------- 1 file changed, 149 insertions(+), 86 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index a13ccbf64a..37ba4beb71 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -1,6 +1,7 @@ import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; +import {Range} from 'atom'; import {autobind} from '../helpers'; import AtomTextEditor from '../atom/atom-text-editor'; @@ -11,6 +12,7 @@ import Gutter from '../atom/gutter'; import FilePatchHeaderView from './file-patch-header-view'; import FilePatchMetaView from './file-patch-meta-view'; import HunkHeaderView from './hunk-header-view'; +import RefHolder from '../models/ref-holder'; const executableText = { 100644: 'non executable', @@ -55,36 +57,32 @@ export default class FilePatchView extends React.Component { 'oldLineNumberLabel', 'newLineNumberLabel', ); - const presentedFilePatch = this.props.filePatch.present(); - const selectedLines = this.props.selection.getSelectedLines(); this.state = { lastSelection: this.props.selection, selectedHunks: this.props.selection.getSelectedHunks(), - selectedLines, - presentedFilePatch, - selectedLinePositions: Array.from(selectedLines, line => presentedFilePatch.getPositionForLine(line)), + selectedLines: this.props.selection.getSelectedLines(), + selectedLineRanges: Array.from( + this.props.selection.getSelectedLines(), + line => Range.fromObject([[line, 0], [line, 0]]), + ), }; this.lastMouseMoveLine = null; + this.hunksByMarkerID = new Map(); + this.hunkMarkerLayerHolder = new RefHolder(); } static getDerivedStateFromProps(props, state) { const nextState = {}; - let currentPresentedFilePatch = state.presentedFilePatch; - - if (props.filePatch !== state.presentedFilePatch.getFilePatch()) { - currentPresentedFilePatch = props.filePatch.present(); - nextState.presentedFilePatch = currentPresentedFilePatch; - } if (props.selection !== state.lastSelection) { nextState.lastSelection = props.selection; nextState.selectedHunks = props.selection.getSelectedHunks(); nextState.selectedLines = props.selection.getSelectedLines(); - - nextState.selectedLinePositions = Array.from(nextState.selectedLines, line => { - return currentPresentedFilePatch.getPositionForLine(line); - }); + nextState.selectedLineRanges = Array.from( + props.selection.getSelectedLines(), + line => Range.fromObject([[line, 0], [line, 0]]), + ); } return nextState; @@ -121,11 +119,12 @@ export default class FilePatchView extends React.Component {
+ autoHeight={false} + readOnly={true}> { - const isSelected = selectedHunks.has(hunk); - let buttonSuffix = (isHunkSelectionMode || !isSelected) ? ' Hunk' : ' Selection'; - if (isSelected && selectedHunks.size > 1) { - buttonSuffix += 's'; - } - const toggleSelectionLabel = `${toggleVerb}${buttonSuffix}`; - const discardSelectionLabel = `Discard${buttonSuffix}`; - - const onDidChange = event => { - hunkStartPositions[index] = event.newPosition; - }; - - return ( - - - - this.toggleSelection(hunk)} - discardSelection={() => this.discardSelection(hunk)} - mouseDown={this.props.mouseDownOnHeader} - /> - - - ); - }); + return ( + + {/* + The markers on this layer are used to efficiently locate Hunks based on buffer row. + See .getHunkAt(). + */} + + {this.props.filePatch.getHunks().map((hunk, index) => { + return ( + { this.hunksByMarkerID.set(id, hunk); }} + /> + ); + })} + + {/* + These markers are decorated to position hunk headers as block decorations. + */} + + {this.props.filePatch.getHunks().map((hunk, index) => { + const isSelected = selectedHunks.has(hunk); + let buttonSuffix = (isHunkSelectionMode || !isSelected) ? ' Hunk' : ' Selection'; + if (isSelected && selectedHunks.size > 1) { + buttonSuffix += 's'; + } + const toggleSelectionLabel = `${toggleVerb}${buttonSuffix}`; + const discardSelectionLabel = `Discard${buttonSuffix}`; + + const startPoint = hunk.getBufferRange().start; + const startRange = new Range(startPoint, startPoint); + + return ( + + + this.toggleSelection(hunk)} + discardSelection={() => this.discardSelection(hunk)} + mouseDown={this.props.mouseDownOnHeader} + /> + + + ); + })} + + + ); } - renderLineDecorations(positions, lineClass, {line, gutter}) { - if (positions.length === 0) { + renderLineDecorations(ranges, lineClass, {line, gutter}) { + if (ranges.length === 0) { return null; } return ( - {positions.map((position, index) => { - const onDidChange = event => { - positions[index] = event.newPosition; - }; - + {ranges.map((range, index) => { return ( ); })} - {line && } + {line && } {gutter && ( - - + + )} @@ -383,10 +404,9 @@ export default class FilePatchView extends React.Component { } didMouseDownOnLineNumber(event) { - const line = this.state.presentedFilePatch.getLineAt(event.bufferRow); - const hunk = this.state.presentedFilePatch.getHunkAt(event.bufferRow); - - if (line === undefined || hunk === undefined) { + const line = event.bufferRow; + const hunk = this.getHunkAt(event.bufferRow); + if (hunk === undefined) { return; } @@ -394,13 +414,13 @@ export default class FilePatchView extends React.Component { } didMouseMoveOnLineNumber(event) { - const line = this.state.presentedFilePatch.getLineAt(event.bufferRow); + const line = event.bufferRow; if (this.lastMouseMoveLine === line || line === undefined) { return; } this.lastMouseMoveLine = line; - const hunk = this.state.presentedFilePatch.getHunkAt(event.bufferRow); + const hunk = this.getHunkAt(event.bufferRow); if (hunk === undefined) { return; } @@ -412,12 +432,31 @@ export default class FilePatchView extends React.Component { this.props.mouseUp(); } - oldLineNumberLabel({bufferRow}) { - return this.pad(this.state.presentedFilePatch.getOldLineNumberAt(bufferRow)); + oldLineNumberLabel({bufferRow, softWrapped}) { + const hunk = this.getHunkAt(bufferRow); + if (hunk === undefined) { + return this.pad(''); + } + + const oldRow = hunk.getOldRowAt(bufferRow); + if (softWrapped) { + return this.pad(oldRow === null ? '' : '•'); + } + + return this.pad(oldRow); } - newLineNumberLabel({bufferRow}) { - return this.pad(this.state.presentedFilePatch.getNewLineNumberAt(bufferRow)); + newLineNumberLabel({bufferRow, softWrapped}) { + const hunk = this.getHunkAt(bufferRow); + if (hunk === undefined) { + return ''; + } + + const newRow = hunk.getNewRowAt(bufferRow); + if (softWrapped) { + return this.pad(newRow === null ? '' : '•'); + } + return this.pad(newRow); } toggleSelection(hunk) { @@ -436,9 +475,33 @@ export default class FilePatchView extends React.Component { } } + getHunkAt(bufferRow) { + const hunkFromMarker = this.hunkMarkerLayerHolder.map(layer => { + const markers = layer.findMarkers({intersectsRow: bufferRow}); + if (markers.length === 0) { + return null; + } + return this.hunksByMarkerID.get(markers[0].id); + }).getOr(null); + + if (hunkFromMarker !== null) { + return hunkFromMarker; + } + + // Fall back to a linear hunk scan. + for (const hunk of this.props.filePatch.getHunks()) { + if (hunk.includesBufferRow(bufferRow)) { + return hunk; + } + } + + // Hunk not found. + return undefined; + } + pad(num) { - const maxDigits = this.state.presentedFilePatch.getMaxLineNumberWidth(); - if (num === undefined || num === -1) { + const maxDigits = this.props.filePatch.getMaxLineNumberWidth(); + if (num === null) { return NBSP_CHARACTER.repeat(maxDigits); } else { return NBSP_CHARACTER.repeat(maxDigits - num.toString().length) + num.toString(); From 2c91e572ba9ba16a5b4e5da86a8cbd01f9c9e16f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 22 Aug 2018 16:25:19 -0400 Subject: [PATCH 0134/4053] FilePatchView tests :white_check_mark: --- test/views/file-patch-view.test.js | 310 +++++++++++++++++++++ test/views/file-patch-view.test.pending.js | 239 ---------------- 2 files changed, 310 insertions(+), 239 deletions(-) create mode 100644 test/views/file-patch-view.test.js delete mode 100644 test/views/file-patch-view.test.pending.js diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js new file mode 100644 index 0000000000..a8fa414c74 --- /dev/null +++ b/test/views/file-patch-view.test.js @@ -0,0 +1,310 @@ +import path from 'path'; +import fs from 'fs-extra'; +import React from 'react'; +import {shallow, mount} from 'enzyme'; + +import {cloneRepository, buildRepository} from '../helpers'; +import FilePatchView from '../../lib/views/file-patch-view'; +import FilePatchSelection from '../../lib/models/file-patch-selection'; +import {nullFile} from '../../lib/models/patch/file'; +import Hunk from '../../lib/models/patch/hunk'; +import {Addition, Deletion, NoNewline} from '../../lib/models/patch/region'; +import IndexedRowRange from '../../lib/models/indexed-row-range'; + +describe('FilePatchView', function() { + let atomEnv, repository, filePatch; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + + const workdirPath = await cloneRepository(); + repository = await buildRepository(workdirPath); + + // a.txt: unstaged changes + await fs.writeFile(path.join(workdirPath, 'a.txt'), 'changed\n'); + filePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(overrideProps = {}) { + const props = { + relPath: 'a.txt', + stagingStatus: 'unstaged', + isPartiallyStaged: false, + filePatch, + selection: new FilePatchSelection(filePatch.getHunks()), + repository, + + tooltips: atomEnv.tooltips, + + mouseDownOnHeader: () => {}, + mouseDownOnLineNumber: () => {}, + mouseMoveOnLineNumber: () => {}, + mouseUp: () => {}, + + diveIntoMirrorPatch: () => {}, + openFile: () => {}, + toggleFile: () => {}, + selectAndToggleHunk: () => {}, + toggleLines: () => {}, + toggleModeChange: () => {}, + toggleSymlinkChange: () => {}, + undoLastDiscard: () => {}, + discardLines: () => {}, + selectAndDiscardHunk: () => {}, + + ...overrideProps, + }; + + return ; + } + + it('renders the file header', function() { + const wrapper = shallow(buildApp()); + assert.isTrue(wrapper.find('FilePatchHeaderView').exists()); + }); + + it('renders the file patch within an editor', function() { + const wrapper = mount(buildApp()); + + const editor = wrapper.find('AtomTextEditor'); + assert.strictEqual(editor.instance().getModel().getText(), filePatch.getBufferText()); + }); + + describe('executable mode changes', function() { + it('does not render if the mode has not changed', function() { + const fp = filePatch.clone({ + oldFile: filePatch.getOldFile().clone({mode: '100644'}), + newFile: filePatch.getNewFile().clone({mode: '100644'}), + }); + + const wrapper = shallow(buildApp({filePatch: fp})); + assert.isFalse(wrapper.find('FilePatchMetaView[title="Mode change"]').exists()); + }); + + it('renders change details within a meta container', function() { + const fp = filePatch.clone({ + oldFile: filePatch.getOldFile().clone({mode: '100644'}), + newFile: filePatch.getNewFile().clone({mode: '100755'}), + }); + + const wrapper = mount(buildApp({filePatch: fp, stagingStatus: 'unstaged'})); + + const meta = wrapper.find('FilePatchMetaView[title="Mode change"]'); + assert.strictEqual(meta.prop('actionIcon'), 'icon-move-down'); + assert.strictEqual(meta.prop('actionText'), 'Stage Mode Change'); + + const details = meta.find('.github-FilePatchView-metaDetails'); + assert.strictEqual(details.text(), 'File changed modefrom non executable 100644to executable 100755'); + }); + + it("stages or unstages the mode change when the meta container's action is triggered", function() { + const fp = filePatch.clone({ + oldFile: filePatch.getOldFile().clone({mode: '100644'}), + newFile: filePatch.getNewFile().clone({mode: '100755'}), + }); + + const toggleModeChange = sinon.stub(); + const wrapper = shallow(buildApp({filePatch: fp, stagingStatus: 'staged', toggleModeChange})); + + const meta = wrapper.find('FilePatchMetaView[title="Mode change"]'); + assert.isTrue(meta.exists()); + assert.strictEqual(meta.prop('actionIcon'), 'icon-move-up'); + assert.strictEqual(meta.prop('actionText'), 'Unstage Mode Change'); + + meta.prop('action')(); + assert.isTrue(toggleModeChange.called); + }); + }); + + describe('symlink changes', function() { + it('does not render if the symlink status is unchanged', function() { + const fp = filePatch.clone({ + oldFile: filePatch.getOldFile().clone({mode: '100644'}), + newFile: filePatch.getNewFile().clone({mode: '100755'}), + }); + + const wrapper = mount(buildApp({filePatch: fp})); + assert.lengthOf(wrapper.find('FilePatchMetaView').filterWhere(v => v.prop('title').startsWith('Symlink')), 0); + }); + + it('renders symlink change information within a meta container', function() { + const fp = filePatch.clone({ + oldFile: filePatch.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), + newFile: filePatch.getNewFile().clone({mode: '120000', symlink: '/new.txt'}), + }); + + const wrapper = mount(buildApp({filePatch: fp, stagingStatus: 'unstaged'})); + const meta = wrapper.find('FilePatchMetaView[title="Symlink changed"]'); + assert.isTrue(meta.exists()); + assert.strictEqual(meta.prop('actionIcon'), 'icon-move-down'); + assert.strictEqual(meta.prop('actionText'), 'Stage Symlink Change'); + assert.strictEqual( + meta.find('.github-FilePatchView-metaDetails').text(), + 'Symlink changedfrom /old.txtto /new.txt.', + ); + }); + + it('stages or unstages the symlink change', function() { + const toggleSymlinkChange = sinon.stub(); + const fp = filePatch.clone({ + oldFile: filePatch.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), + newFile: filePatch.getNewFile().clone({mode: '120000', symlink: '/new.txt'}), + }); + + const wrapper = mount(buildApp({filePatch: fp, stagingStatus: 'staged', toggleSymlinkChange})); + const meta = wrapper.find('FilePatchMetaView[title="Symlink changed"]'); + assert.isTrue(meta.exists()); + assert.strictEqual(meta.prop('actionIcon'), 'icon-move-up'); + assert.strictEqual(meta.prop('actionText'), 'Unstage Symlink Change'); + + meta.find('button.icon-move-up').simulate('click'); + assert.isTrue(toggleSymlinkChange.called); + }); + + it('renders details for a symlink deletion', function() { + const fp = filePatch.clone({ + oldFile: filePatch.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), + newFile: nullFile, + }); + + const wrapper = mount(buildApp({filePatch: fp})); + const meta = wrapper.find('FilePatchMetaView[title="Symlink deleted"]'); + assert.isTrue(meta.exists()); + assert.strictEqual( + meta.find('.github-FilePatchView-metaDetails').text(), + 'Symlinkto /old.txtdeleted.', + ); + }); + + it('renders details for a symlink creation', function() { + const fp = filePatch.clone({ + oldFile: nullFile, + newFile: filePatch.getOldFile().clone({mode: '120000', symlink: '/new.txt'}), + }); + + const wrapper = mount(buildApp({filePatch: fp})); + const meta = wrapper.find('FilePatchMetaView[title="Symlink created"]'); + assert.isTrue(meta.exists()); + assert.strictEqual( + meta.find('.github-FilePatchView-metaDetails').text(), + 'Symlinkto /new.txtcreated.', + ); + }); + }); + + it('renders a header for each hunk', function() { + const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 2, newStartRow: 1, newRowCount: 3, + sectionHeading: 'first hunk', + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + ], + }), + new Hunk({ + oldStartRow: 10, oldRowCount: 3, newStartRow: 11, newRowCount: 2, + sectionHeading: 'second hunk', + rowRange: new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 15, endOffset: 30}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 5, endOffset: 10})), + ], + }), + ]; + const fp = filePatch.clone({ + patch: filePatch.getPatch().clone({hunks, bufferText}), + }); + const wrapper = mount(buildApp({filePatch: fp})); + assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[0])); + assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[1])); + }); + + describe('hunk lines', function() { + let linesPatch; + + beforeEach(function() { + const bufferText = + '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n' + + '0010\n0011\n0012\n0013\n0014\n0015\n0016\n' + + ' No newline at end of file\n'; + const hunks = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 6, + sectionHeading: 'first hunk', + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [6, 0]], startOffset: 0, endOffset: 35}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 5, endOffset: 15})), + new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [3, 0]], startOffset: 15, endOffset: 20})), + new Addition(new IndexedRowRange({bufferRange: [[4, 0], [5, 0]], startOffset: 20, endOffset: 30})), + ], + }), + new Hunk({ + oldStartRow: 10, oldRowCount: 0, newStartRow: 13, newRowCount: 0, + sectionHeading: 'second hunk', + rowRange: new IndexedRowRange({bufferRange: [[7, 0], [17, 0]], startOffset: 35, endOffset: 112}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[8, 0], [10, 0]], startOffset: 40, endOffset: 55})), + new Addition(new IndexedRowRange({bufferRange: [[12, 0], [14, 0]], startOffset: 60, endOffset: 75})), + new Deletion(new IndexedRowRange({bufferRange: [[15, 0], [15, 0]], startOffset: 75, endOffset: 80})), + new NoNewline(new IndexedRowRange({bufferRange: [[17, 0], [17, 0]], startOffset: 85, endOffset: 112})), + ], + }), + ]; + + linesPatch = filePatch.clone({ + patch: filePatch.getPatch().clone({hunks, bufferText}), + }); + }); + + it('decorates added lines', function() { + const wrapper = mount(buildApp({filePatch: linesPatch})); + + const decorationSelector = 'Decoration[type="line"][className="github-FilePatchView-line--added"]'; + const decoration = wrapper.find(decorationSelector); + assert.isTrue(decoration.exists()); + + const layer = wrapper.find('MarkerLayer').filterWhere(each => each.find(decorationSelector).exists()); + const markers = layer.find('Marker').map(marker => marker.prop('bufferRange').serialize()); + assert.deepEqual(markers, [ + [[1, 0], [2, 0]], + [[4, 0], [5, 0]], + [[12, 0], [14, 0]], + ]); + }); + + it('decorates deleted lines', function() { + const wrapper = mount(buildApp({filePatch: linesPatch})); + + const decorationSelector = 'Decoration[type="line"][className="github-FilePatchView-line--deleted"]'; + const decoration = wrapper.find(decorationSelector); + assert.isTrue(decoration.exists()); + + const layer = wrapper.find('MarkerLayer').filterWhere(each => each.find(decorationSelector).exists()); + const markers = layer.find('Marker').map(marker => marker.prop('bufferRange').serialize()); + assert.deepEqual(markers, [ + [[3, 0], [3, 0]], + [[8, 0], [10, 0]], + [[15, 0], [15, 0]], + ]); + }); + + it('decorates the nonewline line', function() { + const wrapper = mount(buildApp({filePatch: linesPatch})); + + const decorationSelector = 'Decoration[type="line"][className="github-FilePatchView-line--nonewline"]'; + const decoration = wrapper.find(decorationSelector); + assert.isTrue(decoration.exists()); + + const layer = wrapper.find('MarkerLayer').filterWhere(each => each.find(decorationSelector).exists()); + const markers = layer.find('Marker').map(marker => marker.prop('bufferRange').serialize()); + assert.deepEqual(markers, [ + [[17, 0], [17, 0]], + ]); + }); + }); +}); diff --git a/test/views/file-patch-view.test.pending.js b/test/views/file-patch-view.test.pending.js deleted file mode 100644 index 2209313379..0000000000 --- a/test/views/file-patch-view.test.pending.js +++ /dev/null @@ -1,239 +0,0 @@ -import path from 'path'; -import fs from 'fs-extra'; -import React from 'react'; -import {shallow, mount} from 'enzyme'; - -import {cloneRepository, buildRepository} from '../helpers'; -import Hunk from '../../lib/models/hunk'; -import HunkLine from '../../lib/models/hunk-line'; -import FilePatchView from '../../lib/views/file-patch-view'; - -describe('FilePatchView', function() { - let atomEnv, repository, filePatch; - - beforeEach(async function() { - atomEnv = global.buildAtomEnvironment(); - - const workdirPath = await cloneRepository(); - repository = await buildRepository(workdirPath); - - // a.txt: unstaged changes - await fs.writeFile(path.join(workdirPath, 'a.txt'), 'changed\n'); - filePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); - }); - - afterEach(function() { - atomEnv.destroy(); - }); - - function buildApp(overrideProps = {}) { - const props = { - relPath: 'a.txt', - stagingStatus: 'unstaged', - isPartiallyStaged: false, - filePatch, - repository, - tooltips: atomEnv.tooltips, - - undoLastDiscard: () => {}, - diveIntoMirrorPatch: () => {}, - openFile: () => {}, - toggleFile: () => {}, - toggleModeChange: () => {}, - toggleSymlinkChange: () => {}, - - ...overrideProps, - }; - - return ; - } - - it('renders the file header', function() { - const wrapper = shallow(buildApp()); - assert.isTrue(wrapper.find('FilePatchHeaderView').exists()); - }); - - it('renders the file patch within an editor', function() { - const wrapper = mount(buildApp()); - - const editor = wrapper.find('AtomTextEditor'); - assert.strictEqual(editor.instance().getModel().getText(), filePatch.present().getText()); - }); - - describe('executable mode changes', function() { - it('does not render if the mode has not changed', function() { - sinon.stub(filePatch, 'getOldMode').returns('100644'); - sinon.stub(filePatch, 'getNewMode').returns('100644'); - - const wrapper = shallow(buildApp()); - assert.isFalse(wrapper.find('FilePatchMetaView[title="Mode change"]').exists()); - }); - - it('renders change details within a meta container', function() { - sinon.stub(filePatch, 'getOldMode').returns('100644'); - sinon.stub(filePatch, 'getNewMode').returns('100755'); - - const wrapper = mount(buildApp({stagingStatus: 'unstaged'})); - - const meta = wrapper.find('FilePatchMetaView[title="Mode change"]'); - assert.isTrue(meta.exists()); - assert.strictEqual(meta.prop('actionIcon'), 'icon-move-down'); - assert.strictEqual(meta.prop('actionText'), 'Stage Mode Change'); - - const details = meta.find('.github-FilePatchView-metaDetails'); - assert.strictEqual(details.text(), 'File changed modefrom non executable 100644to executable 100755'); - }); - - it("stages or unstages the mode change when the meta container's action is triggered", function() { - sinon.stub(filePatch, 'getOldMode').returns('100644'); - sinon.stub(filePatch, 'getNewMode').returns('100755'); - - const toggleModeChange = sinon.stub(); - const wrapper = shallow(buildApp({stagingStatus: 'staged', toggleModeChange})); - - const meta = wrapper.find('FilePatchMetaView[title="Mode change"]'); - assert.isTrue(meta.exists()); - assert.strictEqual(meta.prop('actionIcon'), 'icon-move-up'); - assert.strictEqual(meta.prop('actionText'), 'Unstage Mode Change'); - - meta.prop('action')(); - assert.isTrue(toggleModeChange.called); - }); - }); - - describe('symlink changes', function() { - it('does not render if the symlink status is unchanged', function() { - const wrapper = mount(buildApp()); - assert.lengthOf(wrapper.find('FilePatchMetaView').filterWhere(v => v.prop('title').startsWith('Symlink')), 0); - }); - - it('renders symlink change information within a meta container', function() { - sinon.stub(filePatch, 'hasSymlink').returns(true); - sinon.stub(filePatch, 'getOldSymlink').returns('/old.txt'); - sinon.stub(filePatch, 'getNewSymlink').returns('/new.txt'); - - const wrapper = mount(buildApp({stagingStatus: 'unstaged'})); - const meta = wrapper.find('FilePatchMetaView[title="Symlink changed"]'); - assert.isTrue(meta.exists()); - assert.strictEqual(meta.prop('actionIcon'), 'icon-move-down'); - assert.strictEqual(meta.prop('actionText'), 'Stage Symlink Change'); - assert.strictEqual( - meta.find('.github-FilePatchView-metaDetails').text(), - 'Symlink changedfrom /old.txtto /new.txt.', - ); - }); - - it('stages or unstages the symlink change', function() { - const toggleSymlinkChange = sinon.stub(); - sinon.stub(filePatch, 'hasSymlink').returns(true); - sinon.stub(filePatch, 'getOldSymlink').returns('/old.txt'); - sinon.stub(filePatch, 'getNewSymlink').returns('/new.txt'); - - const wrapper = mount(buildApp({stagingStatus: 'staged', toggleSymlinkChange})); - const meta = wrapper.find('FilePatchMetaView[title="Symlink changed"]'); - assert.isTrue(meta.exists()); - assert.strictEqual(meta.prop('actionIcon'), 'icon-move-up'); - assert.strictEqual(meta.prop('actionText'), 'Unstage Symlink Change'); - - meta.find('button.icon-move-up').simulate('click'); - assert.isTrue(toggleSymlinkChange.called); - }); - - it('renders details for a symlink deletion', function() { - sinon.stub(filePatch, 'hasSymlink').returns(true); - sinon.stub(filePatch, 'getOldSymlink').returns('/old.txt'); - sinon.stub(filePatch, 'getNewSymlink').returns(null); - - const wrapper = mount(buildApp()); - const meta = wrapper.find('FilePatchMetaView[title="Symlink deleted"]'); - assert.isTrue(meta.exists()); - assert.strictEqual( - meta.find('.github-FilePatchView-metaDetails').text(), - 'Symlinkto /old.txtdeleted.', - ); - }); - - it('renders details for a symlink creation', function() { - sinon.stub(filePatch, 'hasSymlink').returns(true); - sinon.stub(filePatch, 'getOldSymlink').returns(null); - sinon.stub(filePatch, 'getNewSymlink').returns('/new.txt'); - - const wrapper = mount(buildApp()); - const meta = wrapper.find('FilePatchMetaView[title="Symlink created"]'); - assert.isTrue(meta.exists()); - assert.strictEqual( - meta.find('.github-FilePatchView-metaDetails').text(), - 'Symlinkto /new.txtcreated.', - ); - }); - }); - - it('renders a header for each hunk', function() { - const hunks = [ - new Hunk(0, 0, 5, 5, 'hunk 0', []), - new Hunk(10, 10, 15, 15, 'hunk 1', []), - ]; - sinon.stub(filePatch, 'getHunks').returns(hunks); - - const wrapper = mount(buildApp()); - assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[0])); - assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[1])); - }); - - describe('hunk lines', function() { - it('decorates added lines', function() { - const hunks = [ - new Hunk(0, 0, 1, 1, 'hunk 0', [ - new HunkLine('line 0', 'added', 0, 1, 0), - new HunkLine('line 1', 'deleted', 0, 1, 0), - ]), - ]; - sinon.stub(filePatch, 'getHunks').returns(hunks); - - const wrapper = mount(buildApp()); - assert.lengthOf( - wrapper.find('Decoration').filterWhere(h => { - return h.prop('type') === 'line' && h.prop('className') === 'github-FilePatchView-line--added'; - }), - 1, - ); - }); - - it('decorates deleted lines', function() { - const hunks = [ - new Hunk(0, 0, 1, 1, 'hunk 0', [ - new HunkLine('line 0', 'added', 0, 1, 0), - new HunkLine('line 1', 'deleted', 0, 1, 0), - ]), - ]; - sinon.stub(filePatch, 'getHunks').returns(hunks); - - const wrapper = mount(buildApp()); - assert.lengthOf( - wrapper.find('Decoration').filterWhere(h => { - return h.prop('type') === 'line' && h.prop('className') === 'github-FilePatchView-line--deleted'; - }), - 1, - ); - }); - - it('decorates the nonewline line', function() { - const hunks = [ - new Hunk(0, 0, 1, 1, 'hunk 0', [ - new HunkLine('line 0', 'added', 0, 1, 0), - new HunkLine('line 1', 'deleted', 0, 1, 0), - new HunkLine('no newline', 'nonewline', 0, 1, 0), - ]), - ]; - sinon.stub(filePatch, 'getHunks').returns(hunks); - - const wrapper = mount(buildApp()); - assert.lengthOf( - wrapper.find('Decoration').filterWhere(h => { - return h.prop('type') === 'line' && h.prop('className') === 'github-FilePatchView-line--nonewline'; - }), - 1, - ); - }); - }); -}); From 077f08499ad206bdb32178722a01324baab1643f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 23 Aug 2018 13:01:16 -0400 Subject: [PATCH 0135/4053] Display the correct messaging when viewing a null patch --- lib/containers/file-patch-container.js | 12 --- lib/views/file-patch-view.js | 140 ++++++++++++++----------- menus/git.cson | 4 +- styles/file-patch-view.less | 11 +- test/views/file-patch-view.test.js | 14 +++ 5 files changed, 100 insertions(+), 81 deletions(-) diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index a5040a61db..61b9dfe3b2 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -46,10 +46,6 @@ export default class FilePatchContainer extends React.Component { return ; } - if (data.filePatch === null) { - return this.renderEmptyPatchMessage(); - } - return ( ); } - - renderEmptyPatchMessage() { - return ( -
-

No changes to display

-
- ); - } } diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 37ba4beb71..e444d2bd42 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -97,10 +97,14 @@ export default class FilePatchView extends React.Component { } render() { + const rootClass = cx( + 'github-FilePatchView', + `github-FilePatchView--${this.props.stagingStatus}`, + {'github-FilePatchView--blank': !this.props.filePatch.isPresent()}, + ); + return ( -
+
- - - - - - - - - {this.renderExecutableModeChangeMeta()} - {this.renderSymlinkChangeMeta()} - - - - - {this.renderHunkHeaders()} - - {this.renderLineDecorations( - this.state.selectedLineRanges, - 'github-FilePatchView-line--selected', - {gutter: true}, - )} - {this.renderLineDecorations( - this.props.filePatch.getAdditionRanges(), - 'github-FilePatchView-line--added', - {line: true}, - )} - {this.renderLineDecorations( - this.props.filePatch.getDeletionRanges(), - 'github-FilePatchView-line--deleted', - {line: true}, - )} - {this.renderLineDecorations( - this.props.filePatch.getNoNewlineRanges(), - 'github-FilePatchView-line--nonewline', - {line: true}, - )} - - + {this.props.filePatch.isPresent() ? this.renderNonEmptyPatch() : this.renderEmptyPatch()}
); } + renderEmptyPatch() { + return

No changes to display

; + } + + renderNonEmptyPatch() { + return ( + + + + + + + + + {this.renderExecutableModeChangeMeta()} + {this.renderSymlinkChangeMeta()} + + + + + {this.renderHunkHeaders()} + + {this.renderLineDecorations( + this.state.selectedLineRanges, + 'github-FilePatchView-line--selected', + {gutter: true}, + )} + {this.renderLineDecorations( + this.props.filePatch.getAdditionRanges(), + 'github-FilePatchView-line--added', + {line: true}, + )} + {this.renderLineDecorations( + this.props.filePatch.getDeletionRanges(), + 'github-FilePatchView-line--deleted', + {line: true}, + )} + {this.renderLineDecorations( + this.props.filePatch.getNoNewlineRanges(), + 'github-FilePatchView-line--nonewline', + {line: true}, + )} + + + ); + } + renderExecutableModeChangeMeta() { if (!this.props.filePatch.didChangeExecutableMode()) { return null; diff --git a/menus/git.cson b/menus/git.cson index fa126d32d1..689fe55a2c 100644 --- a/menus/git.cson +++ b/menus/git.cson @@ -44,13 +44,13 @@ 'command': 'github:open-file' } ] - '.github-FilePatchView.is-staged': [ + '.github-FilePatchView--staged': [ { 'label': 'Unstage Selection' 'command': 'core:confirm' } ] - '.github-FilePatchView.is-unstaged': [ + '.github-FilePatchView--unstaged': [ { 'label': 'Stage Selection' 'command': 'core:confirm' diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 4e4b4ff3e6..47eb743583 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -12,9 +12,14 @@ min-width: 0; height: 100%; - &.is-blank { - text-align: center; + &--blank &-container { + flex: 1; + display: flex; + align-items: center; justify-content: center; + text-align: center; + font-size: 1.2em; + padding: @component-padding; } &-header { @@ -49,7 +54,6 @@ height: 100%; flex-direction: column; - .is-blank, .large-file-patch { flex: 1; display: flex; @@ -70,7 +74,6 @@ } } - // Meta section &-meta { diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index a8fa414c74..b7d47f562a 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -6,6 +6,7 @@ import {shallow, mount} from 'enzyme'; import {cloneRepository, buildRepository} from '../helpers'; import FilePatchView from '../../lib/views/file-patch-view'; import FilePatchSelection from '../../lib/models/file-patch-selection'; +import {nullFilePatch} from '../../lib/models/patch/file-patch'; import {nullFile} from '../../lib/models/patch/file'; import Hunk from '../../lib/models/patch/hunk'; import {Addition, Deletion, NoNewline} from '../../lib/models/patch/region'; @@ -307,4 +308,17 @@ describe('FilePatchView', function() { ]); }); }); + + describe('when viewing an empty patch', function() { + it('renders an empty patch message', function() { + const wrapper = shallow(buildApp({filePatch: nullFilePatch})); + assert.isTrue(wrapper.find('.github-FilePatchView').hasClass('github-FilePatchView--blank')); + assert.isTrue(wrapper.find('.github-FilePatchView-message').exists()); + }); + + it('shows navigation controls', function() { + const wrapper = shallow(buildApp({filePatch: nullFilePatch})); + assert.isTrue(wrapper.find('FilePatchHeaderView').exists()); + }); + }); }); From c2943187d6cecc9c218759355375387b7dbe7061 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 23 Aug 2018 13:01:28 -0400 Subject: [PATCH 0136/4053] Turns out those methods are plural --- lib/controllers/file-patch-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 3b6741a218..c46d77a6b4 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -210,7 +210,7 @@ export default class FilePatchController extends React.Component { toggleFile() { return this.stagingOperation(() => { - const methodName = this.withStagingStatus({staged: 'unstageFile', unstaged: 'stageFile'}); + const methodName = this.withStagingStatus({staged: 'unstageFiles', unstaged: 'stageFiles'}); return this.props.repository[methodName]([this.props.relPath]); }); } From fa8259ee9d271e11af77ffa7d78e7a5b7e00ec7c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 24 Aug 2018 13:40:22 -0400 Subject: [PATCH 0137/4053] Allow AtomTextEditor to pass its parent a model reference via RefHolder --- lib/atom/atom-text-editor.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index fef2e5ca7b..803b0ca3b3 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import {CompositeDisposable} from 'event-kit'; import RefHolder from '../models/ref-holder'; +import {RefHolderPropType} from '../prop-types'; import {autobind, extractProps} from '../helpers'; const editorProps = { @@ -44,6 +45,7 @@ export default class AtomTextEditor extends React.PureComponent { didChange: PropTypes.func, didChangeCursorPosition: PropTypes.func, preserveMarkers: PropTypes.bool, + refModel: RefHolderPropType, children: PropTypes.node, } @@ -51,6 +53,7 @@ export default class AtomTextEditor extends React.PureComponent { text: '', didChange: () => {}, didChangeCursorPosition: () => {}, + refModel: new RefHolder(), } constructor(props, context) { @@ -61,16 +64,16 @@ export default class AtomTextEditor extends React.PureComponent { this.suppressChange = false; this.refElement = new RefHolder(); - this.refModel = new RefHolder(); + this.refModel = null; } render() { - this.refModel.map(() => this.quietlySetText(this.props.text)); + this.getRefModel().map(() => this.quietlySetText(this.props.text)); return ( - + {this.props.children} @@ -84,7 +87,7 @@ export default class AtomTextEditor extends React.PureComponent { const editor = element.getModel(); editor.setText(this.props.text, {bypassReadOnly: true}); - this.refModel.setter(editor); + this.getRefModel().setter(editor); this.subs.add( editor.onDidChange(this.didChange), @@ -150,4 +153,16 @@ export default class AtomTextEditor extends React.PureComponent { getModel() { return this.refElement.map(e => e.getModel()).getOr(null); } + + getRefModel() { + if (this.props.refModel) { + return this.props.refModel; + } + + if (!this.refModel) { + this.refModel = new RefHolder(); + } + + return this.refModel; + } } From b0af3a65a7083e3de4af0c01cf1a4b8605e7ea9d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 24 Aug 2018 13:40:36 -0400 Subject: [PATCH 0138/4053] Accessor for an IndexRowRange's end row --- lib/models/indexed-row-range.js | 4 ++++ test/models/indexed-row-range.test.js | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js index aa080338a0..1dc48345de 100644 --- a/lib/models/indexed-row-range.js +++ b/lib/models/indexed-row-range.js @@ -14,6 +14,10 @@ export default class IndexedRowRange { return this.bufferRange.start.row; } + getEndBufferRow() { + return this.bufferRange.end.row; + } + getBufferRows() { return this.bufferRange.getRows(); } diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index 99730df4c9..8985db788b 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -20,6 +20,15 @@ describe('IndexedRowRange', function() { assert.strictEqual(range.getStartBufferRow(), 2); }); + it('returns its ending buffer row', function() { + const range = new IndexedRowRange({ + bufferRange: [[2, 0], [8, 0]], + startOffset: 0, + endOffset: 10, + }); + assert.strictEqual(range.getEndBufferRow(), 8); + }); + it('returns an array of the covered rows', function() { const range = new IndexedRowRange({ bufferRange: [[2, 0], [8, 0]], From 4fc2b17dab452336765e9072278a919fd425efb0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 24 Aug 2018 13:40:49 -0400 Subject: [PATCH 0139/4053] Accessor for a Region's end row --- lib/models/patch/region.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index ce021b1185..233a7136cf 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -11,6 +11,10 @@ class Region { return this.range.getStartBufferRow(); } + getEndBufferRow() { + return this.range.getEndBufferRow(); + } + includesBufferRow(row) { return this.range.includesRow(row); } From c127d244bb0d1d946f77b11c0239c218f38502e3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 24 Aug 2018 13:41:05 -0400 Subject: [PATCH 0140/4053] Register and dispatch Atom commands --- lib/views/file-patch-view.js | 90 +++++++++++++++++++- test/views/file-patch-view.test.js | 131 +++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+), 4 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index e444d2bd42..19784d31e7 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -9,6 +9,7 @@ import Marker from '../atom/marker'; import MarkerLayer from '../atom/marker-layer'; import Decoration from '../atom/decoration'; import Gutter from '../atom/gutter'; +import Commands, {Command} from '../atom/commands'; import FilePatchHeaderView from './file-patch-header-view'; import FilePatchMetaView from './file-patch-meta-view'; import HunkHeaderView from './hunk-header-view'; @@ -30,6 +31,7 @@ export default class FilePatchView extends React.Component { selection: PropTypes.object.isRequired, repository: PropTypes.object.isRequired, + commands: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, mouseDownOnHeader: PropTypes.func.isRequired, @@ -47,13 +49,16 @@ export default class FilePatchView extends React.Component { undoLastDiscard: PropTypes.func.isRequired, discardLines: PropTypes.func.isRequired, selectAndDiscardHunk: PropTypes.func.isRequired, + selectNextHunk: PropTypes.func.isRequired, + selectPreviousHunk: PropTypes.func.isRequired, + togglePatchSelectionMode: PropTypes.func.isRequired, } constructor(props) { super(props); autobind( this, - 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', + 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', 'didConfirm', 'didOpenFile', 'oldLineNumberLabel', 'newLineNumberLabel', ); @@ -70,6 +75,8 @@ export default class FilePatchView extends React.Component { this.lastMouseMoveLine = null; this.hunksByMarkerID = new Map(); this.hunkMarkerLayerHolder = new RefHolder(); + this.refRoot = new RefHolder(); + this.refEditor = new RefHolder(); } static getDerivedStateFromProps(props, state) { @@ -104,7 +111,9 @@ export default class FilePatchView extends React.Component { ); return ( -
+
+ + {this.renderCommands()} @@ -129,6 +138,18 @@ export default class FilePatchView extends React.Component { ); } + renderCommands() { + return ( + + + + + + + + ); + } + renderEmptyPatch() { return

No changes to display

; } @@ -141,7 +162,8 @@ export default class FilePatchView extends React.Component { lineNumberGutterVisible={false} autoWidth={false} autoHeight={false} - readOnly={true}> + readOnly={true} + refModel={this.refEditor}> { + const placedRows = new Set(); + + for (const cursor of editor.getCursors()) { + const cursorRow = cursor.getBufferPosition().row; + const hunk = this.getHunkAt(cursorRow); + /* istanbul ignore next */ + if (!hunk) { + continue; + } + + let newRow = hunk.getNewRowAt(cursorRow); + let newColumn = cursor.getBufferPosition().column; + if (newRow === null) { + let nearestRow = hunk.getNewStartRow() - 1; + for (const region of hunk.getRegions()) { + if (!region.includesBufferRow(cursorRow)) { + region.when({ + unchanged: () => { + nearestRow += region.bufferRowCount(); + }, + addition: () => { + nearestRow += region.bufferRowCount(); + }, + }); + } else { + break; + } + } + + if (!placedRows.has(nearestRow)) { + newRow = nearestRow; + newColumn = 0; + placedRows.add(nearestRow); + } + } + + if (newRow !== null) { + cursors.push([newRow, newColumn]); + } + } + + return null; + }); + + this.props.openFile(cursors); + } + oldLineNumberLabel({bufferRow, softWrapped}) { const hunk = this.getHunkAt(bufferRow); if (hunk === undefined) { diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index b7d47f562a..6283fe7a5d 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -39,6 +39,7 @@ describe('FilePatchView', function() { selection: new FilePatchSelection(filePatch.getHunks()), repository, + commands: atomEnv.commands, tooltips: atomEnv.tooltips, mouseDownOnHeader: () => {}, @@ -56,6 +57,9 @@ describe('FilePatchView', function() { undoLastDiscard: () => {}, discardLines: () => {}, selectAndDiscardHunk: () => {}, + selectNextHunk: () => {}, + selectPreviousHunk: () => {}, + togglePatchSelectionMode: () => {}, ...overrideProps, }; @@ -321,4 +325,131 @@ describe('FilePatchView', function() { assert.isTrue(wrapper.find('FilePatchHeaderView').exists()); }); }); + + describe('registers Atom commands', function() { + it('toggles the patch selection mode', function() { + const togglePatchSelectionMode = sinon.spy(); + const wrapper = mount(buildApp({togglePatchSelectionMode})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:toggle-patch-selection-mode'); + + assert.isTrue(togglePatchSelectionMode.called); + }); + + it('toggles the current selection', function() { + const toggleLines = sinon.spy(); + const wrapper = mount(buildApp({toggleLines})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'core:confirm'); + + assert.isTrue(toggleLines.called); + }); + + it('selects the next hunk', function() { + const selectNextHunk = sinon.spy(); + const wrapper = mount(buildApp({selectNextHunk})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:select-next-hunk'); + + assert.isTrue(selectNextHunk.called); + }); + + it('selects the previous hunk', function() { + const selectPreviousHunk = sinon.spy(); + const wrapper = mount(buildApp({selectPreviousHunk})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:select-previous-hunk'); + + assert.isTrue(selectPreviousHunk.called); + }); + + describe('opening the file', function() { + let fp; + + beforeEach(function() { + const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n'; + const hunks = [ + new Hunk({ + oldStartRow: 2, oldRowCount: 2, newStartRow: 2, newRowCount: 3, + sectionHeading: 'first hunk', + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + changes: [ + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + ], + }), + new Hunk({ + oldStartRow: 10, oldRowCount: 6, newStartRow: 11, newRowCount: 3, + sectionHeading: 'second hunk', + rowRange: new IndexedRowRange({bufferRange: [[3, 0], [8, 0]], startOffset: 15, endOffset: 45}), + changes: [ + new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 20, endOffset: 25})), + new Deletion(new IndexedRowRange({bufferRange: [[6, 0], [7, 0]], startOffset: 30, endOffset: 40})), + ], + }), + ]; + fp = filePatch.clone({ + patch: filePatch.getPatch().clone({hunks, bufferText}), + }); + }); + + it('opens the file at the current unchanged row', function() { + const openFile = sinon.spy(); + const wrapper = mount(buildApp({filePatch: fp, openFile})); + + const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); + editor.setCursorBufferPosition([3, 2]); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); + + assert.isTrue(openFile.calledWith([[11, 2]])); + }); + + it('opens the file at a current added row', function() { + const openFile = sinon.spy(); + const wrapper = mount(buildApp({filePatch: fp, openFile})); + + const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); + editor.setCursorBufferPosition([1, 3]); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); + + assert.isTrue(openFile.calledWith([[3, 3]])); + }); + + it('opens the file at the beginning of the previous added or unchanged row', function() { + const openFile = sinon.spy(); + const wrapper = mount(buildApp({filePatch: fp, openFile})); + + const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); + editor.setCursorBufferPosition([4, 2]); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); + + assert.isTrue(openFile.calledWith([[11, 0]])); + }); + + it('preserves multiple cursors', function() { + const openFile = sinon.spy(); + const wrapper = mount(buildApp({filePatch: fp, openFile})); + + const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); + editor.setCursorBufferPosition([3, 2]); + editor.addCursorAtBufferPosition([4, 2]); + editor.addCursorAtBufferPosition([1, 3]); + editor.addCursorAtBufferPosition([6, 2]); + editor.addCursorAtBufferPosition([7, 1]); + + // The cursors at [6, 2] and [7, 1] are both collapsed to a single one on the unchanged line. + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); + + assert.isTrue(openFile.calledWith([ + [11, 2], + [11, 0], + [3, 3], + [12, 0], + ])); + }); + }); + }); }); From d7f62e5352b8a5a78260e432c2822f97806308f7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 24 Aug 2018 15:08:23 -0400 Subject: [PATCH 0141/4053] Raise the minimum required Atom version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a4966cfd98..d0deb4478c 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "relay": "relay-compiler --src ./lib --schema graphql/schema.graphql" }, "engines": { - "atom": ">=1.18.0" + "atom": ">=1.31.0" }, "atomTestRunner": "./test/runner", "atomTranspilers": [ From 37e9b7c150b0031f25921b66073240a83978b426 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 24 Aug 2018 15:21:37 -0400 Subject: [PATCH 0142/4053] No default RefHolder --- lib/atom/atom-text-editor.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index 803b0ca3b3..38a76548e2 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -53,7 +53,6 @@ export default class AtomTextEditor extends React.PureComponent { text: '', didChange: () => {}, didChangeCursorPosition: () => {}, - refModel: new RefHolder(), } constructor(props, context) { From e4f8a2a76b542ba82bbed75bb09191e317aef5d2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 24 Aug 2018 15:22:02 -0400 Subject: [PATCH 0143/4053] Drill the Atom CommandRegistry down to the FilePatchView --- lib/containers/file-patch-container.js | 1 + lib/controllers/file-patch-controller.js | 1 + lib/controllers/root-controller.js | 1 + lib/items/file-patch-item.js | 1 + test/controllers/file-patch-controller.test.js | 1 + test/items/file-patch-item.test.js | 1 + 6 files changed, 6 insertions(+) diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index 61b9dfe3b2..3e34aa292a 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -14,6 +14,7 @@ export default class FilePatchContainer extends React.Component { relPath: PropTypes.string.isRequired, workspace: PropTypes.object.isRequired, + commands: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index c46d77a6b4..b3402b3d31 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -16,6 +16,7 @@ export default class FilePatchController extends React.Component { filePatch: PropTypes.object.isRequired, workspace: PropTypes.object.isRequired, + commands: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index bd61553dfe..b76b27a013 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -314,6 +314,7 @@ export default class RootController extends React.Component { stagingStatus={params.stagingStatus} tooltips={this.props.tooltips} + commands={this.props.commandRegistry} workspace={this.props.workspace} discardLines={this.discardLines} diff --git a/lib/items/file-patch-item.js b/lib/items/file-patch-item.js index d7d735500a..db087e81f0 100644 --- a/lib/items/file-patch-item.js +++ b/lib/items/file-patch-item.js @@ -15,6 +15,7 @@ export default class FilePatchItem extends React.Component { stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), workspace: PropTypes.object.isRequired, + commands: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, discardLines: PropTypes.func.isRequired, diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index 03410e9bdc..0e5b358c45 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -33,6 +33,7 @@ describe('FilePatchController', function() { isPartiallyStaged: false, filePatch, workspace: atomEnv.workspace, + commands: atomEnv.commands, tooltips: atomEnv.tooltips, destroy: () => {}, discardLines: () => {}, diff --git a/test/items/file-patch-item.test.js b/test/items/file-patch-item.test.js index 57960ca33b..84cbe3429b 100644 --- a/test/items/file-patch-item.test.js +++ b/test/items/file-patch-item.test.js @@ -30,6 +30,7 @@ describe('FilePatchItem', function() { const props = { workdirContextPool: pool, workspace: atomEnv.workspace, + commands: atomEnv.commands, tooltips: atomEnv.tooltips, discardLines: () => {}, undoLastDiscard: () => {}, From 572d3f5f83a40f625a5249adea6451313d54ddb0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 24 Aug 2018 15:35:48 -0400 Subject: [PATCH 0144/4053] Stub controller methods --- lib/controllers/file-patch-controller.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index b3402b3d31..cbc62c7299 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -32,6 +32,7 @@ export default class FilePatchController extends React.Component { 'undoLastDiscard', 'diveIntoMirrorPatch', 'openFile', 'toggleFile', 'selectAndToggleHunk', 'toggleLines', 'toggleModeChange', 'toggleSymlinkChange', 'discardLines', 'selectAndDiscardHunk', + 'selectNextHunk', 'selectPreviousHunk', 'togglePatchSelectionMode', ); this.state = { @@ -89,6 +90,9 @@ export default class FilePatchController extends React.Component { undoLastDiscard={this.undoLastDiscard} discardLines={this.discardLines} selectAndDiscardHunk={this.selectAndDiscardHunk} + selectNextHunk={this.selectNextHunk} + selectPreviousHunk={this.selectPreviousHunk} + togglePatchSelectionMode={this.togglePatchSelectionMode} /> ); } @@ -285,6 +289,18 @@ export default class FilePatchController extends React.Component { }); } + selectNextHunk() { + // + } + + selectPreviousHunk() { + // + } + + togglePatchSelectionMode() { + // + } + withStagingStatus(callbacks) { const callback = callbacks[this.props.stagingStatus]; if (!callback) { From b6c3819cdd7bf095d933911ffa56a53f758f77a2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 24 Aug 2018 15:35:59 -0400 Subject: [PATCH 0145/4053] Pass a CommandRegistry in FilePatchContainer tests --- test/containers/file-patch-container.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/containers/file-patch-container.test.js b/test/containers/file-patch-container.test.js index 0fcaee7717..0bec38dcef 100644 --- a/test/containers/file-patch-container.test.js +++ b/test/containers/file-patch-container.test.js @@ -35,6 +35,7 @@ describe('FilePatchContainer', function() { stagingStatus: 'unstaged', relPath: 'a.txt', workspace: atomEnv.workspace, + commands: atomEnv.commands, tooltips: atomEnv.tooltips, discardLines: () => {}, undoLastDiscard: () => {}, From 329f18011bdf0460d785a37558e08488bf1fa9d7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 27 Aug 2018 09:23:09 -0400 Subject: [PATCH 0146/4053] Hunk navigation --- lib/controllers/file-patch-controller.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index cbc62c7299..018157b4bf 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -290,15 +290,21 @@ export default class FilePatchController extends React.Component { } selectNextHunk() { - // + return new Promise(resolve => { + this.setState(prevState => ({selection: prevState.selection.selectNextHunk()}), resolve); + }); } selectPreviousHunk() { - // + return new Promise(resolve => { + this.setState(prevState => ({selection: prevState.selection.selectPreviousHunk()}), resolve); + }); } togglePatchSelectionMode() { - // + return new Promise(resolve => { + this.setState(prevState => ({selection: prevState.selection.toggleMode()}), resolve); + }); } withStagingStatus(callbacks) { From 9a5b5d06334715c2a17ca197e93066971879aec1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 27 Aug 2018 09:23:22 -0400 Subject: [PATCH 0147/4053] Fuss with file patch keybindings --- keymaps/git.cson | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/keymaps/git.cson b/keymaps/git.cson index 83decf1cc0..8e1f70dedb 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -36,14 +36,14 @@ 'ctrl-enter': 'github:commit' 'shift-tab': 'core:focus-previous' -'.github-FilePatchView': - '/': 'github:toggle-patch-selection-mode' - 'tab': 'github:select-next-hunk' - 'shift-tab': 'github:select-previous-hunk' - 'right': 'core:move-right' - 'o': 'github:open-file' +'.github-FilePatchView atom-text-editor:not([mini])': + 'cmd-/': 'github:toggle-patch-selection-mode' 'cmd-backspace': 'github:discard-selected-lines' - 'ctrl-backspace': 'github:discard-selected-lines' + 'cmd-enter': 'core:confirm' + 'cmd-right': 'github:surface-file' + 'cmd-o': 'github:open-file' + 'alt-tab': 'github:select-next-hunk' + 'alt-shift-tab': 'github:select-previous-hunk' '.github-Prompt-input': 'enter': 'core:confirm' From e09d6f8d28d2549c5d9f2f972887f1f04cdb07a6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 27 Aug 2018 13:09:20 -0400 Subject: [PATCH 0148/4053] Track selection changes within the patch view TextEditor --- lib/atom/atom-text-editor.js | 10 +++++++++- lib/views/file-patch-view.js | 20 +++++++++++++++++++- test/views/file-patch-view.test.js | 15 ++++++++++++++- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index 38a76548e2..ec2c5b3980 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -44,6 +44,7 @@ export default class AtomTextEditor extends React.PureComponent { text: PropTypes.string, didChange: PropTypes.func, didChangeCursorPosition: PropTypes.func, + didChangeSelections: PropTypes.func, preserveMarkers: PropTypes.bool, refModel: RefHolderPropType, children: PropTypes.node, @@ -53,11 +54,12 @@ export default class AtomTextEditor extends React.PureComponent { text: '', didChange: () => {}, didChangeCursorPosition: () => {}, + didChangeSelections: () => {}, } constructor(props, context) { super(props, context); - autobind(this, 'didChange', 'didChangeCursorPosition'); + autobind(this, 'didChange', 'didChangeCursorPosition', 'didChangeSelections'); this.subs = new CompositeDisposable(); this.suppressChange = false; @@ -91,6 +93,8 @@ export default class AtomTextEditor extends React.PureComponent { this.subs.add( editor.onDidChange(this.didChange), editor.onDidChangeCursorPosition(this.didChangeCursorPosition), + editor.onDidAddSelection(this.didChangeSelections), + editor.onDidRemoveSelection(this.didChangeSelections), ); // shhh, eslint. shhhh @@ -141,6 +145,10 @@ export default class AtomTextEditor extends React.PureComponent { this.props.didChangeCursorPosition(this.getModel()); } + didChangeSelections() { + this.props.didChangeSelections(this.getModel().getSelections()); + } + contains(element) { return this.refElement.map(e => e.contains(element)).getOr(false); } diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 19784d31e7..892bdf65f1 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -38,6 +38,7 @@ export default class FilePatchView extends React.Component { mouseDownOnLineNumber: PropTypes.func.isRequired, mouseMoveOnLineNumber: PropTypes.func.isRequired, mouseUp: PropTypes.func.isRequired, + selectedRowsChanged: PropTypes.func.isRequired, diveIntoMirrorPatch: PropTypes.func.isRequired, openFile: PropTypes.func.isRequired, @@ -59,7 +60,7 @@ export default class FilePatchView extends React.Component { autobind( this, 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', 'didConfirm', 'didOpenFile', - 'oldLineNumberLabel', 'newLineNumberLabel', + 'didChangeSelections', 'oldLineNumberLabel', 'newLineNumberLabel', ); this.state = { @@ -163,6 +164,7 @@ export default class FilePatchView extends React.Component { autoWidth={false} autoHeight={false} readOnly={true} + didChangeSelections={this.didChangeSelections} refModel={this.refEditor}> { + + this.props.selectedRowsChanged( + editor.getSelectedBufferRanges().reduce((acc, range) => { + for (const row of range.getRows()) { + acc.add(row); + } + return acc; + }, new Set()), + ); + + return null; + }); + } + oldLineNumberLabel({bufferRow, softWrapped}) { const hunk = this.getHunkAt(bufferRow); if (hunk === undefined) { diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 6283fe7a5d..dcfb81a89e 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -22,7 +22,7 @@ describe('FilePatchView', function() { repository = await buildRepository(workdirPath); // a.txt: unstaged changes - await fs.writeFile(path.join(workdirPath, 'a.txt'), 'changed\n'); + await fs.writeFile(path.join(workdirPath, 'a.txt'), 'zero\none\ntwo\nthree\nfour\n'); filePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); }); @@ -46,6 +46,7 @@ describe('FilePatchView', function() { mouseDownOnLineNumber: () => {}, mouseMoveOnLineNumber: () => {}, mouseUp: () => {}, + selectedRowsChanged: () => {}, diveIntoMirrorPatch: () => {}, openFile: () => {}, @@ -313,6 +314,18 @@ describe('FilePatchView', function() { }); }); + it('notifies a callback when the editor selection changes', function() { + const selectedRowsChanged = sinon.spy(); + const wrapper = mount(buildApp({selectedRowsChanged})); + const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); + + assert.isFalse(selectedRowsChanged.called); + + editor.addSelectionForBufferRange([[3, 1], [4, 0]]); + + assert.isTrue(selectedRowsChanged.calledWith(new Set([3, 4]))); + }); + describe('when viewing an empty patch', function() { it('renders an empty patch message', function() { const wrapper = shallow(buildApp({filePatch: nullFilePatch})); From c4f8449d622403a555484d52bc207b72fa23288b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 29 Aug 2018 14:21:03 -0400 Subject: [PATCH 0149/4053] Back file patch selections with editor selection --- lib/atom/atom-text-editor.js | 13 +- lib/controllers/file-patch-controller.js | 173 +++-------------- lib/helpers.js | 18 ++ lib/views/file-patch-view.js | 235 +++++++++++++++-------- 4 files changed, 211 insertions(+), 228 deletions(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index ec2c5b3980..d6ec6883bf 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -44,7 +44,7 @@ export default class AtomTextEditor extends React.PureComponent { text: PropTypes.string, didChange: PropTypes.func, didChangeCursorPosition: PropTypes.func, - didChangeSelections: PropTypes.func, + observeSelections: PropTypes.func, preserveMarkers: PropTypes.bool, refModel: RefHolderPropType, children: PropTypes.node, @@ -54,12 +54,12 @@ export default class AtomTextEditor extends React.PureComponent { text: '', didChange: () => {}, didChangeCursorPosition: () => {}, - didChangeSelections: () => {}, + observeSelections: () => {}, } constructor(props, context) { super(props, context); - autobind(this, 'didChange', 'didChangeCursorPosition', 'didChangeSelections'); + autobind(this, 'didChange', 'didChangeCursorPosition'); this.subs = new CompositeDisposable(); this.suppressChange = false; @@ -93,8 +93,7 @@ export default class AtomTextEditor extends React.PureComponent { this.subs.add( editor.onDidChange(this.didChange), editor.onDidChangeCursorPosition(this.didChangeCursorPosition), - editor.onDidAddSelection(this.didChangeSelections), - editor.onDidRemoveSelection(this.didChangeSelections), + editor.observeSelections(this.props.observeSelections), ); // shhh, eslint. shhhh @@ -145,10 +144,6 @@ export default class AtomTextEditor extends React.PureComponent { this.props.didChangeCursorPosition(this.getModel()); } - didChangeSelections() { - this.props.didChangeSelections(this.getModel().getSelections()); - } - contains(element) { return this.refElement.map(e => e.contains(element)).getOr(false); } diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 018157b4bf..46f2eb79a8 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -1,10 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Point} from 'atom'; import path from 'path'; -import {autobind} from '../helpers'; -import FilePatchSelection from '../models/file-patch-selection'; +import {autobind, equalSets} from '../helpers'; import FilePatchItem from '../items/file-patch-item'; import FilePatchView from '../views/file-patch-view'; @@ -28,16 +26,15 @@ export default class FilePatchController extends React.Component { super(props); autobind( this, - 'mouseDownOnHeader', 'mouseDownOnLineNumber', 'mouseMoveOnLineNumber', 'mouseUp', + 'selectedRangesChanged', 'undoLastDiscard', 'diveIntoMirrorPatch', 'openFile', - 'toggleFile', 'selectAndToggleHunk', 'toggleLines', 'toggleModeChange', 'toggleSymlinkChange', - 'discardLines', 'selectAndDiscardHunk', - 'selectNextHunk', 'selectPreviousHunk', 'togglePatchSelectionMode', + 'toggleFile', 'toggleHunk', 'toggleLines', 'toggleModeChange', 'toggleSymlinkChange', + 'discardLines', 'discardHunk', ); this.state = { lastFilePatch: this.props.filePatch, - selection: new FilePatchSelection(this.props.filePatch.getHunks()), + selectedRows: new Set(), }; this.mouseSelectionInProgress = false; @@ -51,7 +48,7 @@ export default class FilePatchController extends React.Component { static getDerivedStateFromProps(props, state) { if (props.filePatch !== state.lastFilePatch) { return { - selection: state.selection.updateHunks(props.filePatch.getHunks()), + selectedRows: new Set(), // FIXME lastFilePatch: props.filePatch, }; } @@ -73,125 +70,25 @@ export default class FilePatchController extends React.Component { ); } - mouseDownOnHeader(event, hunk) { - if (event.button !== 0) { return; } - const windows = process.platform === 'win32'; - if (event.ctrlKey && !windows) { return; } // simply open context menu - - this.mouseSelectionInProgress = true; - event.persist && event.persist(); - - this.setState(prevState => { - let selection = prevState.selection; - if (event.metaKey || (event.ctrlKey && windows)) { - if (selection.getMode() === 'hunk') { - selection = selection.addOrSubtractHunkSelection(hunk); - } else { - // TODO: optimize - selection = hunk.getLines().reduce( - (current, line) => current.addOrSubtractLineSelection(line).coalesce(), - selection, - ); - } - } else if (event.shiftKey) { - if (selection.getMode() === 'hunk') { - selection = selection.selectHunk(hunk, true); - } else { - const hunkLines = hunk.getLines(); - const tailIndex = selection.getLineSelectionTailIndex(); - const selectedHunkAfterTail = tailIndex < hunkLines[0].diffLineNumber; - if (selectedHunkAfterTail) { - selection = selection.selectLine(hunkLines[hunkLines.length - 1], true); - } else { - selection = selection.selectLine(hunkLines[0], true); - } - } - } else { - selection = selection.selectHunk(hunk, false); - } - - return {selection}; - }); - } - - mouseDownOnLineNumber(event, hunk, line) { - if (event.button !== 0) { return; } - const windows = process.platform === 'win32'; - if (event.ctrlKey && !windows) { return; } // simply open context menu - - this.mouseSelectionInProgress = true; - event.persist && event.persist(); - - this.setState(prevState => { - let selection = prevState.selection; - - if (event.metaKey || (event.ctrlKey && windows)) { - if (selection.getMode() === 'hunk') { - selection = selection.addOrSubtractHunkSelection(hunk); - } else { - selection = selection.addOrSubtractLineSelection(line); - } - } else if (event.shiftKey) { - if (selection.getMode() === 'hunk') { - selection = selection.selectHunk(hunk, true); - } else { - selection = selection.selectLine(line, true); - } - } else if (event.detail === 1) { - selection = selection.selectLine(line, false); - } else if (event.detail === 2) { - selection = selection.selectHunk(hunk, false); - } - - return {selection}; - }); - } - - mouseMoveOnLineNumber(event, hunk, line) { - if (!this.mouseSelectionInProgress) { return; } - - this.setState(prevState => { - let selection = null; - if (prevState.selection.getMode() === 'hunk') { - selection = prevState.selection.selectHunk(hunk, true); - } else { - selection = prevState.selection.selectLine(line, true); - } - return {selection}; - }); - } - - mouseUp() { - this.mouseSelectionInProgress = false; - this.setState(prevState => { - return {selection: prevState.selection.coalesce()}; - }); - } - undoLastDiscard() { return this.props.undoLastDiscard(this.props.relPath, this.props.repository); } @@ -205,12 +102,16 @@ export default class FilePatchController extends React.Component { await this.props.workspace.open(uri); } - async openFile(lineNumber = null) { + async openFile(positions) { const absolutePath = path.join(this.props.repository.getWorkingDirectoryPath(), this.props.relPath); const editor = await this.props.workspace.open(absolutePath, {pending: true}); - const position = new Point(lineNumber ? lineNumber - 1 : 0, 0); - editor.scrollToBufferPosition(position, {center: true}); - editor.setCursorBufferPosition(position); + if (positions.length > 0) { + editor.setCursorBufferPosition(positions[0], {autoscroll: false}); + for (const position of positions.slice(1)) { + editor.addCursorAtBufferPosition(position); + } + editor.scrollToBufferPosition(positions[positions.length - 1], {center: true}); + } } toggleFile() { @@ -220,9 +121,7 @@ export default class FilePatchController extends React.Component { }); } - async selectAndToggleHunk(hunk) { - await this.selectHunk(hunk); - + toggleHunk(hunk) { return this.stagingOperation(() => { const patch = this.withStagingStatus({ staged: () => this.props.filePatch.getUnstagePatchForHunk(hunk), @@ -278,33 +177,23 @@ export default class FilePatchController extends React.Component { return this.props.discardLines(this.props.filePatch, lines, this.props.repository); } - async selectAndDiscardHunk(hunk) { - await this.selectHunk(hunk); + discardHunk(hunk) { return this.discardLines(hunk.getLines()); } - selectHunk(hunk) { - return new Promise(resolve => { - this.setState(prevState => ({selection: prevState.selection.selectHunk(hunk)}), resolve); - }); - } - - selectNextHunk() { - return new Promise(resolve => { - this.setState(prevState => ({selection: prevState.selection.selectNextHunk()}), resolve); - }); - } + selectedRangesChanged(ranges) { + const newSelectedRows = new Set(); + for (const range of ranges) { + for (let row = range.start.row; row <= range.end.row; row++) { + newSelectedRows.add(row); + } + } - selectPreviousHunk() { - return new Promise(resolve => { - this.setState(prevState => ({selection: prevState.selection.selectPreviousHunk()}), resolve); - }); - } + if (equalSets(this.state.selectedRows, newSelectedRows)) { + return; + } - togglePatchSelectionMode() { - return new Promise(resolve => { - this.setState(prevState => ({selection: prevState.selection.toggleMode()}), resolve); - }); + this.setState({selectedRows: newSelectedRows}); } withStagingStatus(callbacks) { diff --git a/lib/helpers.js b/lib/helpers.js index c154322fee..a77cbfb169 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -410,6 +410,8 @@ export function extractCoAuthorsAndRawCommitMessage(commitMessage) { return {message: messageLines.join('\n'), coAuthors}; } +// Atom API pane item manipulation + export function createItem(node, componentHolder = null, uri = null, extra = {}) { const holder = componentHolder || new RefHolder(); @@ -455,3 +457,19 @@ export function createItem(node, componentHolder = null, uri = null, extra = {}) return override; } } + +// Set functions + +export function equalSets(left, right) { + if (left.size !== right.size) { + return false; + } + + for (const each of left) { + if (!right.has(each)) { + return false; + } + } + + return true; +} diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 892bdf65f1..3a899fef94 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -2,6 +2,7 @@ import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import {Range} from 'atom'; +import {CompositeDisposable} from 'event-kit'; import {autobind} from '../helpers'; import AtomTextEditor from '../atom/atom-text-editor'; @@ -28,72 +29,44 @@ export default class FilePatchView extends React.Component { stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, isPartiallyStaged: PropTypes.bool.isRequired, filePatch: PropTypes.object.isRequired, - selection: PropTypes.object.isRequired, + selectedRows: PropTypes.object.isRequired, repository: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, - mouseDownOnHeader: PropTypes.func.isRequired, - mouseDownOnLineNumber: PropTypes.func.isRequired, - mouseMoveOnLineNumber: PropTypes.func.isRequired, - mouseUp: PropTypes.func.isRequired, - selectedRowsChanged: PropTypes.func.isRequired, + selectedRangesChanged: PropTypes.func.isRequired, diveIntoMirrorPatch: PropTypes.func.isRequired, openFile: PropTypes.func.isRequired, toggleFile: PropTypes.func.isRequired, - selectAndToggleHunk: PropTypes.func.isRequired, + toggleHunk: PropTypes.func.isRequired, toggleLines: PropTypes.func.isRequired, toggleModeChange: PropTypes.func.isRequired, toggleSymlinkChange: PropTypes.func.isRequired, undoLastDiscard: PropTypes.func.isRequired, discardLines: PropTypes.func.isRequired, - selectAndDiscardHunk: PropTypes.func.isRequired, - selectNextHunk: PropTypes.func.isRequired, - selectPreviousHunk: PropTypes.func.isRequired, - togglePatchSelectionMode: PropTypes.func.isRequired, + discardHunk: PropTypes.func.isRequired, } constructor(props) { super(props); autobind( this, - 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', 'didConfirm', 'didOpenFile', - 'didChangeSelections', 'oldLineNumberLabel', 'newLineNumberLabel', + 'didMouseDownOnHeader', 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', + 'didChangeSelectedRanges', + 'didConfirm', 'didOpenFile', 'observeSelections', 'oldLineNumberLabel', 'newLineNumberLabel', + 'selectNextHunk', 'selectPreviousHunk', ); - this.state = { - lastSelection: this.props.selection, - selectedHunks: this.props.selection.getSelectedHunks(), - selectedLines: this.props.selection.getSelectedLines(), - selectedLineRanges: Array.from( - this.props.selection.getSelectedLines(), - line => Range.fromObject([[line, 0], [line, 0]]), - ), - }; - + this.mouseSelectionInProgress = false; this.lastMouseMoveLine = null; this.hunksByMarkerID = new Map(); this.hunkMarkerLayerHolder = new RefHolder(); this.refRoot = new RefHolder(); this.refEditor = new RefHolder(); - } - - static getDerivedStateFromProps(props, state) { - const nextState = {}; - - if (props.selection !== state.lastSelection) { - nextState.lastSelection = props.selection; - nextState.selectedHunks = props.selection.getSelectedHunks(); - nextState.selectedLines = props.selection.getSelectedLines(); - nextState.selectedLineRanges = Array.from( - props.selection.getSelectedLines(), - line => Range.fromObject([[line, 0], [line, 0]]), - ); - } - return nextState; + this.subs = new CompositeDisposable(); } componentDidMount() { @@ -102,6 +75,7 @@ export default class FilePatchView extends React.Component { componentWillUnmount() { window.removeEventListener('mouseup', this.didMouseUp); + this.subs.dispose(); } render() { @@ -142,10 +116,9 @@ export default class FilePatchView extends React.Component { renderCommands() { return ( - - - + + ); @@ -164,7 +137,7 @@ export default class FilePatchView extends React.Component { autoWidth={false} autoHeight={false} readOnly={true} - didChangeSelections={this.didChangeSelections} + observeSelections={this.observeSelections} refModel={this.refEditor}> Range.fromObject([[row, 0], [row, 0]])), 'github-FilePatchView-line--selected', {gutter: true}, )} @@ -339,8 +312,6 @@ export default class FilePatchView extends React.Component { } renderHunkHeaders() { - const selectedHunks = this.state.selectedHunks; - const isHunkSelectionMode = this.props.selection.getMode() === 'hunk'; const toggleVerb = this.props.stagingStatus === 'unstaged' ? 'Stage' : 'Unstage'; return ( @@ -366,11 +337,8 @@ export default class FilePatchView extends React.Component { */} {this.props.filePatch.getHunks().map((hunk, index) => { - const isSelected = selectedHunks.has(hunk); - let buttonSuffix = (isHunkSelectionMode || !isSelected) ? ' Hunk' : ' Selection'; - if (isSelected && selectedHunks.size > 1) { - buttonSuffix += 's'; - } + const buttonSuffix = ' Selected Line'; + // FIXME Add "s" when plural const toggleSelectionLabel = `${toggleVerb}${buttonSuffix}`; const discardSelectionLabel = `Discard${buttonSuffix}`; @@ -382,9 +350,9 @@ export default class FilePatchView extends React.Component { this.toggleSelection(hunk)} discardSelection={() => this.discardSelection(hunk)} - mouseDown={this.props.mouseDownOnHeader} + mouseDown={this.didMouseDownOnHeader} /> @@ -441,33 +409,131 @@ export default class FilePatchView extends React.Component { ); } + didMouseDownOnHeader(event, hunk) { + this.handleSelectionEvent(event.domEvent, hunk.getBufferRange()); + } + didMouseDownOnLineNumber(event) { const line = event.bufferRow; - const hunk = this.getHunkAt(event.bufferRow); - if (hunk === undefined) { + if (line === undefined || isNaN(line)) { return; } - this.props.mouseDownOnLineNumber(event.domEvent, hunk, line); + if (this.handleSelectionEvent(event.domEvent, [[line, 0], [line, Infinity]])) { + this.mouseSelectionInProgress = true; + } } didMouseMoveOnLineNumber(event) { - const line = event.bufferRow; - if (this.lastMouseMoveLine === line || line === undefined) { + if (!this.mouseSelectionInProgress) { return; } - this.lastMouseMoveLine = line; - const hunk = this.getHunkAt(event.bufferRow); - if (hunk === undefined) { + const line = event.bufferRow; + if (this.lastMouseMoveLine === line || line === undefined || isNaN(line)) { return; } + this.lastMouseMoveLine = line; - this.props.mouseMoveOnLineNumber(event.domEvent, hunk, line); + this.handleSelectionEvent(event.domEvent, [[line, 0], [line, Infinity]], {add: true}); } didMouseUp() { - this.props.mouseUp(); + this.mouseSelectionInProgress = false; + } + + handleSelectionEvent(event, rangeLike, opts) { + if (event.button !== 0) { + return false; + } + + const isWindows = process.platform === 'win32'; + if (event.ctrlKey && !isWindows) { + // Allow the context menu to open. + return false; + } + + const options = { + add: false, + ...opts, + }; + + // Normalize the target selection range + const converted = Range.fromObject(rangeLike); + const flipped = converted.start.isLessThanOrEqual(converted.end) ? converted : converted.negate(); + const range = this.refEditor.map(editor => editor.clipBufferRange(flipped)).getOr(flipped); + + if (event.metaKey || (event.ctrlKey && isWindows)) { + this.refEditor.map(editor => { + let intersects = false; + + for (const selection of editor.getSelections()) { + if (selection.intersectsBufferRange(range)) { + // Remove range from this selection by truncating it to the "near edge" of the range and creating a + // new selection from the "far edge" to the previous end. Omit either side if it is empty. + intersects = true; + const selectionRange = selection.getBufferRange(); + + const newRanges = []; + + if (!range.start.isEqual(selectionRange.start)) { + // Include the bit from the selection's previous start to the range's start. + let nudged = range.start; + if (range.start.column === 0) { + const lastColumn = editor.getBuffer().lineLengthForRow(range.start.row - 1); + nudged = [range.start.row - 1, lastColumn]; + } + + newRanges.push([selectionRange.start, nudged]); + } + + if (!range.end.isEqual(selectionRange.end)) { + // Include the bit from the range's end to the selection's end. + let nudged = range.end; + const lastColumn = editor.getBuffer().lineLengthForRow(range.end.row); + if (range.end.column === lastColumn) { + nudged = [range.end.row + 1, 0]; + } + + newRanges.push([nudged, selectionRange.end]); + } + + if (newRanges.length > 0) { + selection.setBufferRange(newRanges[0]); + for (const newRange of newRanges.slice(1)) { + editor.addSelectionForBufferRange(newRange, {reversed: selection.isReversed()}); + } + } + } + } + + if (!intersects) { + // Add this range as a new, distinct selection. + editor.addSelectionForBufferRange(range); + } + + return null; + }); + } else if (options.add || event.shiftKey) { + // Extend the existing selection to encompass this range. + this.refEditor.map(editor => { + const lastSelection = editor.getLastSelection(); + const lastSelectionRange = lastSelection.getBufferRange(); + + // You are now entering the wall of ternery operators. This is your last exit before the tollbooth + const cursorHead = lastSelection.isReversed() ? lastSelectionRange.end : lastSelectionRange.start; + const isBefore = range.start.isLessThan(cursorHead); + const farEdge = isBefore ? range.start : range.end; + const newRange = isBefore ? [farEdge, cursorHead] : [cursorHead, farEdge]; + + lastSelection.setBufferRange(newRange, {reversed: isBefore}); + return null; + }); + } else { + this.refEditor.map(editor => editor.setSelectedBufferRange(range)); + } + + return true; } didConfirm() { @@ -530,20 +596,27 @@ export default class FilePatchView extends React.Component { this.props.openFile(cursors); } - didChangeSelections() { - this.refEditor.map(editor => { + getSelectedBufferRanges() { + return this.refEditor.map(editor => { + return editor.getSelections().map(selection => selection.getBufferRange()); + }).getOr([]); + } - this.props.selectedRowsChanged( - editor.getSelectedBufferRanges().reduce((acc, range) => { - for (const row of range.getRows()) { - acc.add(row); - } - return acc; - }, new Set()), - ); + observeSelections(selection) { + const selectionSubs = new CompositeDisposable( + selection.onDidChangeRange(this.didChangeSelectedRanges), + selection.onDidDestroy(() => { + selectionSubs.dispose(); + this.subs.remove(selectionSubs); + }), + ); + this.subs.add(selectionSubs); - return null; - }); + this.didChangeSelectedRanges(); + } + + didChangeSelectedRanges() { + this.props.selectedRangesChanged(this.getSelectedBufferRanges()); } oldLineNumberLabel({bufferRow, softWrapped}) { @@ -577,7 +650,7 @@ export default class FilePatchView extends React.Component { if (this.state.selectedHunks.has(hunk)) { return this.props.toggleLines(this.state.selectedLines); } else { - return this.props.selectAndToggleHunk(hunk); + return this.props.toggleHunk(hunk); } } @@ -585,10 +658,18 @@ export default class FilePatchView extends React.Component { if (this.state.selectedHunks.has(hunk)) { return this.props.discardLines(this.state.selectedLines); } else { - return this.props.selectAndDiscardHunk(hunk); + return this.props.discardHunk(hunk); } } + selectNextHunk() { + // + } + + selectPreviousHunk() { + // + } + getHunkAt(bufferRow) { const hunkFromMarker = this.hunkMarkerLayerHolder.map(layer => { const markers = layer.findMarkers({intersectsRow: bufferRow}); From bef4e2f4f26e829d64b07525ae7d3bd144a19832 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 29 Aug 2018 15:28:06 -0400 Subject: [PATCH 0150/4053] Filter non-change rows from the selection before calling prop callback --- lib/controllers/file-patch-controller.js | 17 ++----- lib/views/file-patch-view.js | 58 ++++++++++++++++++------ 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 46f2eb79a8..02f05a10fb 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -26,7 +26,7 @@ export default class FilePatchController extends React.Component { super(props); autobind( this, - 'selectedRangesChanged', + 'selectedRowsChanged', 'undoLastDiscard', 'diveIntoMirrorPatch', 'openFile', 'toggleFile', 'toggleHunk', 'toggleLines', 'toggleModeChange', 'toggleSymlinkChange', 'discardLines', 'discardHunk', @@ -71,7 +71,7 @@ export default class FilePatchController extends React.Component { {...this.props} selectedRows={this.state.selectedRows} - selectedRangesChanged={this.selectedRangesChanged} + selectedRowsChanged={this.selectedRowsChanged} diveIntoMirrorPatch={this.diveIntoMirrorPatch} openFile={this.openFile} @@ -181,19 +181,12 @@ export default class FilePatchController extends React.Component { return this.discardLines(hunk.getLines()); } - selectedRangesChanged(ranges) { - const newSelectedRows = new Set(); - for (const range of ranges) { - for (let row = range.start.row; row <= range.end.row; row++) { - newSelectedRows.add(row); - } - } - - if (equalSets(this.state.selectedRows, newSelectedRows)) { + selectedRowsChanged(rows) { + if (equalSets(this.state.selectedRows, rows)) { return; } - this.setState({selectedRows: newSelectedRows}); + this.setState({selectedRows: rows}); } withStagingStatus(callbacks) { diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 3a899fef94..e54f5fd53f 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -35,7 +35,7 @@ export default class FilePatchView extends React.Component { commands: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, - selectedRangesChanged: PropTypes.func.isRequired, + selectedRowsChanged: PropTypes.func.isRequired, diveIntoMirrorPatch: PropTypes.func.isRequired, openFile: PropTypes.func.isRequired, @@ -54,7 +54,6 @@ export default class FilePatchView extends React.Component { autobind( this, 'didMouseDownOnHeader', 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', - 'didChangeSelectedRanges', 'didConfirm', 'didOpenFile', 'observeSelections', 'oldLineNumberLabel', 'newLineNumberLabel', 'selectNextHunk', 'selectPreviousHunk', ); @@ -65,6 +64,8 @@ export default class FilePatchView extends React.Component { this.hunkMarkerLayerHolder = new RefHolder(); this.refRoot = new RefHolder(); this.refEditor = new RefHolder(); + this.refAdditionLayer = new RefHolder(); + this.refDeletionLayer = new RefHolder(); this.subs = new CompositeDisposable(); } @@ -178,12 +179,12 @@ export default class FilePatchView extends React.Component { {this.renderLineDecorations( this.props.filePatch.getAdditionRanges(), 'github-FilePatchView-line--added', - {line: true}, + {line: true, refHolder: this.refAdditionLayer}, )} {this.renderLineDecorations( this.props.filePatch.getDeletionRanges(), 'github-FilePatchView-line--deleted', - {line: true}, + {line: true, refHolder: this.refDeletionLayer}, )} {this.renderLineDecorations( this.props.filePatch.getNoNewlineRanges(), @@ -371,13 +372,14 @@ export default class FilePatchView extends React.Component { ); } - renderLineDecorations(ranges, lineClass, {line, gutter}) { + renderLineDecorations(ranges, lineClass, {line, gutter, refHolder}) { if (ranges.length === 0) { return null; } + const holder = refHolder || new RefHolder(); return ( - + {ranges.map((range, index) => { return ( { - return editor.getSelections().map(selection => selection.getBufferRange()); - }).getOr([]); + return new Set( + editor.getSelections() + .map(selection => selection.getBufferRowRange()) + .reduce((acc, [start, end]) => { + for (let row = start; row <= end; row++) { + if (this.isChangeRow(row)) { + acc.push(row); + } + } + return acc; + }, []), + ); + }).getOr(new Set()); } observeSelections(selection) { const selectionSubs = new CompositeDisposable( - selection.onDidChangeRange(this.didChangeSelectedRanges), + selection.onDidChangeRange(event => { + if ( + !event || + event.oldBufferRange.start.row !== event.newBufferRange.start.row || + event.oldBufferRange.end.row !== event.newBufferRange.end.row + ) { + this.didChangeSelectedRows(); + } + }), selection.onDidDestroy(() => { selectionSubs.dispose(); this.subs.remove(selectionSubs); + this.didChangeSelectedRows(); }), ); this.subs.add(selectionSubs); - this.didChangeSelectedRanges(); + this.didChangeSelectedRows(); } - didChangeSelectedRanges() { - this.props.selectedRangesChanged(this.getSelectedBufferRanges()); + didChangeSelectedRows() { + this.props.selectedRowsChanged(this.getSelectedRows()); } oldLineNumberLabel({bufferRow, softWrapped}) { @@ -694,6 +716,16 @@ export default class FilePatchView extends React.Component { return undefined; } + isChangeRow(bufferRow) { + for (const holder of [this.refAdditionLayer, this.refDeletionLayer]) { + const changeMarkers = holder.map(layer => layer.findMarkers({intersectsRow: bufferRow})).getOr([]); + if (changeMarkers.length !== 0) { + return true; + } + } + return false; + } + pad(num) { const maxDigits = this.props.filePatch.getMaxLineNumberWidth(); if (num === null) { From f2803f0964c88437ad541dda951bdcb0f8f1a24a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 29 Aug 2018 15:29:30 -0400 Subject: [PATCH 0151/4053] FilePatchHeader provides the raw DOM event --- lib/views/file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index e54f5fd53f..201581c174 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -412,7 +412,7 @@ export default class FilePatchView extends React.Component { } didMouseDownOnHeader(event, hunk) { - this.handleSelectionEvent(event.domEvent, hunk.getBufferRange()); + this.handleSelectionEvent(event, hunk.getBufferRange()); } didMouseDownOnLineNumber(event) { From 8c7e7b7c5f3f002ca273734f909827943be406b4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 29 Aug 2018 15:32:09 -0400 Subject: [PATCH 0152/4053] Show the loading spinner while the repository itself is loading --- lib/containers/file-patch-container.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index 3e34aa292a..66fae68d49 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -43,7 +43,7 @@ export default class FilePatchContainer extends React.Component { } renderWithData(data) { - if (data === null) { + if (this.props.repository.isLoading() || data === null) { return ; } From 2b0179ff7771e7db9e4e37ca09478d1fb3a058e8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 29 Aug 2018 15:52:29 -0400 Subject: [PATCH 0153/4053] Consider a hunk "selected" if its range is enclosed by a selection --- lib/views/file-patch-view.js | 12 +++++++++--- styles/hunk-header-view.less | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 201581c174..4f860b159e 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -338,20 +338,26 @@ export default class FilePatchView extends React.Component { */} {this.props.filePatch.getHunks().map((hunk, index) => { - const buttonSuffix = ' Selected Line'; - // FIXME Add "s" when plural + let buttonSuffix = ' Selected Change'; + if (this.props.selectedRows.size > 1) { + buttonSuffix += 's'; + } const toggleSelectionLabel = `${toggleVerb}${buttonSuffix}`; const discardSelectionLabel = `Discard${buttonSuffix}`; const startPoint = hunk.getBufferRange().start; const startRange = new Range(startPoint, startPoint); + const isSelected = this.refEditor.map(editor => { + return editor.getSelectedBufferRanges().some(range => range.containsRange(hunk.getBufferRange())); + }).getOr(false); + return ( Date: Wed, 29 Aug 2018 16:04:56 -0400 Subject: [PATCH 0154/4053] Reword that FIXME :eyes: --- lib/controllers/file-patch-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 02f05a10fb..4663bd27ff 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -48,7 +48,7 @@ export default class FilePatchController extends React.Component { static getDerivedStateFromProps(props, state) { if (props.filePatch !== state.lastFilePatch) { return { - selectedRows: new Set(), // FIXME + selectedRows: new Set(), // FIXME derive new selected rows from old ones lastFilePatch: props.filePatch, }; } From 619e841c28d0ae39d04ecbf9743ae9b4c63462e4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 30 Aug 2018 15:16:43 -0400 Subject: [PATCH 0155/4053] Update patch generation logic --- lib/controllers/file-patch-controller.js | 33 ++++++------------------ lib/views/file-patch-view.js | 26 +++---------------- 2 files changed, 12 insertions(+), 47 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 4663bd27ff..46d49f6c59 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -28,8 +28,7 @@ export default class FilePatchController extends React.Component { this, 'selectedRowsChanged', 'undoLastDiscard', 'diveIntoMirrorPatch', 'openFile', - 'toggleFile', 'toggleHunk', 'toggleLines', 'toggleModeChange', 'toggleSymlinkChange', - 'discardLines', 'discardHunk', + 'toggleFile', 'toggleRows', 'toggleModeChange', 'toggleSymlinkChange', 'discardRows', ); this.state = { @@ -76,13 +75,11 @@ export default class FilePatchController extends React.Component { diveIntoMirrorPatch={this.diveIntoMirrorPatch} openFile={this.openFile} toggleFile={this.toggleFile} - toggleHunk={this.toggleHunk} - toggleLines={this.toggleLines} + toggleRows={this.toggleRows} toggleModeChange={this.toggleModeChange} toggleSymlinkChange={this.toggleSymlinkChange} undoLastDiscard={this.undoLastDiscard} - discardLines={this.discardLines} - discardHunk={this.discardHunk} + discardRows={this.discardRows} selectNextHunk={this.selectNextHunk} selectPreviousHunk={this.selectPreviousHunk} /> @@ -121,21 +118,11 @@ export default class FilePatchController extends React.Component { }); } - toggleHunk(hunk) { + toggleRows() { return this.stagingOperation(() => { const patch = this.withStagingStatus({ - staged: () => this.props.filePatch.getUnstagePatchForHunk(hunk), - unstaged: () => this.props.filePatch.getStagePatchForHunk(hunk), - }); - return this.props.repository.applyPatchToIndex(patch); - }); - } - - toggleLines(lines) { - return this.stagingOperation(() => { - const patch = this.withStagingStatus({ - staged: () => this.props.filePatch.getUnstagePatchForLines(lines), - unstaged: () => this.props.filePatch.getStagePatchForLines(lines), + staged: () => this.props.filePatch.getUnstagePatchForLines(this.state.selectedRows), + unstaged: () => this.props.filePatch.getStagePatchForLines(this.state.selectedRows), }); return this.props.repository.applyPatchToIndex(patch); }); @@ -173,12 +160,8 @@ export default class FilePatchController extends React.Component { }); } - discardLines(lines) { - return this.props.discardLines(this.props.filePatch, lines, this.props.repository); - } - - discardHunk(hunk) { - return this.discardLines(hunk.getLines()); + discardRows() { + return this.props.discardLines(this.props.filePatch, this.state.selectedRows, this.props.repository); } selectedRowsChanged(rows) { diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 4f860b159e..6617eab542 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -40,13 +40,11 @@ export default class FilePatchView extends React.Component { diveIntoMirrorPatch: PropTypes.func.isRequired, openFile: PropTypes.func.isRequired, toggleFile: PropTypes.func.isRequired, - toggleHunk: PropTypes.func.isRequired, - toggleLines: PropTypes.func.isRequired, + toggleRows: PropTypes.func.isRequired, toggleModeChange: PropTypes.func.isRequired, toggleSymlinkChange: PropTypes.func.isRequired, undoLastDiscard: PropTypes.func.isRequired, - discardLines: PropTypes.func.isRequired, - discardHunk: PropTypes.func.isRequired, + discardRows: PropTypes.func.isRequired, } constructor(props) { @@ -365,8 +363,8 @@ export default class FilePatchView extends React.Component { tooltips={this.props.tooltips} - toggleSelection={() => this.toggleSelection(hunk)} - discardSelection={() => this.discardSelection(hunk)} + toggleSelection={this.props.toggleRows} + discardSelection={this.props.discardRows} mouseDown={this.didMouseDownOnHeader} /> @@ -674,22 +672,6 @@ export default class FilePatchView extends React.Component { return this.pad(newRow); } - toggleSelection(hunk) { - if (this.state.selectedHunks.has(hunk)) { - return this.props.toggleLines(this.state.selectedLines); - } else { - return this.props.toggleHunk(hunk); - } - } - - discardSelection(hunk) { - if (this.state.selectedHunks.has(hunk)) { - return this.props.discardLines(this.state.selectedLines); - } else { - return this.props.discardHunk(hunk); - } - } - selectNextHunk() { // } From 489b17f9def26baa46116c7baf127b77c0ea2aa3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 30 Aug 2018 15:33:22 -0400 Subject: [PATCH 0156/4053] Brighten selected changes --- lib/views/file-patch-view.js | 2 +- styles/file-patch-view.less | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 6617eab542..dd4e7b945d 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -172,7 +172,7 @@ export default class FilePatchView extends React.Component { {this.renderLineDecorations( Array.from(this.props.selectedRows, row => Range.fromObject([[row, 0], [row, 0]])), 'github-FilePatchView-line--selected', - {gutter: true}, + {gutter: true, line: true}, )} {this.renderLineDecorations( this.props.filePatch.getAdditionRanges(), diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 47eb743583..8997bdb3c8 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -157,22 +157,22 @@ &-line { // mixin - .hunk-line-mixin(@fg; @bg) { - color: saturate( mix(@fg, @text-color-highlight, 20%), 20%); - background-color: saturate( mix(@bg, @hunk-bg-color, 15%), 20%); + .hunk-line-mixin(@fg; @bg; @sat) { + color: saturate( mix(@fg, @text-color-highlight, 20%), @sat); + background-color: saturate( mix(@bg, @hunk-bg-color, 15%), @sat); - &.line.cursor-line { - color: saturate( mix(@fg, @text-color-highlight, 20%), 80%); - background-color: saturate( mix(@bg, @hunk-bg-color, 15%), 80%); + &.line.github-FilePatchView-line--selected { + color: saturate( mix(@fg, @text-color-highlight, 20%), 100% - @sat); + background-color: saturate( mix(@bg, @hunk-bg-color, 15%), 100% - @sat); } } &--deleted { - .hunk-line-mixin(@text-color-error, @background-color-error); + .hunk-line-mixin(@text-color-error, @background-color-error, 20%); } &--added { - .hunk-line-mixin(@text-color-success, @background-color-success); + .hunk-line-mixin(@text-color-success, @background-color-success, 20%); } } } From b53d1451bf8fd592eaa956b763c301f1ed3d7b8d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 31 Aug 2018 08:56:40 -0400 Subject: [PATCH 0157/4053] Improve the error message from a failed autobind() --- lib/helpers.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/helpers.js b/lib/helpers.js index a77cbfb169..37f1854de1 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -11,6 +11,9 @@ export const CO_AUTHOR_REGEX = /^co-authored-by. (.+?) <(.+?)>$/i; export function autobind(self, ...methods) { for (const method of methods) { + if (typeof self[method] !== 'function') { + throw new Error(`Unable to autobind method ${method}`); + } self[method] = self[method].bind(self); } } From cb76f8093d46b7880eddbc9d7841baf37fc63674 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 31 Aug 2018 08:56:54 -0400 Subject: [PATCH 0158/4053] core:confirm can call toggleRows directly --- lib/views/file-patch-view.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index dd4e7b945d..b7fbe66547 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -52,7 +52,7 @@ export default class FilePatchView extends React.Component { autobind( this, 'didMouseDownOnHeader', 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', - 'didConfirm', 'didOpenFile', 'observeSelections', 'oldLineNumberLabel', 'newLineNumberLabel', + 'didOpenFile', 'observeSelections', 'oldLineNumberLabel', 'newLineNumberLabel', 'selectNextHunk', 'selectPreviousHunk', ); @@ -115,7 +115,7 @@ export default class FilePatchView extends React.Component { renderCommands() { return ( - + @@ -542,15 +542,6 @@ export default class FilePatchView extends React.Component { return true; } - didConfirm() { - const [firstHunk] = this.state.selectedHunks; - if (!firstHunk) { - return; - } - - this.toggleSelection(firstHunk); - } - didOpenFile() { const cursors = []; From 50de48ef75930e3d112e75986dd90839cca9f7b9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 13:52:44 -0400 Subject: [PATCH 0159/4053] Select the next changed row by index when the patch changes --- lib/models/patch/patch.js | 58 +++++++++++++++++++++++++++ test/models/patch/patch.test.js | 69 +++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 0e5289ba85..1e57dfaffb 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -304,6 +304,60 @@ export default class Patch { return this.clone({hunks, status}); } + getNextSelectionRange(lastPatch, lastSelectedRows) { + const lastMax = Math.max(...lastSelectedRows); + + let lastSelectionIndex = 0; + for (const hunk of lastPatch.getHunks()) { + let includesUnselectedChange = false; + let includesMax = false; + let hunkSelectionIndex = 0; + + changeLoop: for (const change of hunk.getChanges()) { + const intersections = change.getRowRange().intersectRowsIn(lastSelectedRows, this.getBufferText(), true); + for (const {intersection, gap} of intersections) { + // Only include a partial range if this intersection includes the last selected buffer row. + includesMax = intersection.includesRow(lastMax); + const delta = includesMax ? lastMax - intersection.getStartBufferRow() + 1 : intersection.bufferRowCount(); + + if (gap) { + // This hunk includes at least one change row that was *not* selected. + includesUnselectedChange = true; + + // Range of unselected changes. + hunkSelectionIndex += delta; + } + + if (includesMax) { + break changeLoop; + } + } + } + + if (includesUnselectedChange) { + lastSelectionIndex += hunkSelectionIndex; + } + + if (includesMax) { + break; + } + } + + let newSelectionRow = 0; + hunkLoop: for (const hunk of this.getHunks()) { + for (const change of hunk.getChanges()) { + if (lastSelectionIndex <= change.bufferRowCount()) { + newSelectionRow = change.getStartBufferRow() + lastSelectionIndex; + break hunkLoop; + } else { + lastSelectionIndex -= change.bufferRowCount(); + } + } + } + + return [[newSelectionRow, 0], [newSelectionRow, 0]]; + } + toString() { return this.getHunks().reduce((str, hunk) => { str += hunk.toStringIn(this.getBufferText()); @@ -361,6 +415,10 @@ export const nullPatch = { return this; }, + getNextSelectionRange() { + return [[0, 0], [0, 0]]; + }, + getMaxLineNumberWidth() { return 0; }, diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index e3b3350d21..320bef70bf 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -436,6 +436,67 @@ describe('Patch', function() { }); }); + describe('next selection range derivation', function() { + it('selects the first change region after the highest buffer row', function() { + const lastPatch = buildPatchFixture(); + // Selected: + // deletions (1-2) and partial addition (4 from 3-5) from hunk 0 + // one deletion row (13 from 12-16) from the middle of hunk 1; + // nothing in hunks 2 or 3 + const lastSelectedRows = new Set([1, 2, 4, 5, 13]); + + const nBufferText = + // 0 1 2 3 4 + '0000\n0003\n0004\n0005\n0006\n' + + // 5 6 7 8 9 10 11 12 13 14 15 + '0007\n0008\n0009\n0010\n0011\n0012\n0014\n0015\n0016\n0017\n0018\n' + + // 16 17 18 19 20 + '0019\n0020\n0021\n0022\n0023\n' + + // 21 22 23 + '0024\n0025\n No newline at end of file\n'; + const nHunks = [ + new Hunk({ + oldStartRow: 3, oldRowCount: 3, newStartRow: 3, newRowCount: 5, // next row drift = +2 + rowRange: buildRange(0, 4), // context: 0, 2, 4 + changes: [ + new Addition(buildRange(1)), // + 1 + new Addition(buildRange(3)), // + 3 + ], + }), + new Hunk({ + oldStartRow: 12, oldRowCount: 9, newStartRow: 14, newRowCount: 7, // next row drift = +2 -2 = 0 + rowRange: buildRange(5, 15), // context: 5, 7, 8, 9, 15 + changes: [ + new Addition(buildRange(6)), // +6 + new Deletion(buildRange(10, 13)), // -10 -11 -12 -13 + new Addition(buildRange(14)), // +14 + ], + }), + new Hunk({ + oldStartRow: 26, oldRowCount: 4, newStartRow: 26, newRowCount: 3, // next row drift = 0 -1 = -1 + rowRange: buildRange(16, 20), // context: 16, 20 + changes: [ + new Addition(buildRange(17)), // +17 + new Deletion(buildRange(18, 19)), // -18 -19 + ], + }), + new Hunk({ + oldStartRow: 32, oldRowCount: 1, newStartRow: 31, newRowCount: 2, + rowRange: buildRange(22, 24), // context: 22 + changes: [ + new Addition(buildRange(23)), // +23 + new NoNewline(buildRange(24)), + ], + }), + ]; + const nextPatch = new Patch({status: 'modified', hunks: nHunks, bufferText: nBufferText}); + + const nextRange = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); + // Original buffer row 14 = the next changed row = new buffer row 11 + assert.deepEqual(nextRange, [[11, 0], [11, 0]]); + }); + }); + it('prints itself as an apply-ready string', function() { const bufferText = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999\n'; // old: 0000.2222.3333.4444.5555.6666.7777.8888.9999. @@ -493,6 +554,14 @@ describe('Patch', function() { }); }); +function buildRange(startRow, endRow = startRow, rowLength = 5) { + return new IndexedRowRange({ + bufferRange: [[startRow, 0], [endRow, 0]], + startOffset: startRow * rowLength, + endOffset: (endRow + 1) * rowLength, + }); +} + function buildPatchFixture() { const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n' + From a1820c1a396f44d573fcf5304866afda34e35b96 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 13:53:09 -0400 Subject: [PATCH 0160/4053] Forward to Patch::getNextSelectionRange() from FilePatch --- lib/models/patch/file-patch.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index df93c80eed..ba88fde762 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -181,6 +181,10 @@ export default class FilePatch { return this.getUnstagePatchForLines(new Set(hunk.getBufferRows())); } + getNextSelectionRange(lastFilePatch, lastSelectedRows) { + return this.getPatch().getNextSelectionRange(lastFilePatch.getPatch(), lastSelectedRows); + } + toString() { if (!this.isPresent()) { return ''; From 49c7d53e83528c8379062f8fa9fd9f164f59727b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 13:53:54 -0400 Subject: [PATCH 0161/4053] Advance the selected editor range when a new FilePatch arrives --- lib/views/file-patch-view.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index b7fbe66547..de86dd47dd 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -68,6 +68,22 @@ export default class FilePatchView extends React.Component { this.subs = new CompositeDisposable(); } + componentDidUpdate(prevProps) { + if (this.props.filePatch !== prevProps.filePatch) { + // Heuristically adjust the editor selection based on the old file patch, the old row selection state, and + // the incoming patch. + this.refEditor.map(editor => { + const newSelectionRange = this.props.filePatch.getNextSelectionRange( + prevProps.filePatch, + this.props.selectedRows, + ); + editor.setSelectedBufferRange(newSelectionRange); + + return null; + }); + } + } + componentDidMount() { window.addEventListener('mouseup', this.didMouseUp); } From a141eefc298cb8b3fcab61c002e8a4b12535bce9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 14:03:38 -0400 Subject: [PATCH 0162/4053] Pass previous selectedRows set, not current one --- lib/views/file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index de86dd47dd..3a8293eae3 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -75,7 +75,7 @@ export default class FilePatchView extends React.Component { this.refEditor.map(editor => { const newSelectionRange = this.props.filePatch.getNextSelectionRange( prevProps.filePatch, - this.props.selectedRows, + prevProps.selectedRows, ); editor.setSelectedBufferRange(newSelectionRange); From 5603dff823cbf79af09eb7c97fe4349d7a99a09a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 15:08:10 -0400 Subject: [PATCH 0163/4053] Suppress Selection change events while setting editor text --- lib/atom/atom-text-editor.js | 44 ++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index d6ec6883bf..7ef7ffaafe 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -44,6 +44,9 @@ export default class AtomTextEditor extends React.PureComponent { text: PropTypes.string, didChange: PropTypes.func, didChangeCursorPosition: PropTypes.func, + didAddSelection: PropTypes.func, + didChangeSelectionRange: PropTypes.func, + didDestroySelection: PropTypes.func, observeSelections: PropTypes.func, preserveMarkers: PropTypes.bool, refModel: RefHolderPropType, @@ -54,12 +57,14 @@ export default class AtomTextEditor extends React.PureComponent { text: '', didChange: () => {}, didChangeCursorPosition: () => {}, - observeSelections: () => {}, + didAddSelection: () => {}, + didChangeSelectionRange: () => {}, + didDestroySelection: () => {}, } constructor(props, context) { super(props, context); - autobind(this, 'didChange', 'didChangeCursorPosition'); + autobind(this, 'didChange', 'didChangeCursorPosition', 'observeSelections'); this.subs = new CompositeDisposable(); this.suppressChange = false; @@ -86,14 +91,14 @@ export default class AtomTextEditor extends React.PureComponent { this.refElement.map(element => { const editor = element.getModel(); - editor.setText(this.props.text, {bypassReadOnly: true}); + // editor.setText(this.props.text, {bypassReadOnly: true}); this.getRefModel().setter(editor); this.subs.add( editor.onDidChange(this.didChange), editor.onDidChangeCursorPosition(this.didChangeCursorPosition), - editor.observeSelections(this.props.observeSelections), + editor.observeSelections(this.observeSelections), ); // shhh, eslint. shhhh @@ -110,17 +115,17 @@ export default class AtomTextEditor extends React.PureComponent { } quietlySetText(text) { - this.suppressChange = true; try { const editor = this.getModel(); if (editor.getText() === text) { return; } + this.suppressChange = true; if (this.props.preserveMarkers) { editor.getBuffer().setTextViaDiff(text); } else { - editor.setText(text); + editor.setText(text, {bypassReadOnly: true}); } } finally { this.suppressChange = false; @@ -141,7 +146,32 @@ export default class AtomTextEditor extends React.PureComponent { } didChangeCursorPosition() { - this.props.didChangeCursorPosition(this.getModel()); + if (!this.suppressChange) { + this.props.didChangeCursorPosition(this.getModel()); + } + } + + observeSelections(selection) { + const selectionSubs = new CompositeDisposable( + selection.onDidChangeRange(event => { + if (!this.suppressChange) { + this.props.didChangeSelectionRange(event); + } + }), + selection.onDidDestroy(() => { + selectionSubs.dispose(); + this.subs.remove(selectionSubs); + + if (!this.suppressChange) { + this.props.didDestroySelection(selection); + } + }), + ); + this.subs.add(selectionSubs); + + if (!this.suppressChange) { + this.props.didAddSelection(selection); + } } contains(element) { From 505ef96a9066425199e84ccd86ad8e9fe2912ff1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 15:08:38 -0400 Subject: [PATCH 0164/4053] Don't reset selectedRows in FilePatchController --- lib/controllers/file-patch-controller.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 46d49f6c59..4876a73dd9 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -44,17 +44,6 @@ export default class FilePatchController extends React.Component { }); } - static getDerivedStateFromProps(props, state) { - if (props.filePatch !== state.lastFilePatch) { - return { - selectedRows: new Set(), // FIXME derive new selected rows from old ones - lastFilePatch: props.filePatch, - }; - } - - return null; - } - componentDidUpdate(prevProps) { if (prevProps.filePatch !== this.props.filePatch) { this.resolvePatchChangePromise(); From a4db197235071fc6d13213679c63367a4db29036 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 15:08:50 -0400 Subject: [PATCH 0165/4053] toggleRows() is a no-op with no changes selected --- lib/controllers/file-patch-controller.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 4876a73dd9..905f6caaea 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -108,6 +108,10 @@ export default class FilePatchController extends React.Component { } toggleRows() { + if (this.state.selectedRows.size === 0) { + return Promise.resolve(); + } + return this.stagingOperation(() => { const patch = this.withStagingStatus({ staged: () => this.props.filePatch.getUnstagePatchForLines(this.state.selectedRows), From 7c070a78d2502ca938527b8d042b42a2693b350f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 15:22:39 -0400 Subject: [PATCH 0166/4053] Go go gadget off-by-one error --- lib/models/patch/patch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 1e57dfaffb..71497ab795 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -346,7 +346,7 @@ export default class Patch { let newSelectionRow = 0; hunkLoop: for (const hunk of this.getHunks()) { for (const change of hunk.getChanges()) { - if (lastSelectionIndex <= change.bufferRowCount()) { + if (lastSelectionIndex < change.bufferRowCount()) { newSelectionRow = change.getStartBufferRow() + lastSelectionIndex; break hunkLoop; } else { From cfd1db204d0b8eeb767c824bc456a21eb1db1ecc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 15:23:16 -0400 Subject: [PATCH 0167/4053] Allow AtomTextEditor to manage selection events --- lib/views/file-patch-view.js | 41 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 3a8293eae3..2ac75795e7 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -52,8 +52,8 @@ export default class FilePatchView extends React.Component { autobind( this, 'didMouseDownOnHeader', 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', - 'didOpenFile', 'observeSelections', 'oldLineNumberLabel', 'newLineNumberLabel', - 'selectNextHunk', 'selectPreviousHunk', + 'didOpenFile', 'didAddSelection', 'didChangeSelectionRange', 'didDestroySelection', + 'oldLineNumberLabel', 'newLineNumberLabel', 'selectNextHunk', 'selectPreviousHunk', ); this.mouseSelectionInProgress = false; @@ -152,7 +152,9 @@ export default class FilePatchView extends React.Component { autoWidth={false} autoHeight={false} readOnly={true} - observeSelections={this.observeSelections} + didAddSelection={this.didAddSelection} + didChangeSelectionRange={this.didChangeSelectionRange} + didDestroySelection={this.didDestroySelection} refModel={this.refEditor}> { - if ( - !event || - event.oldBufferRange.start.row !== event.newBufferRange.start.row || - event.oldBufferRange.end.row !== event.newBufferRange.end.row - ) { - this.didChangeSelectedRows(); - } - }), - selection.onDidDestroy(() => { - selectionSubs.dispose(); - this.subs.remove(selectionSubs); - this.didChangeSelectedRows(); - }), - ); - this.subs.add(selectionSubs); + didAddSelection() { + this.didChangeSelectedRows(); + } + + didChangeSelectionRange(event) { + if ( + !event || + event.oldBufferRange.start.row !== event.newBufferRange.start.row || + event.oldBufferRange.end.row !== event.newBufferRange.end.row + ) { + this.didChangeSelectedRows(); + } + } + didDestroySelection() { this.didChangeSelectedRows(); } didChangeSelectedRows() { + const selectionRanges = this.refEditor.map(editor => editor.getSelectedBufferRanges()).getOr([]); this.props.selectedRowsChanged(this.getSelectedRows()); } From 18663ee377c03bccf4c11bad09209c9701948aa9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 15:24:57 -0400 Subject: [PATCH 0168/4053] Remove unused variable --- lib/views/file-patch-view.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 2ac75795e7..0157a2946b 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -647,7 +647,6 @@ export default class FilePatchView extends React.Component { } didChangeSelectedRows() { - const selectionRanges = this.refEditor.map(editor => editor.getSelectedBufferRanges()).getOr([]); this.props.selectedRowsChanged(this.getSelectedRows()); } From 10c0f44da1f9c5481ab5f79bd4c0f051aa4eca5c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 15:39:38 -0400 Subject: [PATCH 0169/4053] Oh, right. This is here so that markers don't render on row 0 --- lib/atom/atom-text-editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index 7ef7ffaafe..e5c2edca5c 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -91,7 +91,7 @@ export default class AtomTextEditor extends React.PureComponent { this.refElement.map(element => { const editor = element.getModel(); - // editor.setText(this.props.text, {bypassReadOnly: true}); + editor.setText(this.props.text, {bypassReadOnly: true}); this.getRefModel().setter(editor); From bba38bfba75835c53db264901db5f3114fb65e8a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 15:39:56 -0400 Subject: [PATCH 0170/4053] Access a patch's first change range row --- lib/models/patch/file-patch.js | 4 ++++ lib/models/patch/patch.js | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index ba88fde762..55d21178d8 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -181,6 +181,10 @@ export default class FilePatch { return this.getUnstagePatchForLines(new Set(hunk.getBufferRows())); } + getFirstChangeRange() { + return this.getPatch().getFirstChangeRange(); + } + getNextSelectionRange(lastFilePatch, lastSelectedRows) { return this.getPatch().getNextSelectionRange(lastFilePatch.getPatch(), lastSelectedRows); } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 71497ab795..394839afc1 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -304,7 +304,26 @@ export default class Patch { return this.clone({hunks, status}); } + getFirstChangeRange() { + const firstHunk = this.getHunks()[0]; + if (!firstHunk) { + return [[0, 0], [0, 0]]; + } + + const firstChange = firstHunk.getChanges()[0]; + if (!firstChange) { + return [[0, 0], [0, 0]]; + } + + const firstRow = firstChange.getStartBufferRow(); + return [[firstRow, 0], [firstRow, 0]]; + } + getNextSelectionRange(lastPatch, lastSelectedRows) { + if (lastSelectedRows.size === 0) { + return this.getFirstChangeRange(); + } + const lastMax = Math.max(...lastSelectedRows); let lastSelectionIndex = 0; From 7aff78031eb75a6d8da087f3c2e2409e32de09a1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 4 Sep 2018 15:40:11 -0400 Subject: [PATCH 0171/4053] Select the first row of the first change on mount --- lib/views/file-patch-view.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 0157a2946b..7832b4b917 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -86,6 +86,10 @@ export default class FilePatchView extends React.Component { componentDidMount() { window.addEventListener('mouseup', this.didMouseUp); + this.refEditor.map(editor => { + editor.setSelectedBufferRange(this.props.filePatch.getFirstChangeRange()); + return null; + }); } componentWillUnmount() { From 1f44b4a497a8ec4d665e5815107692ee08ffdc50 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 07:31:42 -0400 Subject: [PATCH 0172/4053] :hocho: .old.js files --- lib/controllers/file-patch-controller.old.js | 550 ------------ lib/models/presented-file-patch.js | 103 --- lib/views/file-patch-view.old.js | 716 ---------------- lib/views/hunk-view.old.js | 143 --- .../file-patch-controller.test.old.js | 811 ------------------ test/models/file-patch.test.old.js | 507 ----------- test/views/file-patch-view.test.old.js | 615 ------------- 7 files changed, 3445 deletions(-) delete mode 100644 lib/controllers/file-patch-controller.old.js delete mode 100644 lib/models/presented-file-patch.js delete mode 100644 lib/views/file-patch-view.old.js delete mode 100644 lib/views/hunk-view.old.js delete mode 100644 test/controllers/file-patch-controller.test.old.js delete mode 100644 test/models/file-patch.test.old.js delete mode 100644 test/views/file-patch-view.test.old.js diff --git a/lib/controllers/file-patch-controller.old.js b/lib/controllers/file-patch-controller.old.js deleted file mode 100644 index 90d6a67df8..0000000000 --- a/lib/controllers/file-patch-controller.old.js +++ /dev/null @@ -1,550 +0,0 @@ -import path from 'path'; -import React from 'react'; -import PropTypes from 'prop-types'; -import {Point} from 'atom'; -import {Emitter, CompositeDisposable} from 'event-kit'; - -import Switchboard from '../switchboard'; -import FilePatchView from '../views/file-patch-view'; -import ModelObserver from '../models/model-observer'; -import {autobind} from '../helpers'; - -export default class FilePatchController extends React.Component { - static propTypes = { - largeDiffByteThreshold: PropTypes.number, - getRepositoryForWorkdir: PropTypes.func.isRequired, - workingDirectoryPath: PropTypes.string.isRequired, - commandRegistry: PropTypes.object.isRequired, - deserializers: PropTypes.object.isRequired, - tooltips: PropTypes.object.isRequired, - filePath: PropTypes.string.isRequired, - lineNumber: PropTypes.number, - initialStagingStatus: PropTypes.oneOf(['unstaged', 'staged']).isRequired, - discardLines: PropTypes.func.isRequired, - didSurfaceFile: PropTypes.func.isRequired, - quietlySelectItem: PropTypes.func.isRequired, - undoLastDiscard: PropTypes.func.isRequired, - openFiles: PropTypes.func.isRequired, - switchboard: PropTypes.instanceOf(Switchboard), - } - - static defaultProps = { - largeDiffByteThreshold: 32768, - switchboard: new Switchboard(), - } - - static uriPattern = 'atom-github://file-patch/{relpath...}?workdir={workdir}&stagingStatus={stagingStatus}' - - static buildURI(relPath, workdir, stagingStatus) { - return 'atom-github://file-patch/' + - relPath + - `?workdir=${encodeURIComponent(workdir)}` + - `&stagingStatus=${encodeURIComponent(stagingStatus)}`; - } - - static confirmedLargeFilePatches = new Set() - - static resetConfirmedLargeFilePatches() { - this.confirmedLargeFilePatches = new Set(); - } - - constructor(props, context) { - super(props, context); - autobind( - this, - 'onRepoRefresh', 'handleShowDiffClick', 'attemptHunkStageOperation', 'attemptFileStageOperation', - 'attemptModeStageOperation', 'attemptSymlinkStageOperation', 'attemptLineStageOperation', 'didSurfaceFile', - 'diveIntoCorrespondingFilePatch', 'focus', 'openCurrentFile', 'discardLines', 'undoLastDiscard', 'hasUndoHistory', - ); - - this.stagingOperationInProgress = false; - this.emitter = new Emitter(); - - this.state = { - filePatch: null, - stagingStatus: props.initialStagingStatus, - isPartiallyStaged: false, - }; - - this.repositoryObserver = new ModelObserver({ - didUpdate: repo => this.onRepoRefresh(repo), - }); - this.repositoryObserver.setActiveModel(props.getRepositoryForWorkdir(this.props.workingDirectoryPath)); - - this.filePatchLoadedPromise = new Promise(res => { - this.resolveFilePatchLoadedPromise = res; - }); - - this.subscriptions = new CompositeDisposable(); - this.subscriptions.add( - this.props.switchboard.onDidFinishActiveContextUpdate(() => { - this.repositoryObserver.setActiveModel(this.props.getRepositoryForWorkdir(this.props.workingDirectoryPath)); - }), - ); - } - - getFilePatchLoadedPromise() { - return this.filePatchLoadedPromise; - } - - getStagingStatus() { - return this.state.stagingStatus; - } - - getFilePath() { - return this.props.filePath; - } - - getWorkingDirectory() { - return this.props.workingDirectoryPath; - } - - getTitle() { - let title = this.isStaged() ? 'Staged' : 'Unstaged'; - title += ' Changes: '; - title += this.props.filePath; - return title; - } - - serialize() { - return { - deserializer: 'FilePatchControllerStub', - uri: this.getURI(), - }; - } - - onDidDestroy(callback) { - return this.emitter.on('did-destroy', callback); - } - - terminatePendingState() { - if (!this.hasTerminatedPendingState) { - this.emitter.emit('did-terminate-pending-state'); - this.hasTerminatedPendingState = true; - } - } - - onDidTerminatePendingState(callback) { - return this.emitter.on('did-terminate-pending-state', callback); - } - - async onRepoRefresh(repository) { - const staged = this.isStaged(); - let filePatch = await this.getFilePatchForPath(this.props.filePath, staged); - const isPartiallyStaged = await repository.isPartiallyStaged(this.props.filePath); - - const onFinish = () => { - this.props.switchboard.didFinishRepositoryRefresh(); - }; - - if (filePatch) { - this.resolveFilePatchLoadedPromise(); - if (!this.destroyed) { - this.setState({filePatch, isPartiallyStaged}, onFinish); - } else { - onFinish(); - } - } else { - const oldFilePatch = this.state.filePatch; - if (oldFilePatch) { - filePatch = oldFilePatch.clone({ - oldFile: oldFilePatch.oldFile.clone({mode: null, symlink: null}), - newFile: oldFilePatch.newFile.clone({mode: null, symlink: null}), - patch: oldFilePatch.getPatch().clone({hunks: []}), - }); - if (!this.destroyed) { - this.setState({filePatch, isPartiallyStaged}, onFinish); - } else { - onFinish(); - } - } - } - } - - getFilePatchForPath(filePath, staged) { - const repository = this.repositoryObserver.getActiveModel(); - return repository.getFilePatchForPath(filePath, {staged}); - } - - componentDidUpdate(_prevProps, prevState) { - if (prevState.stagingStatus !== this.state.stagingStatus) { - this.emitter.emit('did-change-title'); - } - } - - goToDiffLine(lineNumber) { - this.filePatchView.goToDiffLine(lineNumber); - } - - componentWillUnmount() { - this.destroy(); - } - - render() { - const fp = this.state.filePatch; - const hunks = fp ? fp.getHunks() : []; - const executableModeChange = fp && fp.didChangeExecutableMode() ? - {oldMode: fp.getOldMode(), newMode: fp.getNewMode()} : - null; - const symlinkChange = fp && fp.hasSymlink() ? - { - oldSymlink: fp.getOldSymlink(), - newSymlink: fp.getNewSymlink(), - typechange: fp.hasTypechange(), - filePatchStatus: fp.getStatus(), - } : null; - const repository = this.repositoryObserver.getActiveModel(); - if (repository.isUndetermined() || repository.isLoading()) { - return ( -
- -
- ); - } else if (repository.isAbsent()) { - return ( -
- - The repository for {this.props.workingDirectoryPath} is not open in Atom. - -
- ); - } else { - const hasUndoHistory = repository ? this.hasUndoHistory() : false; - return ( -
- { this.filePatchView = c; }} - commandRegistry={this.props.commandRegistry} - tooltips={this.props.tooltips} - displayLargeDiffMessage={!this.shouldDisplayLargeDiff(this.state.filePatch)} - byteCount={this.byteCount} - handleShowDiffClick={this.handleShowDiffClick} - hunks={hunks} - executableModeChange={executableModeChange} - symlinkChange={symlinkChange} - filePath={this.props.filePath} - workingDirectoryPath={this.getWorkingDirectory()} - stagingStatus={this.state.stagingStatus} - isPartiallyStaged={this.state.isPartiallyStaged} - attemptLineStageOperation={this.attemptLineStageOperation} - attemptHunkStageOperation={this.attemptHunkStageOperation} - attemptFileStageOperation={this.attemptFileStageOperation} - attemptModeStageOperation={this.attemptModeStageOperation} - attemptSymlinkStageOperation={this.attemptSymlinkStageOperation} - didSurfaceFile={this.didSurfaceFile} - didDiveIntoCorrespondingFilePatch={this.diveIntoCorrespondingFilePatch} - switchboard={this.props.switchboard} - openCurrentFile={this.openCurrentFile} - discardLines={this.discardLines} - undoLastDiscard={this.undoLastDiscard} - hasUndoHistory={hasUndoHistory} - lineNumber={this.props.lineNumber} - /> -
- ); - } - } - - shouldDisplayLargeDiff(filePatch) { - if (!filePatch) { return true; } - - const fullPath = path.join(this.getWorkingDirectory(), this.props.filePath); - if (FilePatchController.confirmedLargeFilePatches.has(fullPath)) { - return true; - } - - this.byteCount = filePatch.getByteSize(); - return this.byteCount < this.props.largeDiffByteThreshold; - } - - onDidChangeTitle(callback) { - return this.emitter.on('did-change-title', callback); - } - - handleShowDiffClick() { - if (this.repositoryObserver.getActiveModel()) { - const fullPath = path.join(this.getWorkingDirectory(), this.props.filePath); - FilePatchController.confirmedLargeFilePatches.add(fullPath); - this.forceUpdate(); - } - } - - async stageHunk(hunk) { - this.props.switchboard.didBeginStageOperation({stage: true, hunk: true}); - - await this.repositoryObserver.getActiveModel().applyPatchToIndex( - this.state.filePatch.getStagePatchForHunk(hunk), - ); - - this.props.switchboard.didFinishStageOperation({stage: true, hunk: true}); - } - - async unstageHunk(hunk) { - this.props.switchboard.didBeginStageOperation({unstage: true, hunk: true}); - - await this.repositoryObserver.getActiveModel().applyPatchToIndex( - this.state.filePatch.getUnstagePatchForHunk(hunk), - ); - - this.props.switchboard.didFinishStageOperation({unstage: true, hunk: true}); - } - - stageOrUnstageHunk(hunk) { - const stagingStatus = this.state.stagingStatus; - if (stagingStatus === 'unstaged') { - return this.stageHunk(hunk); - } else if (stagingStatus === 'staged') { - return this.unstageHunk(hunk); - } else { - throw new Error(`Unknown stagingStatus: ${stagingStatus}`); - } - } - - async stageFile() { - this.props.switchboard.didBeginStageOperation({stage: true, file: true}); - - await this.repositoryObserver.getActiveModel().stageFiles([this.props.filePath]); - this.props.switchboard.didFinishStageOperation({stage: true, file: true}); - } - - async unstageFile() { - this.props.switchboard.didBeginStageOperation({unstage: true, file: true}); - - await this.repositoryObserver.getActiveModel().unstageFiles([this.props.filePath]); - - this.props.switchboard.didFinishStageOperation({unstage: true, file: true}); - } - - stageOrUnstageFile() { - const stagingStatus = this.state.stagingStatus; - if (stagingStatus === 'unstaged') { - return this.stageFile(); - } else if (stagingStatus === 'staged') { - return this.unstageFile(); - } else { - throw new Error(`Unknown stagingStatus: ${stagingStatus}`); - } - } - - async stageModeChange(mode) { - this.props.switchboard.didBeginStageOperation({stage: true, mode: true}); - - await this.repositoryObserver.getActiveModel().stageFileModeChange( - this.props.filePath, mode, - ); - this.props.switchboard.didFinishStageOperation({stage: true, mode: true}); - } - - async unstageModeChange(mode) { - this.props.switchboard.didBeginStageOperation({unstage: true, mode: true}); - - await this.repositoryObserver.getActiveModel().stageFileModeChange( - this.props.filePath, mode, - ); - this.props.switchboard.didFinishStageOperation({unstage: true, mode: true}); - } - - stageOrUnstageModeChange() { - const stagingStatus = this.state.stagingStatus; - const oldMode = this.state.filePatch.getOldMode(); - const newMode = this.state.filePatch.getNewMode(); - if (stagingStatus === 'unstaged') { - return this.stageModeChange(newMode); - } else if (stagingStatus === 'staged') { - return this.unstageModeChange(oldMode); - } else { - throw new Error(`Unknown stagingStatus: ${stagingStatus}`); - } - } - - async stageSymlinkChange() { - this.props.switchboard.didBeginStageOperation({stage: true, symlink: true}); - - const filePatch = this.state.filePatch; - if (filePatch.hasTypechange() && filePatch.getStatus() === 'added') { - await this.repositoryObserver.getActiveModel().stageFileSymlinkChange(this.props.filePath); - } else { - await this.repositoryObserver.getActiveModel().stageFiles([this.props.filePath]); - } - this.props.switchboard.didFinishStageOperation({stage: true, symlink: true}); - } - - async unstageSymlinkChange() { - this.props.switchboard.didBeginStageOperation({unstage: true, symlink: true}); - - const filePatch = this.state.filePatch; - if (filePatch.hasTypechange() && filePatch.getStatus() === 'deleted') { - await this.repositoryObserver.getActiveModel().stageFileSymlinkChange(this.props.filePath); - } else { - await this.repositoryObserver.getActiveModel().unstageFiles([this.props.filePath]); - } - this.props.switchboard.didFinishStageOperation({unstage: true, symlink: true}); - } - - stageOrUnstageSymlinkChange() { - const stagingStatus = this.state.stagingStatus; - if (stagingStatus === 'unstaged') { - return this.stageSymlinkChange(); - } else if (stagingStatus === 'staged') { - return this.unstageSymlinkChange(); - } else { - throw new Error(`Unknown stagingStatus: ${stagingStatus}`); - } - } - - attemptHunkStageOperation(hunk) { - if (this.stagingOperationInProgress) { - return; - } - - this.stagingOperationInProgress = true; - this.props.switchboard.getChangePatchPromise().then(() => { - this.stagingOperationInProgress = false; - }); - - this.stageOrUnstageHunk(hunk); - } - - attemptFileStageOperation() { - if (this.stagingOperationInProgress) { - return; - } - - this.stagingOperationInProgress = true; - this.props.switchboard.getChangePatchPromise().then(() => { - this.stagingOperationInProgress = false; - }); - - this.stageOrUnstageFile(); - } - - attemptModeStageOperation() { - if (this.stagingOperationInProgress) { - return; - } - - this.stagingOperationInProgress = true; - this.props.switchboard.getChangePatchPromise().then(() => { - this.stagingOperationInProgress = false; - }); - - this.stageOrUnstageModeChange(); - } - - attemptSymlinkStageOperation() { - if (this.stagingOperationInProgress) { - return; - } - - this.stagingOperationInProgress = true; - this.props.switchboard.getChangePatchPromise().then(() => { - this.stagingOperationInProgress = false; - }); - - this.stageOrUnstageSymlinkChange(); - } - - async stageLines(lines) { - this.props.switchboard.didBeginStageOperation({stage: true, line: true}); - - await this.repositoryObserver.getActiveModel().applyPatchToIndex( - this.state.filePatch.getStagePatchForLines(lines), - ); - - this.props.switchboard.didFinishStageOperation({stage: true, line: true}); - } - - async unstageLines(lines) { - this.props.switchboard.didBeginStageOperation({unstage: true, line: true}); - - await this.repositoryObserver.getActiveModel().applyPatchToIndex( - this.state.filePatch.getUnstagePatchForLines(lines), - ); - - this.props.switchboard.didFinishStageOperation({unstage: true, line: true}); - } - - stageOrUnstageLines(lines) { - const stagingStatus = this.state.stagingStatus; - if (stagingStatus === 'unstaged') { - return this.stageLines(lines); - } else if (stagingStatus === 'staged') { - return this.unstageLines(lines); - } else { - throw new Error(`Unknown stagingStatus: ${stagingStatus}`); - } - } - - attemptLineStageOperation(lines) { - if (this.stagingOperationInProgress) { - return; - } - - this.stagingOperationInProgress = true; - this.props.switchboard.getChangePatchPromise().then(() => { - this.stagingOperationInProgress = false; - }); - - this.stageOrUnstageLines(lines); - } - - didSurfaceFile() { - if (this.props.didSurfaceFile) { - this.props.didSurfaceFile(this.props.filePath, this.state.stagingStatus); - } - } - - async diveIntoCorrespondingFilePatch() { - const stagingStatus = this.isStaged() ? 'unstaged' : 'staged'; - const filePatch = await this.getFilePatchForPath(this.props.filePath, stagingStatus === 'staged'); - this.props.quietlySelectItem(this.props.filePath, stagingStatus); - this.setState({filePatch, stagingStatus}); - } - - isStaged() { - return this.state.stagingStatus === 'staged'; - } - - isEmpty() { - return !this.state.filePatch || this.state.filePatch.getHunks().length === 0; - } - - focus() { - if (this.filePatchView) { - this.filePatchView.focus(); - } - } - - wasActivated(isStillActive) { - process.nextTick(() => { - isStillActive() && this.focus(); - }); - } - - async openCurrentFile({lineNumber} = {}) { - const [textEditor] = await this.props.openFiles([this.props.filePath]); - const position = new Point(lineNumber ? lineNumber - 1 : 0, 0); - textEditor.scrollToBufferPosition(position, {center: true}); - textEditor.setCursorBufferPosition(position); - return textEditor; - } - - discardLines(lines) { - return this.props.discardLines(this.state.filePatch, lines, this.repositoryObserver.getActiveModel()); - } - - undoLastDiscard() { - return this.props.undoLastDiscard(this.props.filePath, this.repositoryObserver.getActiveModel()); - } - - hasUndoHistory() { - return this.repositoryObserver.getActiveModel().hasDiscardHistory(this.props.filePath); - } - - destroy() { - this.destroyed = true; - this.subscriptions.dispose(); - this.repositoryObserver.destroy(); - this.emitter.emit('did-destroy'); - } -} diff --git a/lib/models/presented-file-patch.js b/lib/models/presented-file-patch.js deleted file mode 100644 index 8fb5c00437..0000000000 --- a/lib/models/presented-file-patch.js +++ /dev/null @@ -1,103 +0,0 @@ -import {Point} from 'atom'; - -export default class PresentedFilePatch { - constructor(filePatch) { - this.filePatch = filePatch; - - this.hunkStartPositions = []; - this.bufferPositions = { - unchanged: [], - added: [], - deleted: [], - nonewline: [], - }; - this.hunkIndex = new Map(); - this.lineIndex = new Map(); - this.lineReverseIndex = new Map(); - this.maxLineNumberWidth = 0; - - let bufferLine = 0; - this.text = filePatch.getHunks().reduce((str, hunk) => { - this.hunkStartPositions.push(fromBufferPosition(new Point(bufferLine, 0))); - - return hunk.getLines().reduce((hunkStr, line) => { - hunkStr += line.getText() + '\n'; - this.hunkIndex.set(bufferLine, hunk); - this.lineIndex.set(bufferLine, line); - this.lineReverseIndex.set(line, bufferLine); - - if (line.getOldLineNumber().toString().length > this.maxLineNumberWidth) { - this.maxLineNumberWidth = line.getOldLineNumber().toString().length; - } - if (line.getNewLineNumber().toString().length > this.maxLineNumberWidth) { - this.maxLineNumberWidth = line.getNewLineNumber().toString().length; - } - - this.bufferPositions[line.getStatus()].push( - fromBufferPosition(new Point(bufferLine, 0)), - ); - - bufferLine++; - return hunkStr; - }, str); - }, ''); - } - - getFilePatch() { - return this.filePatch; - } - - getText() { - return this.text; - } - - getHunkStartPositions() { - return this.hunkStartPositions; - } - - getUnchangedBufferPositions() { - return this.bufferPositions.unchanged; - } - - getAddedBufferPositions() { - return this.bufferPositions.added; - } - - getDeletedBufferPositions() { - return this.bufferPositions.deleted; - } - - getNoNewlineBufferPositions() { - return this.bufferPositions.nonewline; - } - - getHunkAt(bufferRow) { - return this.hunkIndex.get(bufferRow); - } - - getLineAt(bufferRow) { - return this.lineIndex.get(bufferRow); - } - - getOldLineNumberAt(bufferRow) { - const line = this.getLineAt(bufferRow); - return line ? line.getOldLineNumber() : -1; - } - - getNewLineNumberAt(bufferRow) { - const line = this.getLineAt(bufferRow); - return line ? line.getNewLineNumber() : -1; - } - - getPositionForLine(line) { - const bufferRow = this.lineReverseIndex.get(line); - if (bufferRow === undefined) { - return null; - } - return new fromBufferPosition(new Point(bufferRow, 0)); - } - - getMaxLineNumberWidth() { - return this.maxLineNumberWidth; - } -} diff --git a/lib/views/file-patch-view.old.js b/lib/views/file-patch-view.old.js deleted file mode 100644 index e8067025dc..0000000000 --- a/lib/views/file-patch-view.old.js +++ /dev/null @@ -1,716 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import {CompositeDisposable, Disposable} from 'event-kit'; -import cx from 'classnames'; -import bytes from 'bytes'; - -import HunkView from './hunk-view'; -import SimpleTooltip from '../atom/simple-tooltip'; -import Commands, {Command} from '../atom/commands'; -import FilePatchSelection from '../models/file-patch-selection'; -import Switchboard from '../switchboard'; -import RefHolder from '../models/ref-holder'; -import {autobind} from '../helpers'; - -const executableText = { - 100644: 'non executable 100644', - 100755: 'executable 100755', -}; - -export default class FilePatchView extends React.Component { - static propTypes = { - commandRegistry: PropTypes.object.isRequired, - tooltips: PropTypes.object.isRequired, - filePath: PropTypes.string.isRequired, - hunks: PropTypes.arrayOf(PropTypes.object).isRequired, - executableModeChange: PropTypes.shape({ - oldMode: PropTypes.string.isRequired, - newMode: PropTypes.string.isRequired, - }), - symlinkChange: PropTypes.shape({ - oldSymlink: PropTypes.string, - newSymlink: PropTypes.string, - typechange: PropTypes.bool, - filePatchStatus: PropTypes.string, - }), - stagingStatus: PropTypes.oneOf(['unstaged', 'staged']).isRequired, - isPartiallyStaged: PropTypes.bool.isRequired, - hasUndoHistory: PropTypes.bool.isRequired, - attemptLineStageOperation: PropTypes.func.isRequired, - attemptHunkStageOperation: PropTypes.func.isRequired, - attemptFileStageOperation: PropTypes.func.isRequired, - attemptModeStageOperation: PropTypes.func.isRequired, - attemptSymlinkStageOperation: PropTypes.func.isRequired, - discardLines: PropTypes.func.isRequired, - undoLastDiscard: PropTypes.func.isRequired, - openCurrentFile: PropTypes.func.isRequired, - didSurfaceFile: PropTypes.func.isRequired, - didDiveIntoCorrespondingFilePatch: PropTypes.func.isRequired, - switchboard: PropTypes.instanceOf(Switchboard), - displayLargeDiffMessage: PropTypes.bool, - byteCount: PropTypes.number, - handleShowDiffClick: PropTypes.func.isRequired, - } - - static defaultProps = { - switchboard: new Switchboard(), - } - - constructor(props, context) { - super(props, context); - autobind( - this, - 'registerCommands', 'renderButtonGroup', 'renderExecutableModeChange', 'renderSymlinkChange', 'contextMenuOnItem', - 'mousedownOnLine', 'mousemoveOnLine', 'mouseup', 'togglePatchSelectionMode', 'selectNext', 'selectNextElement', - 'selectToNext', 'selectPrevious', 'selectPreviousElement', 'selectToPrevious', 'selectFirst', 'selectToFirst', - 'selectLast', 'selectToLast', 'selectAll', 'didConfirm', 'didMoveRight', 'focus', 'openFile', 'stageOrUnstageAll', - 'stageOrUnstageModeChange', 'stageOrUnstageSymlinkChange', 'discardSelection', - ); - - this.mouseSelectionInProgress = false; - this.disposables = new CompositeDisposable(); - - this.refElement = new RefHolder(); - - this.state = { - selection: new FilePatchSelection(this.props.hunks), - }; - } - - componentDidMount() { - window.addEventListener('mouseup', this.mouseup); - this.disposables.add(new Disposable(() => window.removeEventListener('mouseup', this.mouseup))); - } - - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - const hunksChanged = this.props.hunks.length !== nextProps.hunks.length || - this.props.hunks.some((hunk, index) => hunk !== nextProps.hunks[index]); - - if (hunksChanged) { - this.setState(prevState => { - return { - selection: prevState.selection.updateHunks(nextProps.hunks), - }; - }, () => { - nextProps.switchboard.didChangePatch(); - }); - } - } - - shouldComponentUpdate(nextProps, nextState) { - const deepProps = { - executableModeChange: ['oldMode', 'newMode'], - symlinkChange: ['oldSymlink', 'newSymlink', 'typechange', 'filePatchStatus'], - }; - - for (const propKey in this.constructor.propTypes) { - const subKeys = deepProps[propKey]; - const oldProp = this.props[propKey]; - const newProp = nextProps[propKey]; - - if (subKeys) { - const oldExists = (oldProp !== null && oldProp !== undefined); - const newExists = (newProp !== null && newProp !== undefined); - - if (oldExists !== newExists) { - return true; - } - - if (!oldExists && !newExists) { - continue; - } - - if (subKeys.some(subKey => this.props[propKey][subKey] !== nextProps[propKey][subKey])) { - return true; - } - } else { - if (oldProp !== newProp) { - return true; - } - } - } - - if (this.state.selection !== nextState.selection) { - return true; - } - - return false; - } - - renderEmptyDiffMessage() { - return ( -
- File has no contents -
- ); - } - - renderLargeDiffMessage() { - const human = bytes.format(this.props.byteCount); - - return ( -
-

- This is a large {human} diff. For performance reasons, it is not rendered by default. -

- -
- ); - } - - renderHunks() { - // Render hunks for symlink change only if 'typechange' (which indicates symlink change AND file content change) - const {symlinkChange} = this.props; - if (symlinkChange && !symlinkChange.typechange) { return null; } - - const selectedHunks = this.state.selection.getSelectedHunks(); - const selectedLines = this.state.selection.getSelectedLines(); - const headHunk = this.state.selection.getHeadHunk(); - const headLine = this.state.selection.getHeadLine(); - const hunkSelectionMode = this.state.selection.getMode() === 'hunk'; - - const unstaged = this.props.stagingStatus === 'unstaged'; - const stageButtonLabelPrefix = unstaged ? 'Stage' : 'Unstage'; - - if (this.props.hunks.length === 0) { - return this.renderEmptyDiffMessage(); - } - - return this.props.hunks.map(hunk => { - const isSelected = selectedHunks.has(hunk); - let stageButtonSuffix = (hunkSelectionMode || !isSelected) ? ' Hunk' : ' Selection'; - if (selectedHunks.size > 1 && selectedHunks.has(hunk)) { - stageButtonSuffix += 's'; - } - const stageButtonLabel = stageButtonLabelPrefix + stageButtonSuffix; - const discardButtonLabel = 'Discard' + stageButtonSuffix; - - return ( - this.mousedownOnHeader(e, hunk)} - mousedownOnLine={this.mousedownOnLine} - mousemoveOnLine={this.mousemoveOnLine} - contextMenuOnItem={this.contextMenuOnItem} - didClickStageButton={() => this.didClickStageButtonForHunk(hunk)} - didClickDiscardButton={() => this.didClickDiscardButtonForHunk(hunk)} - /> - ); - }); - - } - - render() { - const unstaged = this.props.stagingStatus === 'unstaged'; - return ( -
- - {this.registerCommands()} - -
- - {unstaged ? 'Unstaged Changes for ' : 'Staged Changes for '} - {this.props.filePath} - - {this.renderButtonGroup()} -
- -
- {this.props.executableModeChange && this.renderExecutableModeChange(unstaged)} - {this.props.symlinkChange && this.renderSymlinkChange(unstaged)} - {this.props.displayLargeDiffMessage ? this.renderLargeDiffMessage() : this.renderHunks()} -
-
- ); - } - - registerCommands() { - return ( -
- - - - - - - - - - - - - - - - - this.props.isPartiallyStaged && this.props.didDiveIntoCorrespondingFilePatch()} - /> - - this.props.hasUndoHistory && this.props.undoLastDiscard()} - /> - {this.props.executableModeChange && - } - {this.props.symlinkChange && - } - - - - this.props.hasUndoHistory && this.props.undoLastDiscard()} - /> - - -
- ); - } - - renderButtonGroup() { - const unstaged = this.props.stagingStatus === 'unstaged'; - - return ( - - {this.props.hasUndoHistory && unstaged ? ( - - ) : null} - {this.props.isPartiallyStaged || !this.props.hunks.length ? ( - - - ) : null } - - ); - } - - renderExecutableModeChange(unstaged) { - const {executableModeChange} = this.props; - return ( -
-
-
-

Mode change

-
- -
-
-
- File changed mode - - -
-
-
- ); - } - - renderSymlinkChange(unstaged) { - const {symlinkChange} = this.props; - const {oldSymlink, newSymlink} = symlinkChange; - - if (oldSymlink && !newSymlink) { - return ( -
-
-
-

Symlink deleted

-
- -
-
-
- Symlink - - to {oldSymlink} - - deleted. -
-
-
- ); - } else if (!oldSymlink && newSymlink) { - return ( -
-
-
-

Symlink added

-
- -
-
-
- Symlink - - to {newSymlink} - - created. -
-
-
- ); - } else if (oldSymlink && newSymlink) { - return ( -
-
-
-

Symlink changed

-
- -
-
-
- - from {oldSymlink} - - - to {newSymlink} - -
-
-
- ); - } else { - return new Error('Symlink change detected, but missing symlink paths'); - } - } - - componentWillUnmount() { - this.disposables.dispose(); - } - - contextMenuOnItem(event, hunk, line) { - const resend = () => { - const newEvent = new MouseEvent(event.type, event); - setImmediate(() => event.target.parentNode.dispatchEvent(newEvent)); - }; - - const mode = this.state.selection.getMode(); - if (mode === 'hunk' && !this.state.selection.getSelectedHunks().has(hunk)) { - event.stopPropagation(); - - this.setState(prevState => { - return {selection: prevState.selection.selectHunk(hunk, event.shiftKey)}; - }, resend); - } else if (mode === 'line' && !this.state.selection.getSelectedLines().has(line)) { - event.stopPropagation(); - - this.setState(prevState => { - return {selection: prevState.selection.selectLine(line, event.shiftKey)}; - }, resend); - } - } - - mousedownOnHeader(event, hunk) { - if (event.button !== 0) { return; } - const windows = process.platform === 'win32'; - if (event.ctrlKey && !windows) { return; } // simply open context menu - - this.mouseSelectionInProgress = true; - event.persist && event.persist(); - - this.setState(prevState => { - let selection = prevState.selection; - if (event.metaKey || (event.ctrlKey && windows)) { - if (selection.getMode() === 'hunk') { - selection = selection.addOrSubtractHunkSelection(hunk); - } else { - // TODO: optimize - selection = hunk.getLines().reduce( - (current, line) => current.addOrSubtractLineSelection(line).coalesce(), - selection, - ); - } - } else if (event.shiftKey) { - if (selection.getMode() === 'hunk') { - selection = selection.selectHunk(hunk, true); - } else { - const hunkLines = hunk.getLines(); - const tailIndex = selection.getLineSelectionTailIndex(); - const selectedHunkAfterTail = tailIndex < hunkLines[0].diffLineNumber; - if (selectedHunkAfterTail) { - selection = selection.selectLine(hunkLines[hunkLines.length - 1], true); - } else { - selection = selection.selectLine(hunkLines[0], true); - } - } - } else { - selection = selection.selectHunk(hunk, false); - } - - return {selection}; - }); - } - - mousedownOnLine(event, hunk, line) { - if (event.button !== 0) { return; } - const windows = process.platform === 'win32'; - if (event.ctrlKey && !windows) { return; } // simply open context menu - - this.mouseSelectionInProgress = true; - event.persist && event.persist(); - - this.setState(prevState => { - let selection = prevState.selection; - - if (event.metaKey || (event.ctrlKey && windows)) { - if (selection.getMode() === 'hunk') { - selection = selection.addOrSubtractHunkSelection(hunk); - } else { - selection = selection.addOrSubtractLineSelection(line); - } - } else if (event.shiftKey) { - if (selection.getMode() === 'hunk') { - selection = selection.selectHunk(hunk, true); - } else { - selection = selection.selectLine(line, true); - } - } else if (event.detail === 1) { - selection = selection.selectLine(line, false); - } else if (event.detail === 2) { - selection = selection.selectHunk(hunk, false); - } - - return {selection}; - }); - } - - mousemoveOnLine(event, hunk, line) { - if (!this.mouseSelectionInProgress) { return; } - - this.setState(prevState => { - let selection = null; - if (prevState.selection.getMode() === 'hunk') { - selection = prevState.selection.selectHunk(hunk, true); - } else { - selection = prevState.selection.selectLine(line, true); - } - return {selection}; - }); - } - - mouseup() { - this.mouseSelectionInProgress = false; - this.setState(prevState => { - return {selection: prevState.selection.coalesce()}; - }); - } - - togglePatchSelectionMode() { - this.setState(prevState => ({selection: prevState.selection.toggleMode()})); - } - - getPatchSelectionMode() { - return this.state.selection.getMode(); - } - - getSelectedHunks() { - return this.state.selection.getSelectedHunks(); - } - - getSelectedLines() { - return this.state.selection.getSelectedLines(); - } - - selectNext() { - this.setState(prevState => ({selection: prevState.selection.selectNext()})); - } - - selectNextElement() { - if (this.state.selection.isEmpty() && this.props.didSurfaceFile) { - this.props.didSurfaceFile(); - } else { - this.setState(prevState => ({selection: prevState.selection.jumpToNextHunk()})); - } - } - - selectToNext() { - this.setState(prevState => { - return {selection: prevState.selection.selectNext(true).coalesce()}; - }); - } - - selectPrevious() { - this.setState(prevState => ({selection: prevState.selection.selectPrevious()})); - } - - selectPreviousElement() { - if (this.state.selection.isEmpty() && this.props.didSurfaceFile) { - this.props.didSurfaceFile(); - } else { - this.setState(prevState => ({selection: prevState.selection.jumpToPreviousHunk()})); - } - } - - selectToPrevious() { - this.setState(prevState => { - return {selection: prevState.selection.selectPrevious(true).coalesce()}; - }); - } - - selectFirst() { - this.setState(prevState => ({selection: prevState.selection.selectFirst()})); - } - - selectToFirst() { - this.setState(prevState => ({selection: prevState.selection.selectFirst(true)})); - } - - selectLast() { - this.setState(prevState => ({selection: prevState.selection.selectLast()})); - } - - selectToLast() { - this.setState(prevState => ({selection: prevState.selection.selectLast(true)})); - } - - selectAll() { - return new Promise(resolve => { - this.setState(prevState => ({selection: prevState.selection.selectAll()}), resolve); - }); - } - - getNextHunkUpdatePromise() { - return this.state.selection.getNextUpdatePromise(); - } - - didClickStageButtonForHunk(hunk) { - if (this.state.selection.getSelectedHunks().has(hunk)) { - this.props.attemptLineStageOperation(this.state.selection.getSelectedLines()); - } else { - this.setState(prevState => ({selection: prevState.selection.selectHunk(hunk)}), () => { - this.props.attemptHunkStageOperation(hunk); - }); - } - } - - didClickDiscardButtonForHunk(hunk) { - if (this.state.selection.getSelectedHunks().has(hunk)) { - this.discardSelection(); - } else { - this.setState(prevState => ({selection: prevState.selection.selectHunk(hunk)}), () => { - this.discardSelection(); - }); - } - } - - didConfirm() { - return this.didClickStageButtonForHunk([...this.state.selection.getSelectedHunks()][0]); - } - - didMoveRight() { - if (this.props.didSurfaceFile) { - this.props.didSurfaceFile(); - } - } - - focus() { - this.refElement.get().focus(); - } - - openFile() { - let lineNumber = 0; - const firstSelectedLine = Array.from(this.state.selection.getSelectedLines())[0]; - if (firstSelectedLine && firstSelectedLine.newLineNumber > -1) { - lineNumber = firstSelectedLine.newLineNumber; - } else { - const firstSelectedHunk = Array.from(this.state.selection.getSelectedHunks())[0]; - lineNumber = firstSelectedHunk ? firstSelectedHunk.getNewStartRow() : 0; - } - return this.props.openCurrentFile({lineNumber}); - } - - stageOrUnstageAll() { - this.props.attemptFileStageOperation(); - } - - stageOrUnstageModeChange() { - this.props.attemptModeStageOperation(); - } - - stageOrUnstageSymlinkChange() { - this.props.attemptSymlinkStageOperation(); - } - - discardSelection() { - const selectedLines = this.state.selection.getSelectedLines(); - return selectedLines.size ? this.props.discardLines(selectedLines) : null; - } - - goToDiffLine(lineNumber) { - this.setState(prevState => ({selection: prevState.selection.goToDiffLine(lineNumber)})); - } -} diff --git a/lib/views/hunk-view.old.js b/lib/views/hunk-view.old.js deleted file mode 100644 index 794f4887f2..0000000000 --- a/lib/views/hunk-view.old.js +++ /dev/null @@ -1,143 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import SimpleTooltip from '../atom/simple-tooltip'; -import ContextMenuInterceptor from '../context-menu-interceptor'; -import {autobind} from '../helpers'; - -export default class HunkView extends React.Component { - static propTypes = { - tooltips: PropTypes.object.isRequired, - hunk: PropTypes.object.isRequired, - headHunk: PropTypes.object, - headLine: PropTypes.object, - isSelected: PropTypes.bool.isRequired, - selectedLines: PropTypes.instanceOf(Set).isRequired, - hunkSelectionMode: PropTypes.bool.isRequired, - stageButtonLabel: PropTypes.string.isRequired, - discardButtonLabel: PropTypes.string.isRequired, - unstaged: PropTypes.bool.isRequired, - mousedownOnHeader: PropTypes.func.isRequired, - mousedownOnLine: PropTypes.func.isRequired, - mousemoveOnLine: PropTypes.func.isRequired, - contextMenuOnItem: PropTypes.func.isRequired, - didClickStageButton: PropTypes.func.isRequired, - didClickDiscardButton: PropTypes.func.isRequired, - } - - constructor(props, context) { - super(props, context); - autobind(this, 'mousedownOnLine', 'mousemoveOnLine', 'registerLineElement'); - - this.lineElements = new WeakMap(); - this.lastMousemoveLine = null; - } - - render() { - const hunkSelectedClass = this.props.isSelected ? 'is-selected' : ''; - const hunkModeClass = this.props.hunkSelectionMode ? 'is-hunkMode' : ''; - - return ( -
{ this.element = e; }}> -
this.props.mousedownOnHeader(e)}> - - {this.props.hunk.getHeader().trim()} {this.props.hunk.getSectionHeading().trim()} - - - {this.props.unstaged && - -
- {this.props.hunk.getLines().map((line, idx) => ( - this.props.contextMenuOnItem(e, this.props.hunk, clickedLine)} - /> - ))} -
- ); - } - - mousedownOnLine(event, line) { - this.props.mousedownOnLine(event, this.props.hunk, line); - } - - mousemoveOnLine(event, line) { - if (line !== this.lastMousemoveLine) { - this.lastMousemoveLine = line; - this.props.mousemoveOnLine(event, this.props.hunk, line); - } - } - - registerLineElement(line, element) { - this.lineElements.set(line, element); - } - - componentDidUpdate(prevProps) { - if (prevProps.headLine !== this.props.headLine) { - if (this.props.headLine && this.lineElements.has(this.props.headLine)) { - this.lineElements.get(this.props.headLine).scrollIntoViewIfNeeded(); - } - } - - if (prevProps.headHunk !== this.props.headHunk) { - if (this.props.headHunk === this.props.hunk) { - this.element.scrollIntoViewIfNeeded(); - } - } - } -} - -class LineView extends React.Component { - static propTypes = { - line: PropTypes.object.isRequired, - isSelected: PropTypes.bool.isRequired, - mousedown: PropTypes.func.isRequired, - mousemove: PropTypes.func.isRequired, - contextMenuOnItem: PropTypes.func.isRequired, - registerLineElement: PropTypes.func.isRequired, - } - - render() { - const line = this.props.line; - const oldLineNumber = line.getOldLineNumber() === -1 ? ' ' : line.getOldLineNumber(); - const newLineNumber = line.getNewLineNumber() === -1 ? ' ' : line.getNewLineNumber(); - const lineSelectedClass = this.props.isSelected ? 'is-selected' : ''; - - return ( - this.props.contextMenuOnItem(event, line)}> -
this.props.mousedown(event, line)} - onMouseMove={event => this.props.mousemove(event, line)} - ref={e => this.props.registerLineElement(line, e)}> -
{oldLineNumber}
-
{newLineNumber}
-
- {line.getOrigin()} - {line.getText()} -
-
-
- ); - } -} diff --git a/test/controllers/file-patch-controller.test.old.js b/test/controllers/file-patch-controller.test.old.js deleted file mode 100644 index a7b075e8ec..0000000000 --- a/test/controllers/file-patch-controller.test.old.js +++ /dev/null @@ -1,811 +0,0 @@ -import React from 'react'; -import {shallow, mount} from 'enzyme'; -import until from 'test-until'; - -import fs from 'fs'; -import path from 'path'; - -import {cloneRepository, buildRepository} from '../helpers'; -import FilePatch from '../../lib/models/file-patch'; -import FilePatchController from '../../lib/controllers/file-patch-controller'; -import Hunk from '../../lib/models/hunk'; -import HunkLine from '../../lib/models/hunk-line'; -import ResolutionProgress from '../../lib/models/conflicts/resolution-progress'; -import Switchboard from '../../lib/switchboard'; - -function createFilePatch(oldFilePath, newFilePath, status, hunks) { - const oldFile = new FilePatch.File({path: oldFilePath}); - const newFile = new FilePatch.File({path: newFilePath}); - const patch = new FilePatch.Patch({status, hunks}); - - return new FilePatch(oldFile, newFile, patch); -} - -let atomEnv, commandRegistry, tooltips, deserializers; -let switchboard, getFilePatchForPath; -let discardLines, didSurfaceFile, didDiveIntoFilePath, quietlySelectItem, undoLastDiscard, openFiles, getRepositoryForWorkdir; -let getSelectedStagingViewItems, resolutionProgress; - -function createComponent(repository, filePath) { - atomEnv = global.buildAtomEnvironment(); - commandRegistry = atomEnv.commands; - deserializers = atomEnv.deserializers; - tooltips = atomEnv.tooltips; - - switchboard = new Switchboard(); - - discardLines = sinon.spy(); - didSurfaceFile = sinon.spy(); - didDiveIntoFilePath = sinon.spy(); - quietlySelectItem = sinon.spy(); - undoLastDiscard = sinon.spy(); - openFiles = sinon.spy(); - getSelectedStagingViewItems = sinon.spy(); - - getRepositoryForWorkdir = () => repository; - resolutionProgress = new ResolutionProgress(); - - FilePatchController.resetConfirmedLargeFilePatches(); - - return ( - - ); -} - -async function refreshRepository(wrapper) { - const workDir = wrapper.prop('workingDirectoryPath'); - const repository = wrapper.prop('getRepositoryForWorkdir')(workDir); - - const promise = wrapper.prop('switchboard').getFinishRepositoryRefreshPromise(); - repository.refresh(); - await promise; - wrapper.update(); -} - -describe('FilePatchController', function() { - afterEach(function() { - atomEnv.destroy(); - }); - - describe('unit tests', function() { - let workdirPath, repository, filePath, component; - beforeEach(async function() { - workdirPath = await cloneRepository('multi-line-file'); - repository = await buildRepository(workdirPath); - filePath = 'sample.js'; - component = createComponent(repository, filePath); - - getFilePatchForPath = sinon.stub(repository, 'getFilePatchForPath'); - }); - - describe('when the FilePatch is too large', function() { - it('renders a confirmation widget', async function() { - const hunk1 = new Hunk(0, 0, 1, 1, '', [ - new HunkLine('line-1', 'added', 1, 1), - new HunkLine('line-2', 'added', 2, 2), - new HunkLine('line-3', 'added', 3, 3), - new HunkLine('line-4', 'added', 4, 4), - new HunkLine('line-5', 'added', 5, 5), - new HunkLine('line-6', 'added', 6, 6), - ]); - const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk1]); - - getFilePatchForPath.returns(filePatch); - - const wrapper = mount(React.cloneElement(component, {largeDiffByteThreshold: 5})); - - await assert.async.match(wrapper.text(), /large .+ diff/); - }); - - it('renders the full diff when the confirmation is clicked', async function() { - const hunk = new Hunk(0, 0, 1, 1, '', [ - new HunkLine('line-1', 'added', 1, 1), - new HunkLine('line-2', 'added', 2, 2), - new HunkLine('line-3', 'added', 3, 3), - new HunkLine('line-4', 'added', 4, 4), - new HunkLine('line-5', 'added', 5, 5), - new HunkLine('line-6', 'added', 6, 6), - ]); - const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk]); - getFilePatchForPath.returns(filePatch); - - const wrapper = mount(React.cloneElement(component, {largeDiffByteThreshold: 5})); - - await assert.async.isTrue(wrapper.update().find('.large-file-patch').exists()); - wrapper.find('.large-file-patch').find('button').simulate('click'); - - assert.isTrue(wrapper.find('HunkView').exists()); - }); - - it('renders the full diff if the file has been confirmed before', async function() { - const hunk = new Hunk(0, 0, 1, 1, '', [ - new HunkLine('line-1', 'added', 1, 1), - new HunkLine('line-2', 'added', 2, 2), - new HunkLine('line-3', 'added', 3, 3), - new HunkLine('line-4', 'added', 4, 4), - new HunkLine('line-5', 'added', 5, 5), - new HunkLine('line-6', 'added', 6, 6), - ]); - const filePatch1 = createFilePatch(filePath, filePath, 'modified', [hunk]); - const filePatch2 = createFilePatch('b.txt', 'b.txt', 'modified', [hunk]); - - getFilePatchForPath.returns(filePatch1); - - const wrapper = mount(React.cloneElement(component, { - filePath: filePatch1.getPath(), largeDiffByteThreshold: 5, - })); - - await assert.async.isTrue(wrapper.update().find('.large-file-patch').exists()); - wrapper.find('.large-file-patch').find('button').simulate('click'); - assert.isTrue(wrapper.find('HunkView').exists()); - - getFilePatchForPath.returns(filePatch2); - wrapper.setProps({filePath: filePatch2.getPath()}); - await assert.async.isTrue(wrapper.update().find('.large-file-patch').exists()); - - getFilePatchForPath.returns(filePatch1); - wrapper.setProps({filePath: filePatch1.getPath()}); - assert.isTrue(wrapper.update().find('HunkView').exists()); - }); - }); - - describe('onRepoRefresh', function() { - it('sets the correct FilePatch as state', async function() { - repository.getFilePatchForPath.restore(); - fs.writeFileSync(path.join(workdirPath, filePath), 'change', 'utf8'); - - const wrapper = mount(component); - - await assert.async.isNotNull(wrapper.state('filePatch')); - - const originalFilePatch = wrapper.state('filePatch'); - assert.equal(wrapper.state('stagingStatus'), 'unstaged'); - - fs.writeFileSync(path.join(workdirPath, 'file.txt'), 'change\nand again!', 'utf8'); - await refreshRepository(wrapper); - - assert.notEqual(originalFilePatch, wrapper.state('filePatch')); - assert.equal(wrapper.state('stagingStatus'), 'unstaged'); - }); - }); - - it('renders FilePatchView only if FilePatch has hunks', async function() { - const emptyFilePatch = createFilePatch(filePath, filePath, 'modified', []); - getFilePatchForPath.returns(emptyFilePatch); - - const wrapper = mount(component); - - assert.isTrue(wrapper.find('FilePatchView').exists()); - assert.isTrue(wrapper.find('FilePatchView').text().includes('File has no contents')); - - const hunk1 = new Hunk(0, 0, 1, 1, '', [new HunkLine('line-1', 'added', 1, 1)]); - const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk1]); - getFilePatchForPath.returns(filePatch); - - wrapper.instance().onRepoRefresh(repository); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - assert.isTrue(wrapper.find('HunkView').text().includes('@@ -0,1 +0,1 @@')); - }); - - it('updates the FilePatch after a repo update', async function() { - const hunk1 = new Hunk(5, 5, 2, 1, '', [new HunkLine('line-1', 'added', -1, 5)]); - const hunk2 = new Hunk(8, 8, 1, 1, '', [new HunkLine('line-5', 'deleted', 8, -1)]); - const filePatch0 = createFilePatch(filePath, filePath, 'modified', [hunk1, hunk2]); - getFilePatchForPath.returns(filePatch0); - - const wrapper = shallow(component); - - let view0; - await until(() => { - view0 = wrapper.update().find('FilePatchView').shallow(); - return view0.find({hunk: hunk1}).exists(); - }); - assert.isTrue(view0.find({hunk: hunk2}).exists()); - - const hunk3 = new Hunk(8, 8, 1, 1, '', [new HunkLine('line-10', 'modified', 10, 10)]); - const filePatch1 = createFilePatch(filePath, filePath, 'modified', [hunk1, hunk3]); - getFilePatchForPath.returns(filePatch1); - - wrapper.instance().onRepoRefresh(repository); - let view1; - await until(() => { - view1 = wrapper.update().find('FilePatchView').shallow(); - return view1.find({hunk: hunk3}).exists(); - }); - assert.isTrue(view1.find({hunk: hunk1}).exists()); - assert.isFalse(view1.find({hunk: hunk2}).exists()); - }); - - it('invokes a didSurfaceFile callback with the current file path', async function() { - const filePatch = createFilePatch(filePath, filePath, 'modified', [new Hunk(1, 1, 1, 3, '', [])]); - getFilePatchForPath.returns(filePatch); - - const wrapper = mount(component); - - await assert.async.isTrue(wrapper.update().find('Commands').exists()); - commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'core:move-right'); - assert.isTrue(didSurfaceFile.calledWith(filePath, 'unstaged')); - }); - - describe('openCurrentFile({lineNumber})', () => { - it('sets the cursor on the correct line of the opened text editor', async function() { - const editorSpy = { - relativePath: null, - scrollToBufferPosition: sinon.spy(), - setCursorBufferPosition: sinon.spy(), - }; - - const openFilesStub = relativePaths => { - assert.lengthOf(relativePaths, 1); - editorSpy.relativePath = relativePaths[0]; - return Promise.resolve([editorSpy]); - }; - - const hunk = new Hunk(5, 5, 2, 1, '', [new HunkLine('line-1', 'added', -1, 5)]); - const filePatch = createFilePatch(filePath, filePath, 'modified', [hunk]); - getFilePatchForPath.returns(filePatch); - - const wrapper = mount(React.cloneElement(component, {openFiles: openFilesStub})); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - - wrapper.find('LineView').simulate('mousedown', {button: 0, detail: 1}); - window.dispatchEvent(new MouseEvent('mouseup')); - commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'github:open-file'); - wrapper.update(); - - await assert.async.isTrue(editorSpy.setCursorBufferPosition.called); - - assert.isTrue(editorSpy.relativePath === filePath); - - const scrollCall = editorSpy.scrollToBufferPosition.firstCall; - assert.isTrue(scrollCall.args[0].isEqual([4, 0])); - assert.deepEqual(scrollCall.args[1], {center: true}); - - const cursorCall = editorSpy.setCursorBufferPosition.firstCall; - assert.isTrue(cursorCall.args[0].isEqual([4, 0])); - }); - }); - }); - - describe('integration tests', function() { - describe('handling symlink files', function() { - async function indexModeAndOid(repository, filename) { - const output = await repository.git.exec(['ls-files', '-s', '--', filename]); - if (output) { - const parts = output.split(' '); - return {mode: parts[0], oid: parts[1]}; - } else { - return null; - } - } - - it('unstages added lines that don\'t require symlink change', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - - // correctly handle symlinks on Windows - await repository.git.exec(['config', 'core.symlinks', 'true']); - - const deletedSymlinkAddedFilePath = 'symlink.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedSymlinkAddedFilePath)); - fs.writeFileSync(path.join(workingDirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n', 'utf8'); - - // Stage whole file - await repository.stageFiles([deletedSymlinkAddedFilePath]); - - const component = createComponent(repository, deletedSymlinkAddedFilePath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedSymlinkAddedFilePath, initialStagingStatus: 'staged'})); - - // index shows symlink deltion and added lines - assert.autocrlfEqual(await repository.readFileFromIndex(deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n'); - assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); - - // Unstage a couple added lines, but not all - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); - hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // index shows symlink deletions still staged, only a couple of lines have been unstaged - assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); - assert.autocrlfEqual(await repository.readFileFromIndex(deletedSymlinkAddedFilePath), 'qux\nbaz\nzoo\n'); - }); - - it('stages deleted lines that don\'t require symlink change', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - - const deletedFileAddedSymlinkPath = 'a.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedFileAddedSymlinkPath)); - fs.symlinkSync(path.join(workingDirPath, 'regular-file.txt'), path.join(workingDirPath, deletedFileAddedSymlinkPath)); - - const component = createComponent(repository, deletedFileAddedSymlinkPath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedFileAddedSymlinkPath, initialStagingStatus: 'unstaged'})); - - // index shows file is not a symlink, no deleted lines - assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); - assert.autocrlfEqual(await repository.readFileFromIndex(deletedFileAddedSymlinkPath), 'foo\nbar\nbaz\n\n'); - - // stage a couple of lines, but not all - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); - hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // index shows symlink change has not been staged, a couple of lines have been deleted - assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); - assert.autocrlfEqual(await repository.readFileFromIndex(deletedFileAddedSymlinkPath), 'foo\n\n'); - }); - - it('stages symlink change when staging added lines that depend on change', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - - // correctly handle symlinks on Windows - await repository.git.exec(['config', 'core.symlinks', 'true']); - - const deletedSymlinkAddedFilePath = 'symlink.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedSymlinkAddedFilePath)); - fs.writeFileSync(path.join(workingDirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n', 'utf8'); - - const component = createComponent(repository, deletedSymlinkAddedFilePath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedSymlinkAddedFilePath})); - - // index shows file is symlink - assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '120000'); - - // Stage a couple added lines, but not all - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); - hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // index no longer shows file is symlink (symlink has been deleted), now a regular file with contents - assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); - assert.autocrlfEqual(await repository.readFileFromIndex(deletedSymlinkAddedFilePath), 'foo\nbar\n'); - }); - - it('unstages symlink change when unstaging deleted lines that depend on change', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - - const deletedFileAddedSymlinkPath = 'a.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedFileAddedSymlinkPath)); - fs.symlinkSync(path.join(workingDirPath, 'regular-file.txt'), path.join(workingDirPath, deletedFileAddedSymlinkPath)); - await repository.stageFiles([deletedFileAddedSymlinkPath]); - - const component = createComponent(repository, deletedFileAddedSymlinkPath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedFileAddedSymlinkPath, initialStagingStatus: 'staged'})); - - // index shows file is symlink - assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '120000'); - - // unstage a couple of lines, but not all - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); - hunkView0.find('LineView').at(2).find('.github-HunkView-line').simulate('mousemove', {}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // index no longer shows file is symlink (symlink creation has been unstaged), shows contents of file that existed prior to symlink - assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); - assert.autocrlfEqual(await repository.readFileFromIndex(deletedFileAddedSymlinkPath), 'bar\nbaz\n'); - }); - - it('stages file deletion when all deleted lines are staged', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - await repository.getLoadPromise(); - - const deletedFileAddedSymlinkPath = 'a.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedFileAddedSymlinkPath)); - fs.symlinkSync(path.join(workingDirPath, 'regular-file.txt'), path.join(workingDirPath, deletedFileAddedSymlinkPath)); - - const component = createComponent(repository, deletedFileAddedSymlinkPath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedFileAddedSymlinkPath})); - - assert.equal((await indexModeAndOid(repository, deletedFileAddedSymlinkPath)).mode, '100644'); - - // stage all deleted lines - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('.github-HunkView-title').simulate('click'); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // File is not on index, file deletion has been staged - assert.isNull(await indexModeAndOid(repository, deletedFileAddedSymlinkPath)); - const {stagedFiles, unstagedFiles} = await repository.getStatusesForChangedFiles(); - assert.equal(unstagedFiles[deletedFileAddedSymlinkPath], 'added'); - assert.equal(stagedFiles[deletedFileAddedSymlinkPath], 'deleted'); - }); - - it('unstages file creation when all added lines are unstaged', async function() { - const workingDirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workingDirPath); - - await repository.git.exec(['config', 'core.symlinks', 'true']); - - const deletedSymlinkAddedFilePath = 'symlink.txt'; - fs.unlinkSync(path.join(workingDirPath, deletedSymlinkAddedFilePath)); - fs.writeFileSync(path.join(workingDirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\nbaz\nzoo\n', 'utf8'); - await repository.stageFiles([deletedSymlinkAddedFilePath]); - - const component = createComponent(repository, deletedSymlinkAddedFilePath); - const wrapper = mount(React.cloneElement(component, {filePath: deletedSymlinkAddedFilePath, initialStagingStatus: 'staged'})); - - assert.equal((await indexModeAndOid(repository, deletedSymlinkAddedFilePath)).mode, '100644'); - - // unstage all added lines - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('.github-HunkView-title').simulate('click'); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - // File is not on index, file creation has been unstaged - assert.isNull(await indexModeAndOid(repository, deletedSymlinkAddedFilePath)); - const {stagedFiles, unstagedFiles} = await repository.getStatusesForChangedFiles(); - assert.equal(unstagedFiles[deletedSymlinkAddedFilePath], 'added'); - assert.equal(stagedFiles[deletedSymlinkAddedFilePath], 'deleted'); - }); - }); - - describe('handling non-symlink changes', function() { - let workdirPath, repository, filePath, component; - beforeEach(async function() { - workdirPath = await cloneRepository('multi-line-file'); - repository = await buildRepository(workdirPath); - filePath = 'sample.js'; - component = createComponent(repository, filePath); - }); - - it('stages and unstages hunks when the stage button is clicked on hunk views with no individual lines selected', async function() { - const absFilePath = path.join(workdirPath, filePath); - const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); - const unstagedLines = originalLines.slice(); - unstagedLines.splice(1, 1, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - unstagedLines.splice(11, 2, 'this is a modified line'); - fs.writeFileSync(absFilePath, unstagedLines.join('\n')); - - const wrapper = mount(component); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'core:move-down'); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const hunkView0 = wrapper.find('HunkView').at(0); - assert.isFalse(hunkView0.prop('isSelected')); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - const expectedStagedLines = originalLines.slice(); - expectedStagedLines.splice(1, 1, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedStagedLines.join('\n')); - const updatePromise0 = switchboard.getChangePatchPromise(); - const stagedFilePatch = await repository.getFilePatchForPath('sample.js', {staged: true}); - wrapper.setState({ - stagingStatus: 'staged', - filePatch: stagedFilePatch, - }); - await updatePromise0; - const hunkView1 = wrapper.find('HunkView').at(0); - const opPromise1 = switchboard.getFinishStageOperationPromise(); - hunkView1.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise1; - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), originalLines.join('\n')); - }); - - it('stages and unstages individual lines when the stage button is clicked on a hunk with selected lines', async function() { - const absFilePath = path.join(workdirPath, filePath); - const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); - - // write some unstaged changes - const unstagedLines = originalLines.slice(); - unstagedLines.splice(1, 1, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - unstagedLines.splice(11, 2, 'this is a modified line'); - fs.writeFileSync(absFilePath, unstagedLines.join('\n')); - - // stage a subset of lines from first hunk - const wrapper = mount(component); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const opPromise0 = switchboard.getFinishStageOperationPromise(); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line').simulate('mousedown', {button: 0, detail: 1}); - hunkView0.find('LineView').at(3).find('.github-HunkView-line').simulate('mousemove', {}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView0.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise0; - - await refreshRepository(wrapper); - - const expectedLines0 = originalLines.slice(); - expectedLines0.splice(1, 1, - 'this is a modified line', - 'this is a new line', - ); - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedLines0.join('\n')); - - // stage remaining lines in hunk - const opPromise1 = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); - await opPromise1; - - await refreshRepository(wrapper); - - const expectedLines1 = originalLines.slice(); - expectedLines1.splice(1, 1, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedLines1.join('\n')); - - // unstage a subset of lines from the first hunk - wrapper.setState({stagingStatus: 'staged'}); - await refreshRepository(wrapper); - - const hunkView2 = wrapper.find('HunkView').at(0); - hunkView2.find('LineView').at(1).find('.github-HunkView-line') - .simulate('mousedown', {button: 0, detail: 1}); - window.dispatchEvent(new MouseEvent('mouseup')); - hunkView2.find('LineView').at(2).find('.github-HunkView-line') - .simulate('mousedown', {button: 0, detail: 1, metaKey: true}); - window.dispatchEvent(new MouseEvent('mouseup')); - - const opPromise2 = switchboard.getFinishStageOperationPromise(); - hunkView2.find('button.github-HunkView-stageButton').simulate('click'); - await opPromise2; - - await refreshRepository(wrapper); - - const expectedLines2 = originalLines.slice(); - expectedLines2.splice(2, 0, - 'this is a new line', - 'this is another new line', - ); - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), expectedLines2.join('\n')); - - // unstage the rest of the hunk - commandRegistry.dispatch(wrapper.find('FilePatchView').getDOMNode(), 'github:toggle-patch-selection-mode'); - - const opPromise3 = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); - await opPromise3; - - assert.autocrlfEqual(await repository.readFileFromIndex('sample.js'), originalLines.join('\n')); - }); - - // https://github.com/atom/github/issues/417 - describe('when unstaging the last lines/hunks from a file', function() { - it('removes added files from index when last hunk is unstaged', async function() { - const absFilePath = path.join(workdirPath, 'new-file.txt'); - - fs.writeFileSync(absFilePath, 'foo\n'); - await repository.stageFiles(['new-file.txt']); - - const wrapper = mount(React.cloneElement(component, { - filePath: 'new-file.txt', - initialStagingStatus: 'staged', - })); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - - const opPromise = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); - await opPromise; - - const stagedChanges = await repository.getStagedChanges(); - assert.equal(stagedChanges.length, 0); - }); - - it('removes added files from index when last lines are unstaged', async function() { - const absFilePath = path.join(workdirPath, 'new-file.txt'); - - fs.writeFileSync(absFilePath, 'foo\n'); - await repository.stageFiles(['new-file.txt']); - - const wrapper = mount(React.cloneElement(component, { - filePath: 'new-file.txt', - initialStagingStatus: 'staged', - })); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - - const viewNode = wrapper.find('FilePatchView').getDOMNode(); - commandRegistry.dispatch(viewNode, 'github:toggle-patch-selection-mode'); - commandRegistry.dispatch(viewNode, 'core:select-all'); - - const opPromise = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('button.github-HunkView-stageButton').simulate('click'); - await opPromise; - - const stagedChanges = await repository.getStagedChanges(); - assert.lengthOf(stagedChanges, 0); - }); - }); - - // https://github.com/atom/github/issues/341 - describe('when duplicate staging occurs', function() { - it('avoids patch conflicts with pending line staging operations', async function() { - const absFilePath = path.join(workdirPath, filePath); - const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); - - // write some unstaged changes - const unstagedLines = originalLines.slice(); - unstagedLines.splice(1, 0, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - unstagedLines.splice(11, 2, 'this is a modified line'); - fs.writeFileSync(absFilePath, unstagedLines.join('\n')); - - const wrapper = mount(component); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - const hunkView0 = wrapper.find('HunkView').at(0); - hunkView0.find('LineView').at(1).find('.github-HunkView-line') - .simulate('mousedown', {button: 0, detail: 1}); - window.dispatchEvent(new MouseEvent('mouseup')); - - // stage lines in rapid succession - // second stage action is a no-op since the first staging operation is in flight - const line1StagingPromise = switchboard.getFinishStageOperationPromise(); - hunkView0.find('.github-HunkView-stageButton').simulate('click'); - hunkView0.find('.github-HunkView-stageButton').simulate('click'); - await line1StagingPromise; - - const changePatchPromise = switchboard.getChangePatchPromise(); - - // assert that only line 1 has been staged - await refreshRepository(wrapper); // clear the cached file patches - let expectedLines = originalLines.slice(); - expectedLines.splice(1, 0, - 'this is a modified line', - ); - let actualLines = await repository.readFileFromIndex(filePath); - assert.autocrlfEqual(actualLines, expectedLines.join('\n')); - await changePatchPromise; - wrapper.update(); - - const hunkView1 = wrapper.find('HunkView').at(0); - hunkView1.find('LineView').at(2).find('.github-HunkView-line') - .simulate('mousedown', {button: 0, detail: 1}); - window.dispatchEvent(new MouseEvent('mouseup')); - const line2StagingPromise = switchboard.getFinishStageOperationPromise(); - hunkView1.find('.github-HunkView-stageButton').simulate('click'); - await line2StagingPromise; - - // assert that line 2 has now been staged - expectedLines = originalLines.slice(); - expectedLines.splice(1, 0, - 'this is a modified line', - 'this is a new line', - ); - actualLines = await repository.readFileFromIndex(filePath); - assert.autocrlfEqual(actualLines, expectedLines.join('\n')); - }); - - it('avoids patch conflicts with pending hunk staging operations', async function() { - const absFilePath = path.join(workdirPath, filePath); - const originalLines = fs.readFileSync(absFilePath, 'utf8').split('\n'); - - // write some unstaged changes - const unstagedLines = originalLines.slice(); - unstagedLines.splice(1, 0, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - unstagedLines.splice(11, 2, 'this is a modified line'); - fs.writeFileSync(absFilePath, unstagedLines.join('\n')); - - const wrapper = mount(component); - - await assert.async.isTrue(wrapper.update().find('HunkView').exists()); - - // ensure staging the same hunk twice does not cause issues - // second stage action is a no-op since the first staging operation is in flight - const hunk1StagingPromise = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('.github-HunkView-stageButton').simulate('click'); - wrapper.find('HunkView').at(0).find('.github-HunkView-stageButton').simulate('click'); - await hunk1StagingPromise; - - const patchPromise0 = switchboard.getChangePatchPromise(); - await refreshRepository(wrapper); // clear the cached file patches - const modifiedFilePatch = await repository.getFilePatchForPath(filePath); - wrapper.setState({filePatch: modifiedFilePatch}); - await patchPromise0; - - let expectedLines = originalLines.slice(); - expectedLines.splice(1, 0, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - let actualLines = await repository.readFileFromIndex(filePath); - assert.autocrlfEqual(actualLines, expectedLines.join('\n')); - - const hunk2StagingPromise = switchboard.getFinishStageOperationPromise(); - wrapper.find('HunkView').at(0).find('.github-HunkView-stageButton').simulate('click'); - await hunk2StagingPromise; - - expectedLines = originalLines.slice(); - expectedLines.splice(1, 0, - 'this is a modified line', - 'this is a new line', - 'this is another new line', - ); - expectedLines.splice(11, 2, 'this is a modified line'); - actualLines = await repository.readFileFromIndex(filePath); - assert.autocrlfEqual(actualLines, expectedLines.join('\n')); - }); - }); - }); - }); -}); diff --git a/test/models/file-patch.test.old.js b/test/models/file-patch.test.old.js deleted file mode 100644 index 4c2dde5463..0000000000 --- a/test/models/file-patch.test.old.js +++ /dev/null @@ -1,507 +0,0 @@ -import {cloneRepository, buildRepository} from '../helpers'; -import {toGitPathSep} from '../../lib/helpers'; -import path from 'path'; -import fs from 'fs'; -import dedent from 'dedent-js'; - -import FilePatch from '../../lib/models/file-patch'; -import Hunk from '../../lib/models/hunk'; -import HunkLine from '../../lib/models/hunk-line'; - -function createFilePatch(oldFilePath, newFilePath, status, hunks) { - const oldFile = new FilePatch.File({path: oldFilePath}); - const newFile = new FilePatch.File({path: newFilePath}); - const patch = new FilePatch.Patch({status, hunks}); - - return new FilePatch(oldFile, newFile, patch); -} - -// oldStartRow, newStartRow, oldRowCount, newRowCount, sectionHeading, lines - -describe('FilePatch', function() { - it('detects executable mode changes', function() { - const of0 = new FilePatch.File({path: 'a.txt', mode: '100644'}); - const nf0 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const p0 = new FilePatch.Patch({status: 'modified', hunks: []}); - const fp0 = new FilePatch(of0, nf0, p0); - assert.isTrue(fp0.didChangeExecutableMode()); - - const of1 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const nf1 = new FilePatch.File({path: 'a.txt', mode: '100644'}); - const p1 = new FilePatch.Patch({status: 'modified', hunks: []}); - const fp1 = new FilePatch(of1, nf1, p1); - assert.isTrue(fp1.didChangeExecutableMode()); - - const of2 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const nf2 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const p2 = new FilePatch.Patch({status: 'modified', hunks: []}); - const fp2 = new FilePatch(of2, nf2, p2); - assert.isFalse(fp2.didChangeExecutableMode()); - - const of3 = FilePatch.File.empty(); - const nf3 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const p3 = new FilePatch.Patch({status: 'modified', hunks: []}); - const fp3 = new FilePatch(of3, nf3, p3); - assert.isFalse(fp3.didChangeExecutableMode()); - - const of4 = FilePatch.File.empty(); - const nf4 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const p4 = new FilePatch.Patch({status: 'modified', hunks: []}); - const fp4 = new FilePatch(of4, nf4, p4); - assert.isFalse(fp4.didChangeExecutableMode()); - }); - - describe('getStagePatchForLines()', function() { - it('returns a new FilePatch that applies only the specified lines', function() { - const filePatch = createFilePatch('a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 8), - new HunkLine('line-8', 'added', -1, 9), - new HunkLine('line-9', 'added', -1, 10), - new HunkLine('line-10', 'deleted', 8, -1), - new HunkLine('line-11', 'deleted', 9, -1), - ]), - new Hunk(20, 19, 2, 2, '', [ - new HunkLine('line-12', 'deleted', 20, -1), - new HunkLine('line-13', 'added', -1, 19), - new HunkLine('line-14', 'unchanged', 21, 20), - new HunkLine('No newline at end of file', 'nonewline', -1, -1), - ]), - ]); - const linesFromHunk2 = filePatch.getHunks()[1].getLines().slice(1, 4); - assert.deepEqual(filePatch.getStagePatchForLines(new Set(linesFromHunk2)), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(5, 5, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 5), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 6), - new HunkLine('line-10', 'unchanged', 8, 7), - new HunkLine('line-11', 'unchanged', 9, 8), - ]), - ], - )); - - // add lines from other hunks - const linesFromHunk1 = filePatch.getHunks()[0].getLines().slice(0, 1); - const linesFromHunk3 = filePatch.getHunks()[2].getLines().slice(1, 2); - const selectedLines = linesFromHunk2.concat(linesFromHunk1, linesFromHunk3); - assert.deepEqual(filePatch.getStagePatchForLines(new Set(selectedLines)), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 1, 2, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-3', 'unchanged', 1, 2), - ]), - new Hunk(5, 6, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 6), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 7), - new HunkLine('line-10', 'unchanged', 8, 8), - new HunkLine('line-11', 'unchanged', 9, 9), - ]), - new Hunk(20, 18, 2, 3, '', [ - new HunkLine('line-12', 'unchanged', 20, 18), - new HunkLine('line-13', 'added', -1, 19), - new HunkLine('line-14', 'unchanged', 21, 20), - new HunkLine('No newline at end of file', 'nonewline', -1, -1), - ]), - ], - )); - }); - - describe('staging lines from deleted files', function() { - it('handles staging part of the file', function() { - const filePatch = createFilePatch('a.txt', null, 'deleted', [ - new Hunk(1, 0, 3, 0, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'deleted', 3, -1), - ]), - ]); - const linesFromHunk = filePatch.getHunks()[0].getLines().slice(0, 2); - assert.deepEqual(filePatch.getStagePatchForLines(new Set(linesFromHunk)), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 3, 1, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'unchanged', 3, 1), - ]), - ], - )); - }); - - it('handles staging all lines, leaving nothing unstaged', function() { - const filePatch = createFilePatch('a.txt', null, 'deleted', [ - new Hunk(1, 0, 3, 0, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'deleted', 3, -1), - ]), - ]); - const linesFromHunk = filePatch.getHunks()[0].getLines(); - assert.deepEqual(filePatch.getStagePatchForLines(new Set(linesFromHunk)), createFilePatch( - 'a.txt', null, 'deleted', [ - new Hunk(1, 0, 3, 0, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'deleted', 3, -1), - ]), - ], - )); - }); - }); - }); - - describe('getUnstagePatchForLines()', function() { - it('returns a new FilePatch that applies only the specified lines', function() { - const filePatch = createFilePatch('a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 8), - new HunkLine('line-8', 'added', -1, 9), - new HunkLine('line-9', 'added', -1, 10), - new HunkLine('line-10', 'deleted', 8, -1), - new HunkLine('line-11', 'deleted', 9, -1), - ]), - new Hunk(20, 19, 2, 2, '', [ - new HunkLine('line-12', 'deleted', 20, -1), - new HunkLine('line-13', 'added', -1, 19), - new HunkLine('line-14', 'unchanged', 21, 20), - new HunkLine('No newline at end of file', 'nonewline', -1, -1), - ]), - ]); - const lines = new Set(filePatch.getHunks()[1].getLines().slice(1, 5)); - filePatch.getHunks()[2].getLines().forEach(line => lines.add(line)); - assert.deepEqual(filePatch.getUnstagePatchForLines(lines), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(7, 7, 4, 4, '', [ - new HunkLine('line-4', 'unchanged', 7, 7), - new HunkLine('line-7', 'deleted', 8, -1), - new HunkLine('line-8', 'deleted', 9, -1), - new HunkLine('line-5', 'added', -1, 8), - new HunkLine('line-6', 'added', -1, 9), - new HunkLine('line-9', 'unchanged', 10, 10), - ]), - new Hunk(19, 21, 2, 2, '', [ - new HunkLine('line-13', 'deleted', 19, -1), - new HunkLine('line-12', 'added', -1, 21), - new HunkLine('line-14', 'unchanged', 20, 22), - new HunkLine('No newline at end of file', 'nonewline', -1, -1), - ]), - ], - )); - }); - - describe('unstaging lines from an added file', function() { - it('handles unstaging part of the file', function() { - const filePatch = createFilePatch(null, 'a.txt', 'added', [ - new Hunk(0, 1, 0, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - ]), - ]); - const linesFromHunk = filePatch.getHunks()[0].getLines().slice(0, 2); - assert.deepEqual(filePatch.getUnstagePatchForLines(new Set(linesFromHunk)), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 3, 1, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'unchanged', 3, 1), - ]), - ], - )); - }); - - it('handles unstaging all lines, leaving nothign staged', function() { - const filePatch = createFilePatch(null, 'a.txt', 'added', [ - new Hunk(0, 1, 0, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - ]), - ]); - - const linesFromHunk = filePatch.getHunks()[0].getLines(); - assert.deepEqual(filePatch.getUnstagePatchForLines(new Set(linesFromHunk)), createFilePatch( - 'a.txt', null, 'deleted', [ - new Hunk(1, 0, 3, 0, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'deleted', 3, -1), - ]), - ], - )); - }); - }); - }); - - it('handles newly added files', function() { - const filePatch = createFilePatch(null, 'a.txt', 'added', [ - new Hunk(0, 1, 0, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - ]), - ]); - const linesFromHunk = filePatch.getHunks()[0].getLines().slice(0, 2); - assert.deepEqual(filePatch.getUnstagePatchForLines(new Set(linesFromHunk)), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 3, 1, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'unchanged', 3, 1), - ]), - ], - )); - }); - - describe('toString()', function() { - it('converts the patch to the standard textual format', async function() { - const workdirPath = await cloneRepository('multi-line-file'); - const repository = await buildRepository(workdirPath); - - const lines = fs.readFileSync(path.join(workdirPath, 'sample.js'), 'utf8').split('\n'); - lines[0] = 'this is a modified line'; - lines.splice(1, 0, 'this is a new line'); - lines[11] = 'this is a modified line'; - lines.splice(12, 1); - fs.writeFileSync(path.join(workdirPath, 'sample.js'), lines.join('\n')); - - const patch = await repository.getFilePatchForPath('sample.js'); - assert.equal(patch.toString(), dedent` - diff --git a/sample.js b/sample.js - --- a/sample.js - +++ b/sample.js - @@ -1,4 +1,5 @@ - -var quicksort = function () { - +this is a modified line - +this is a new line - var sort = function(items) { - if (items.length <= 1) return items; - var pivot = items.shift(), current, left = [], right = []; - @@ -8,6 +9,5 @@ - } - return sort(left).concat(pivot).concat(sort(right)); - }; - - - - return sort(Array.apply(this, arguments)); - +this is a modified line - }; - - `); - }); - - it('correctly formats new files with no newline at the end', async function() { - const workingDirPath = await cloneRepository('three-files'); - const repo = await buildRepository(workingDirPath); - fs.writeFileSync(path.join(workingDirPath, 'e.txt'), 'qux', 'utf8'); - const patch = await repo.getFilePatchForPath('e.txt'); - - assert.equal(patch.toString(), dedent` - diff --git a/e.txt b/e.txt - new file mode 100644 - --- /dev/null - +++ b/e.txt - @@ -0,0 +1,1 @@ - +qux - \\ No newline at end of file - - `); - }); - - describe('typechange file patches', function() { - it('handles typechange patches for a symlink replaced with a file', async function() { - const workdirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workdirPath); - - await repository.git.exec(['config', 'core.symlinks', 'true']); - - const deletedSymlinkAddedFilePath = 'symlink.txt'; - fs.unlinkSync(path.join(workdirPath, deletedSymlinkAddedFilePath)); - fs.writeFileSync(path.join(workdirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\n', 'utf8'); - - const patch = await repository.getFilePatchForPath(deletedSymlinkAddedFilePath); - assert.equal(patch.toString(), dedent` - diff --git a/symlink.txt b/symlink.txt - deleted file mode 120000 - --- a/symlink.txt - +++ /dev/null - @@ -1 +0,0 @@ - -./regular-file.txt - \\ No newline at end of file - diff --git a/symlink.txt b/symlink.txt - new file mode 100644 - --- /dev/null - +++ b/symlink.txt - @@ -0,0 +1,3 @@ - +qux - +foo - +bar - - `); - }); - - it('handles typechange patches for a file replaced with a symlink', async function() { - const workdirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workdirPath); - - const deletedFileAddedSymlinkPath = 'a.txt'; - fs.unlinkSync(path.join(workdirPath, deletedFileAddedSymlinkPath)); - fs.symlinkSync(path.join(workdirPath, 'regular-file.txt'), path.join(workdirPath, deletedFileAddedSymlinkPath)); - - const patch = await repository.getFilePatchForPath(deletedFileAddedSymlinkPath); - assert.equal(patch.toString(), dedent` - diff --git a/a.txt b/a.txt - deleted file mode 100644 - --- a/a.txt - +++ /dev/null - @@ -1,4 +0,0 @@ - -foo - -bar - -baz - - - diff --git a/a.txt b/a.txt - new file mode 120000 - --- /dev/null - +++ b/a.txt - @@ -0,0 +1 @@ - +${toGitPathSep(path.join(workdirPath, 'regular-file.txt'))} - \\ No newline at end of file - - `); - }); - }); - }); - - describe('getHeaderString()', function() { - it('formats paths with git path separators', function() { - const oldPath = path.join('foo', 'bar', 'old.js'); - const newPath = path.join('baz', 'qux', 'new.js'); - - const patch = createFilePatch(oldPath, newPath, 'modified', []); - assert.equal(patch.getHeaderString(), dedent` - diff --git a/foo/bar/old.js b/baz/qux/new.js - --- a/foo/bar/old.js - +++ b/baz/qux/new.js - - `); - }); - }); - - it('returns the size in bytes from getByteSize()', function() { - const filePatch = createFilePatch('a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - ]), - ]); - - assert.strictEqual(filePatch.getByteSize(), 36); - }); - - describe('present()', function() { - let presented; - - beforeEach(function() { - const patch = createFilePatch('a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 1, 3, '@@ -1,1 +2,2', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 5, 4, '@@ -3,3 +4,4', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 8), - new HunkLine('line-8', 'added', -1, 9), - new HunkLine('line-9', 'added', -1, 10), - new HunkLine('line-10', 'deleted', 8, -1), - new HunkLine('line-11', 'deleted', 9, -1), - new HunkLine('line-12', 'unchanged', 5, 7), - ]), - new Hunk(20, 19, 2, 2, '@@ -5,5 +6,6', [ - new HunkLine('line-13', 'deleted', 20, -1), - new HunkLine('line-14', 'added', -1, 19), - new HunkLine('line-15', 'unchanged', 21, 20), - new HunkLine('No newline at end of file', 'nonewline', -1, -1), - ]), - ]); - - presented = patch.present(); - }); - - function assertPositions(actualPositions, expectedPositions) { - assert.lengthOf(actualPositions, expectedPositions.length); - for (let i = 0; i < expectedPositions.length; i++) { - const actual = actualPositions[i]; - const expected = expectedPositions[i]; - - assert.isTrue(actual.isEqual(expected), - `range ${i}: ${actual.toString()} does not equal [${expected.map(e => e.toString()).join(', ')}]`); - } - } - - it('unifies hunks into a continuous, unadorned string of text', function() { - const actualText = presented.getText(); - const expectedText = - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map(num => `line-${num}\n`).join('') + - 'No newline at end of file\n'; - - assert.strictEqual(actualText, expectedText); - }); - - it("returns the buffer positions corresponding to each hunk's beginning", function() { - assertPositions(presented.getHunkStartPositions(), [ - [0, 0], [3, 0], [12, 0], - ]); - }); - - it('returns the buffer positions corresponding to unchanged lines', function() { - assertPositions(presented.getUnchangedBufferPositions(), [ - [2, 0], [3, 0], [11, 0], [14, 0], - ]); - }); - - it('returns the buffer positions corresponding to added lines', function() { - assertPositions(presented.getAddedBufferPositions(), [ - [0, 0], [1, 0], [6, 0], [7, 0], [8, 0], [13, 0], - ]); - }); - - it('returns the buffer positions corresponding to deleted lines', function() { - assertPositions(presented.getDeletedBufferPositions(), [ - [4, 0], [5, 0], [9, 0], [10, 0], [12, 0], - ]); - }); - - it('returns the buffer position of a "no newline" trailer', function() { - assertPositions(presented.getNoNewlineBufferPositions(), [ - [15, 0], - ]); - }); - }); -}); diff --git a/test/views/file-patch-view.test.old.js b/test/views/file-patch-view.test.old.js deleted file mode 100644 index 59132587d3..0000000000 --- a/test/views/file-patch-view.test.old.js +++ /dev/null @@ -1,615 +0,0 @@ -import React from 'react'; -import {shallow, mount} from 'enzyme'; - -import FilePatchView from '../../lib/views/file-patch-view'; -import Hunk from '../../lib/models/hunk'; -import HunkLine from '../../lib/models/hunk-line'; - -import {assertEqualSets} from '../helpers'; - -describe('FilePatchView', function() { - let atomEnv, commandRegistry, tooltips, component; - let attemptLineStageOperation, attemptHunkStageOperation, attemptFileStageOperation, attemptSymlinkStageOperation; - let attemptModeStageOperation, discardLines, undoLastDiscard, openCurrentFile; - let didSurfaceFile, didDiveIntoCorrespondingFilePatch, handleShowDiffClick; - - beforeEach(function() { - atomEnv = global.buildAtomEnvironment(); - commandRegistry = atomEnv.commands; - tooltips = atomEnv.tooltips; - - attemptLineStageOperation = sinon.spy(); - attemptHunkStageOperation = sinon.spy(); - attemptModeStageOperation = sinon.spy(); - attemptFileStageOperation = sinon.spy(); - attemptSymlinkStageOperation = sinon.spy(); - discardLines = sinon.spy(); - undoLastDiscard = sinon.spy(); - openCurrentFile = sinon.spy(); - didSurfaceFile = sinon.spy(); - didDiveIntoCorrespondingFilePatch = sinon.spy(); - handleShowDiffClick = sinon.spy(); - - component = ( - - ); - }); - - afterEach(function() { - atomEnv.destroy(); - }); - - describe('mouse selection', () => { - it('allows lines and hunks to be selected via mouse drag', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'unchanged', 2, 4), - ]), - new Hunk(5, 7, 1, 4, '', [ - new HunkLine('line-5', 'unchanged', 5, 7), - new HunkLine('line-6', 'added', -1, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'added', -1, 10), - ]), - ]; - - const wrapper = shallow(React.cloneElement(component, {hunks})); - const getHunkView = index => wrapper.find({hunk: hunks[index]}); - - // drag a selection - getHunkView(0).prop('mousedownOnLine')({button: 0, detail: 1}, hunks[0], hunks[0].lines[2]); - getHunkView(1).prop('mousemoveOnLine')({}, hunks[1], hunks[1].lines[1]); - wrapper.instance().mouseup(); - wrapper.update(); - - assert.isTrue(getHunkView(0).prop('isSelected')); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[2])); - assert.isTrue(getHunkView(1).prop('isSelected')); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[1])); - - // start a new selection, drag it across an existing selection - getHunkView(1).prop('mousedownOnLine')({button: 0, detail: 1, metaKey: true}, hunks[1], hunks[1].lines[3]); - getHunkView(0).prop('mousemoveOnLine')({}, hunks[0], hunks[0].lines[0]); - wrapper.update(); - - assert.isTrue(getHunkView(0).prop('isSelected')); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[1])); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[2])); - assert.isTrue(getHunkView(1).prop('isSelected')); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[1])); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[2])); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[3])); - - // drag back down without releasing mouse; the other selection remains intact - getHunkView(1).prop('mousemoveOnLine')({}, hunks[1], hunks[1].lines[3]); - wrapper.update(); - - assert.isTrue(getHunkView(0).prop('isSelected')); - assert.isFalse(getHunkView(0).prop('selectedLines').has(hunks[0].lines[1])); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[2])); - assert.isTrue(getHunkView(1).prop('isSelected')); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[1])); - assert.isFalse(getHunkView(1).prop('selectedLines').has(hunks[1].lines[2])); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[3])); - - // drag back up so selections are adjacent, then release the mouse. selections should merge. - getHunkView(1).prop('mousemoveOnLine')({}, hunks[1], hunks[1].lines[2]); - wrapper.instance().mouseup(); - wrapper.update(); - - assert.isTrue(getHunkView(0).prop('isSelected')); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[2])); - assert.isTrue(getHunkView(1).prop('isSelected')); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[1])); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[2])); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[3])); - - // we detect merged selections based on the head here - wrapper.instance().selectToNext(); - wrapper.update(); - - assert.isFalse(getHunkView(0).prop('isSelected')); - assert.isFalse(getHunkView(0).prop('selectedLines').has(hunks[0].lines[2])); - - // double-clicking clears the existing selection and starts hunk-wise selection - getHunkView(0).prop('mousedownOnLine')({button: 0, detail: 2}, hunks[0], hunks[0].lines[2]); - wrapper.update(); - - assert.isTrue(getHunkView(0).prop('isSelected')); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[1])); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[2])); - assert.isFalse(getHunkView(1).prop('isSelected')); - - getHunkView(1).prop('mousemoveOnLine')({}, hunks[1], hunks[1].lines[1]); - wrapper.update(); - - assert.isTrue(getHunkView(0).prop('isSelected')); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[1])); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[2])); - assert.isTrue(getHunkView(1).prop('isSelected')); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[1])); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[2])); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[3])); - - // clicking the header clears the existing selection and starts hunk-wise selection - getHunkView(0).prop('mousedownOnHeader')({button: 0, detail: 1}, hunks[0], hunks[0].lines[2]); - wrapper.update(); - - assert.isTrue(getHunkView(0).prop('isSelected')); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[1])); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[2])); - assert.isFalse(getHunkView(1).prop('isSelected')); - - getHunkView(1).prop('mousemoveOnLine')({}, hunks[1], hunks[1].lines[1]); - wrapper.update(); - - assert.isTrue(getHunkView(0).prop('isSelected')); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[1])); - assert.isTrue(getHunkView(0).prop('selectedLines').has(hunks[0].lines[2])); - assert.isTrue(getHunkView(1).prop('isSelected')); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[1])); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[2])); - assert.isTrue(getHunkView(1).prop('selectedLines').has(hunks[1].lines[3])); - }); - - it('allows lines and hunks to be selected via cmd-clicking', function() { - const hunk0 = new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-0', 'added', -1, 1), - new HunkLine('line-1', 'added', -1, 2), - new HunkLine('line-2', 'added', -1, 3), - ]); - const hunk1 = new Hunk(5, 7, 1, 4, '', [ - new HunkLine('line-3', 'added', -1, 7), - new HunkLine('line-4', 'added', -1, 8), - new HunkLine('line-5', 'added', -1, 9), - new HunkLine('line-6', 'added', -1, 10), - ]); - - const wrapper = shallow(React.cloneElement(component, { - hunks: [hunk0, hunk1], - })); - const getHunkView = index => wrapper.find({hunk: [hunk0, hunk1][index]}); - - // in line selection mode, cmd-click line - getHunkView(0).prop('mousedownOnLine')({button: 0, detail: 1}, hunk0, hunk0.lines[2]); - wrapper.instance().mouseup(); - - assert.equal(wrapper.instance().getPatchSelectionMode(), 'line'); - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([hunk0.lines[2]])); - - getHunkView(1).prop('mousedownOnLine')({button: 0, detail: 1, metaKey: true}, hunk1, hunk1.lines[2]); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0, hunk1])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([hunk0.lines[2], hunk1.lines[2]])); - - getHunkView(1).prop('mousedownOnLine')({button: 0, detail: 1, metaKey: true}, hunk1, hunk1.lines[2]); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([hunk0.lines[2]])); - - // in line selection mode, cmd-click hunk header for separate hunk - getHunkView(0).prop('mousedownOnLine')({button: 0, detail: 1}, hunk0, hunk0.lines[2]); - wrapper.instance().mouseup(); - - assert.equal(wrapper.instance().getPatchSelectionMode(), 'line'); - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([hunk0.lines[2]])); - - getHunkView(1).prop('mousedownOnHeader')({button: 0, metaKey: true}); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0, hunk1])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([hunk0.lines[2], ...hunk1.lines])); - - getHunkView(1).prop('mousedownOnHeader')({button: 0, metaKey: true}); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([hunk0.lines[2]])); - - // in hunk selection mode, cmd-click line for separate hunk - getHunkView(0).prop('mousedownOnLine')({button: 0, detail: 1}, hunk0, hunk0.lines[2]); - wrapper.instance().mouseup(); - wrapper.instance().togglePatchSelectionMode(); - - assert.equal(wrapper.instance().getPatchSelectionMode(), 'hunk'); - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set(hunk0.lines)); - - // in hunk selection mode, cmd-click hunk header for separate hunk - getHunkView(0).prop('mousedownOnLine')({button: 0, detail: 1}, hunk0, hunk0.lines[2]); - wrapper.instance().mouseup(); - wrapper.instance().togglePatchSelectionMode(); - - assert.equal(wrapper.instance().getPatchSelectionMode(), 'hunk'); - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set(hunk0.lines)); - }); - - it('allows lines and hunks to be selected via shift-clicking', () => { - const hunk0 = new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1, 0), - new HunkLine('line-2', 'added', -1, 2, 1), - new HunkLine('line-3', 'added', -1, 3, 2), - ]); - const hunk1 = new Hunk(5, 7, 1, 4, '', [ - new HunkLine('line-5', 'added', -1, 7, 3), - new HunkLine('line-6', 'added', -1, 8, 4), - new HunkLine('line-7', 'added', -1, 9, 5), - new HunkLine('line-8', 'added', -1, 10, 6), - ]); - const hunk2 = new Hunk(15, 17, 1, 4, '', [ - new HunkLine('line-15', 'added', -1, 15, 7), - new HunkLine('line-16', 'added', -1, 18, 8), - new HunkLine('line-17', 'added', -1, 19, 9), - new HunkLine('line-18', 'added', -1, 20, 10), - ]); - const hunks = [hunk0, hunk1, hunk2]; - - const wrapper = shallow(React.cloneElement(component, {hunks})); - const getHunkView = index => wrapper.find({hunk: hunks[index]}); - - // in line selection mode, shift-click line in separate hunk that comes after selected line - getHunkView(0).prop('mousedownOnLine')({button: 0, detail: 1}, hunk0, hunk0.lines[2]); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([hunk0.lines[2]])); - - getHunkView(2).prop('mousedownOnLine')({button: 0, detail: 1, shiftKey: true}, hunk2, hunk2.lines[2]); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0, hunk1, hunk2])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([...hunk0.lines.slice(2), ...hunk1.lines, ...hunk2.lines.slice(0, 3)])); - - getHunkView(1).prop('mousedownOnLine')({button: 0, detail: 1, shiftKey: true}, hunk1, hunk1.lines[2]); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0, hunk1])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([...hunk0.lines.slice(2), ...hunk1.lines.slice(0, 3)])); - - // in line selection mode, shift-click hunk header for separate hunk that comes after selected line - getHunkView(0).prop('mousedownOnLine')({button: 0, detail: 1}, hunk0, hunk0.lines[2]); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([hunk0.lines[2]])); - - getHunkView(2).prop('mousedownOnHeader')({button: 0, shiftKey: true}, hunk2); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0, hunk1, hunk2])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([...hunk0.lines.slice(2), ...hunk1.lines, ...hunk2.lines])); - - getHunkView(1).prop('mousedownOnHeader')({button: 0, shiftKey: true}, hunk1); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0, hunk1])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([...hunk0.lines.slice(2), ...hunk1.lines])); - - // in line selection mode, shift-click hunk header for separate hunk that comes before selected line - getHunkView(2).prop('mousedownOnLine')({button: 0, detail: 1}, hunk2, hunk2.lines[2]); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk2])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([hunk2.lines[2]])); - - getHunkView(0).prop('mousedownOnHeader')({button: 0, shiftKey: true}, hunk0); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0, hunk1, hunk2])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([...hunk0.lines.slice(1), ...hunk1.lines, ...hunk2.lines.slice(0, 3)])); - - getHunkView(1).prop('mousedownOnHeader')({button: 0, shiftKey: true}, hunk1); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk1, hunk2])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([...hunk1.lines, ...hunk2.lines.slice(0, 3)])); - - // in hunk selection mode, shift-click hunk header for separate hunk that comes after selected line - getHunkView(0).prop('mousedownOnHeader')({button: 0}, hunk0); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set(hunk0.lines.slice(1))); - - getHunkView(2).prop('mousedownOnHeader')({button: 0, shiftKey: true}, hunk2); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0, hunk1, hunk2])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([...hunk0.lines.slice(1), ...hunk1.lines, ...hunk2.lines])); - - getHunkView(1).prop('mousedownOnHeader')({button: 0, shiftKey: true}, hunk1); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0, hunk1])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([...hunk0.lines.slice(1), ...hunk1.lines])); - - // in hunk selection mode, shift-click hunk header for separate hunk that comes before selected line - getHunkView(2).prop('mousedownOnHeader')({button: 0}, hunk2); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk2])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set(hunk2.lines)); - - getHunkView(0).prop('mousedownOnHeader')({button: 0, shiftKey: true}, hunk0); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk0, hunk1, hunk2])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([...hunk0.lines.slice(1), ...hunk1.lines, ...hunk2.lines])); - - getHunkView(1).prop('mousedownOnHeader')({button: 0, shiftKey: true}, hunk1); - wrapper.instance().mouseup(); - - assertEqualSets(wrapper.instance().getSelectedHunks(), new Set([hunk1, hunk2])); - assertEqualSets(wrapper.instance().getSelectedLines(), new Set([...hunk1.lines, ...hunk2.lines])); - }); - - if (process.platform !== 'win32') { - // https://github.com/atom/github/issues/514 - describe('mousedownOnLine', function() { - it('does not select line or set selection to be in progress if ctrl-key is pressed and not on windows', function() { - const hunk0 = new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - ]); - - const wrapper = shallow(React.cloneElement(component, {hunks: [hunk0]})); - - wrapper.instance().togglePatchSelectionMode(); - assert.equal(wrapper.instance().getPatchSelectionMode(), 'line'); - - sinon.spy(wrapper.state('selection'), 'addOrSubtractLineSelection'); - sinon.spy(wrapper.state('selection'), 'selectLine'); - - wrapper.find('HunkView').prop('mousedownOnLine')({button: 0, detail: 1, ctrlKey: true}, hunk0, hunk0.lines[2]); - assert.isFalse(wrapper.state('selection').addOrSubtractLineSelection.called); - assert.isFalse(wrapper.state('selection').selectLine.called); - assert.isFalse(wrapper.instance().mouseSelectionInProgress); - }); - }); - - // https://github.com/atom/github/issues/514 - describe('mousedownOnHeader', function() { - it('does not select line or set selection to be in progress if ctrl-key is pressed and not on windows', function() { - const hunk0 = new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - ]); - const hunk1 = new Hunk(5, 7, 1, 4, '', [ - new HunkLine('line-5', 'added', -1, 7), - new HunkLine('line-6', 'added', -1, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'added', -1, 10), - ]); - - const wrapper = shallow(React.cloneElement(component, {hunks: [hunk0, hunk1]})); - - wrapper.instance().togglePatchSelectionMode(); - assert.equal(wrapper.instance().getPatchSelectionMode(), 'line'); - - sinon.spy(wrapper.state('selection'), 'addOrSubtractLineSelection'); - sinon.spy(wrapper.state('selection'), 'selectLine'); - - // ctrl-click hunk line - wrapper.find({hunk: hunk0}).prop('mousedownOnHeader')({button: 0, detail: 1, ctrlKey: true}, hunk0); - - assert.isFalse(wrapper.state('selection').addOrSubtractLineSelection.called); - assert.isFalse(wrapper.state('selection').selectLine.called); - assert.isFalse(wrapper.instance().mouseSelectionInProgress); - }); - }); - } - }); - - it('scrolls off-screen lines and hunks into view when they are selected', async function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'unchanged', 2, 4), - ]), - new Hunk(5, 7, 1, 4, '', [ - new HunkLine('line-5', 'unchanged', 5, 7), - new HunkLine('line-6', 'added', -1, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'added', -1, 10), - ]), - ]; - - const root = document.createElement('div'); - root.style.overflow = 'scroll'; - root.style.height = '100px'; - document.body.appendChild(root); - - const wrapper = mount(React.cloneElement(component, {hunks}), {attachTo: root}); - - wrapper.instance().togglePatchSelectionMode(); - wrapper.instance().selectNext(); - await new Promise(resolve => root.addEventListener('scroll', resolve)); - assert.isAbove(root.scrollTop, 0); - const initScrollTop = root.scrollTop; - - wrapper.instance().togglePatchSelectionMode(); - wrapper.instance().selectNext(); - await new Promise(resolve => root.addEventListener('scroll', resolve)); - assert.isAbove(root.scrollTop, initScrollTop); - - root.remove(); - }); - - it('assigns the appropriate stage button label on hunks based on the stagingStatus and selection mode', function() { - const hunk = new Hunk(1, 1, 1, 2, '', [new HunkLine('line-1', 'added', -1, 1)]); - - const wrapper = shallow(React.cloneElement(component, { - hunks: [hunk], - stagingStatus: 'unstaged', - })); - - assert.equal(wrapper.find('HunkView').prop('stageButtonLabel'), 'Stage Hunk'); - wrapper.setProps({stagingStatus: 'staged'}); - assert.equal(wrapper.find('HunkView').prop('stageButtonLabel'), 'Unstage Hunk'); - - wrapper.instance().togglePatchSelectionMode(); - wrapper.update(); - - assert.equal(wrapper.find('HunkView').prop('stageButtonLabel'), 'Unstage Selection'); - wrapper.setProps({stagingStatus: 'unstaged'}); - assert.equal(wrapper.find('HunkView').prop('stageButtonLabel'), 'Stage Selection'); - }); - - describe('didClickStageButtonForHunk', function() { - // ref: https://github.com/atom/github/issues/339 - it('selects the next hunk after staging', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'unchanged', 2, 4), - ]), - new Hunk(5, 7, 1, 4, '', [ - new HunkLine('line-5', 'unchanged', 5, 7), - new HunkLine('line-6', 'added', -1, 8), - new HunkLine('line-7', 'added', -1, 9), - new HunkLine('line-8', 'added', -1, 10), - ]), - new Hunk(15, 17, 1, 4, '', [ - new HunkLine('line-9', 'unchanged', 15, 17), - new HunkLine('line-10', 'added', -1, 18), - new HunkLine('line-11', 'added', -1, 19), - new HunkLine('line-12', 'added', -1, 20), - ]), - ]; - - const wrapper = shallow(React.cloneElement(component, { - hunks, - stagingStatus: 'unstaged', - })); - - wrapper.find({hunk: hunks[2]}).prop('didClickStageButton')(); - wrapper.setProps({hunks: hunks.filter(h => h !== hunks[2])}); - - assertEqualSets(wrapper.state('selection').getSelectedHunks(), new Set([hunks[1]])); - }); - }); - - describe('keyboard navigation', function() { - it('invokes the didSurfaceFile callback on core:move-right', function() { - const hunks = [ - new Hunk(1, 1, 2, 2, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - ]), - ]; - - const wrapper = mount(React.cloneElement(component, {hunks})); - commandRegistry.dispatch(wrapper.getDOMNode(), 'core:move-right'); - - assert.equal(didSurfaceFile.callCount, 1); - }); - }); - - describe('openFile', function() { - describe('when the selected line is an added line', function() { - it('calls this.props.openCurrentFile with the first selected line\'s new line number', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'added', -1, 4), - new HunkLine('line-5', 'unchanged', 2, 5), - ]), - ]; - - const wrapper = shallow(React.cloneElement(component, {hunks})); - - wrapper.instance().mousedownOnLine({button: 0, detail: 1}, hunks[0], hunks[0].lines[2]); - wrapper.instance().mousemoveOnLine({}, hunks[0], hunks[0].lines[3]); - wrapper.instance().mouseup(); - - wrapper.instance().openFile(); - assert.isTrue(openCurrentFile.calledWith({lineNumber: 3})); - }); - }); - - describe('when the selected line is a deleted line in a non-empty file', function() { - it('calls this.props.openCurrentFile with the new start row of the first selected hunk', function() { - const hunks = [ - new Hunk(1, 1, 2, 4, '', [ - new HunkLine('line-1', 'unchanged', 1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - new HunkLine('line-4', 'added', -1, 4), - new HunkLine('line-5', 'unchanged', 2, 5), - ]), - new Hunk(15, 17, 4, 1, '', [ - new HunkLine('line-5', 'unchanged', 15, 17), - new HunkLine('line-6', 'deleted', 16, -1), - new HunkLine('line-7', 'deleted', 17, -1), - new HunkLine('line-8', 'deleted', 18, -1), - ]), - ]; - - const wrapper = shallow(React.cloneElement(component, {hunks})); - - wrapper.instance().mousedownOnLine({button: 0, detail: 1}, hunks[1], hunks[1].lines[2]); - wrapper.instance().mousemoveOnLine({}, hunks[1], hunks[1].lines[3]); - wrapper.instance().mouseup(); - - wrapper.instance().openFile(); - assert.isTrue(openCurrentFile.calledWith({lineNumber: 17})); - }); - }); - - describe('when the selected line is a deleted line in an empty file', function() { - it('calls this.props.openCurrentFile with a line number of 0', function() { - const hunks = [ - new Hunk(1, 0, 4, 0, '', [ - new HunkLine('line-5', 'deleted', 1, -1), - new HunkLine('line-6', 'deleted', 2, -1), - new HunkLine('line-7', 'deleted', 3, -1), - new HunkLine('line-8', 'deleted', 4, -1), - ]), - ]; - - const wrapper = shallow(React.cloneElement(component, {hunks})); - - wrapper.instance().mousedownOnLine({button: 0, detail: 1}, hunks[0], hunks[0].lines[2]); - wrapper.instance().mouseup(); - - wrapper.instance().openFile(); - assert.isTrue(openCurrentFile.calledWith({lineNumber: 0})); - }); - }); - }); -}); From 4a7c4f91b7270d4b78b4fbd039c40e2e1e2080ba Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 07:45:04 -0400 Subject: [PATCH 0173/4053] Span IndexedRowRanges to the final column of the row --- lib/models/indexed-row-range.js | 2 +- test/models/indexed-row-range.test.js | 64 +++++++++++++-------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js index 1dc48345de..38357f703f 100644 --- a/lib/models/indexed-row-range.js +++ b/lib/models/indexed-row-range.js @@ -58,7 +58,7 @@ export default class IndexedRowRange { intersections.push({ intersection: new IndexedRowRange({ - bufferRange: Range.fromObject([[nextStartRow, 0], [currentRow - 1, 0]]), + bufferRange: Range.fromObject([[nextStartRow, 0], [currentRow - 1, Infinity]]), startOffset: nextStartOffset, endOffset: currentOffset, }), diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js index 8985db788b..a87ee5c1fc 100644 --- a/test/models/indexed-row-range.test.js +++ b/test/models/indexed-row-range.test.js @@ -3,7 +3,7 @@ import IndexedRowRange, {nullIndexedRowRange} from '../../lib/models/indexed-row describe('IndexedRowRange', function() { it('computes its row count', function() { const range = new IndexedRowRange({ - bufferRange: [[0, 0], [1, 0]], + bufferRange: [[0, 0], [1, Infinity]], startOffset: 0, endOffset: 10, }); @@ -13,7 +13,7 @@ describe('IndexedRowRange', function() { it('returns its starting buffer row', function() { const range = new IndexedRowRange({ - bufferRange: [[2, 0], [8, 0]], + bufferRange: [[2, 0], [8, Infinity]], startOffset: 0, endOffset: 10, }); @@ -22,7 +22,7 @@ describe('IndexedRowRange', function() { it('returns its ending buffer row', function() { const range = new IndexedRowRange({ - bufferRange: [[2, 0], [8, 0]], + bufferRange: [[2, 0], [8, Infinity]], startOffset: 0, endOffset: 10, }); @@ -31,7 +31,7 @@ describe('IndexedRowRange', function() { it('returns an array of the covered rows', function() { const range = new IndexedRowRange({ - bufferRange: [[2, 0], [8, 0]], + bufferRange: [[2, 0], [8, Infinity]], startOffset: 0, endOffset: 10, }); @@ -40,7 +40,7 @@ describe('IndexedRowRange', function() { it('has a buffer row inclusion predicate', function() { const range = new IndexedRowRange({ - bufferRange: [[2, 0], [4, 0]], + bufferRange: [[2, 0], [4, Infinity]], startOffset: 0, endOffset: 10, }); @@ -55,7 +55,7 @@ describe('IndexedRowRange', function() { it('extracts its offset range from buffer text with toStringIn()', function() { const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n'; const range = new IndexedRowRange({ - bufferRange: [[1, 0], [2, 0]], + bufferRange: [[1, 0], [2, Infinity]], startOffset: 5, endOffset: 25, }); @@ -75,87 +75,87 @@ describe('IndexedRowRange', function() { it('returns an array containing all gaps with no intersection rows', function() { const range = new IndexedRowRange({ - bufferRange: [[1, 0], [3, 0]], + bufferRange: [[1, 0], [3, Infinity]], startOffset: 5, endOffset: 20, }); assertIntersections(range.intersectRowsIn(new Set([0, 5, 6]), buffer, false), []); assertIntersections(range.intersectRowsIn(new Set([0, 5, 6]), buffer, true), [ - {intersection: {bufferRange: [[1, 0], [3, 0]], startOffset: 5, endOffset: 20}, gap: true}, + {intersection: {bufferRange: [[1, 0], [3, Infinity]], startOffset: 5, endOffset: 20}, gap: true}, ]); }); it('detects an intersection at the beginning of the range', function() { const range = new IndexedRowRange({ - bufferRange: [[2, 0], [6, 0]], + bufferRange: [[2, 0], [6, Infinity]], startOffset: 10, endOffset: 35, }); const rowSet = new Set([0, 1, 2, 3]); assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ - {intersection: {bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20}, gap: false}, + {intersection: {bufferRange: [[2, 0], [3, Infinity]], startOffset: 10, endOffset: 20}, gap: false}, ]); assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ - {intersection: {bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20}, gap: false}, - {intersection: {bufferRange: [[4, 0], [6, 0]], startOffset: 20, endOffset: 35}, gap: true}, + {intersection: {bufferRange: [[2, 0], [3, Infinity]], startOffset: 10, endOffset: 20}, gap: false}, + {intersection: {bufferRange: [[4, 0], [6, Infinity]], startOffset: 20, endOffset: 35}, gap: true}, ]); }); it('detects an intersection in the middle of the range', function() { const range = new IndexedRowRange({ - bufferRange: [[2, 0], [6, 0]], + bufferRange: [[2, 0], [6, Infinity]], startOffset: 10, endOffset: 35, }); const rowSet = new Set([0, 3, 4, 8, 9]); assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ - {intersection: {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, gap: false}, + {intersection: {bufferRange: [[3, 0], [4, Infinity]], startOffset: 15, endOffset: 25}, gap: false}, ]); assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ - {intersection: {bufferRange: [[2, 0], [2, 0]], startOffset: 10, endOffset: 15}, gap: true}, - {intersection: {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, gap: false}, - {intersection: {bufferRange: [[5, 0], [6, 0]], startOffset: 25, endOffset: 35}, gap: true}, + {intersection: {bufferRange: [[2, 0], [2, Infinity]], startOffset: 10, endOffset: 15}, gap: true}, + {intersection: {bufferRange: [[3, 0], [4, Infinity]], startOffset: 15, endOffset: 25}, gap: false}, + {intersection: {bufferRange: [[5, 0], [6, Infinity]], startOffset: 25, endOffset: 35}, gap: true}, ]); }); it('detects an intersection at the end of the range', function() { const range = new IndexedRowRange({ - bufferRange: [[2, 0], [6, 0]], + bufferRange: [[2, 0], [6, Infinity]], startOffset: 10, endOffset: 35, }); const rowSet = new Set([4, 5, 6, 7, 10, 11]); assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ - {intersection: {bufferRange: [[4, 0], [6, 0]], startOffset: 20, endOffset: 35}, gap: false}, + {intersection: {bufferRange: [[4, 0], [6, Infinity]], startOffset: 20, endOffset: 35}, gap: false}, ]); assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ - {intersection: {bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20}, gap: true}, - {intersection: {bufferRange: [[4, 0], [6, 0]], startOffset: 20, endOffset: 35}, gap: false}, + {intersection: {bufferRange: [[2, 0], [3, Infinity]], startOffset: 10, endOffset: 20}, gap: true}, + {intersection: {bufferRange: [[4, 0], [6, Infinity]], startOffset: 20, endOffset: 35}, gap: false}, ]); }); it('detects multiple intersections', function() { const range = new IndexedRowRange({ - bufferRange: [[2, 0], [8, 0]], + bufferRange: [[2, 0], [8, Infinity]], startOffset: 10, endOffset: 45, }); const rowSet = new Set([0, 3, 4, 6, 7, 10]); assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ - {intersection: {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, gap: false}, - {intersection: {bufferRange: [[6, 0], [7, 0]], startOffset: 30, endOffset: 40}, gap: false}, + {intersection: {bufferRange: [[3, 0], [4, Infinity]], startOffset: 15, endOffset: 25}, gap: false}, + {intersection: {bufferRange: [[6, 0], [7, Infinity]], startOffset: 30, endOffset: 40}, gap: false}, ]); assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ - {intersection: {bufferRange: [[2, 0], [2, 0]], startOffset: 10, endOffset: 15}, gap: true}, - {intersection: {bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25}, gap: false}, - {intersection: {bufferRange: [[5, 0], [5, 0]], startOffset: 25, endOffset: 30}, gap: true}, - {intersection: {bufferRange: [[6, 0], [7, 0]], startOffset: 30, endOffset: 40}, gap: false}, - {intersection: {bufferRange: [[8, 0], [8, 0]], startOffset: 40, endOffset: 45}, gap: true}, + {intersection: {bufferRange: [[2, 0], [2, Infinity]], startOffset: 10, endOffset: 15}, gap: true}, + {intersection: {bufferRange: [[3, 0], [4, Infinity]], startOffset: 15, endOffset: 25}, gap: false}, + {intersection: {bufferRange: [[5, 0], [5, Infinity]], startOffset: 25, endOffset: 30}, gap: true}, + {intersection: {bufferRange: [[6, 0], [7, Infinity]], startOffset: 30, endOffset: 40}, gap: false}, + {intersection: {bufferRange: [[8, 0], [8, Infinity]], startOffset: 40, endOffset: 45}, gap: true}, ]); }); @@ -170,7 +170,7 @@ describe('IndexedRowRange', function() { beforeEach(function() { original = new IndexedRowRange({ - bufferRange: [[3, 0], [5, 0]], + bufferRange: [[3, 0], [5, Infinity]], startOffset: 15, endOffset: 25, }); @@ -183,7 +183,7 @@ describe('IndexedRowRange', function() { it('modifies the buffer range and the buffer offset', function() { const changed = original.offsetBy(10, 3); assert.deepEqual(changed.serialize(), { - bufferRange: [[6, 0], [8, 0]], + bufferRange: [[6, 0], [8, Infinity]], startOffset: 25, endOffset: 35, }); @@ -192,7 +192,7 @@ describe('IndexedRowRange', function() { it('may specify separate start and end offsets', function() { const changed = original.offsetBy(10, 2, 30, 4); assert.deepEqual(changed.serialize(), { - bufferRange: [[5, 0], [9, 0]], + bufferRange: [[5, 0], [9, Infinity]], startOffset: 25, endOffset: 55, }); From 4f33f31b5f623336b344c990ff5c652e809e8fc7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 07:48:13 -0400 Subject: [PATCH 0174/4053] Region test coverage --- test/models/patch/region.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/models/patch/region.test.js b/test/models/patch/region.test.js index 5be6612b12..bc1ab8757c 100644 --- a/test/models/patch/region.test.js +++ b/test/models/patch/region.test.js @@ -6,7 +6,7 @@ describe('Regions', function() { beforeEach(function() { buffer = '0000\n1111\n2222\n3333\n4444\n5555\n'; - range = new IndexedRowRange({bufferRange: [[1, 0], [3, 0]], startOffset: 5, endOffset: 20}); + range = new IndexedRowRange({bufferRange: [[1, 0], [3, Infinity]], startOffset: 5, endOffset: 20}); }); describe('Addition', function() { @@ -19,6 +19,7 @@ describe('Regions', function() { it('has range accessors', function() { assert.strictEqual(addition.getRowRange(), range); assert.strictEqual(addition.getStartBufferRow(), 1); + assert.strictEqual(addition.getEndBufferRow(), 3); }); it('delegates some methods to its row range', function() { From a8857c0d2df232ae4e1b923dbfcb48e435d0ce7c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 07:55:41 -0400 Subject: [PATCH 0175/4053] Preserve end columns in Hunk methods --- lib/models/patch/hunk.js | 4 +- test/models/patch/hunk.test.js | 118 ++++++++++++++++----------------- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 3f577e5643..7ca5697d86 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -60,7 +60,7 @@ export default class Hunk { if (currentRow !== startRow) { regions.push(new Unchanged(new IndexedRowRange({ - bufferRange: [[currentRow, 0], [startRow - 1, 0]], + bufferRange: [[currentRow, 0], [startRow - 1, Infinity]], startOffset: currentPosition, endOffset: startPosition, }))); @@ -77,7 +77,7 @@ export default class Hunk { if (currentRow <= endRow) { regions.push(new Unchanged(new IndexedRowRange({ - bufferRange: [[currentRow, 0], [endRow, 0]], + bufferRange: [[currentRow, 0], [endRow, this.rowRange.bufferRange.end.column]], startOffset: currentPosition, endOffset: endPosition, }))); diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index faf10cc104..5f4509c71a 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -10,14 +10,14 @@ describe('Hunk', function() { newRowCount: 0, sectionHeading: 'sectionHeading', rowRange: new IndexedRowRange({ - bufferRange: [[1, 0], [10, 0]], + bufferRange: [[1, 0], [10, Infinity]], startOffset: 5, endOffset: 100, }), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 6, endOffset: 7})), - new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [4, 0]], startOffset: 8, endOffset: 9})), - new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 10, endOffset: 11})), + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, Infinity]], startOffset: 6, endOffset: 7})), + new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [4, Infinity]], startOffset: 8, endOffset: 9})), + new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, Infinity]], startOffset: 10, endOffset: 11})), ], }; @@ -29,14 +29,14 @@ describe('Hunk', function() { newRowCount: 3, sectionHeading: 'sectionHeading', rowRange: new IndexedRowRange({ - bufferRange: [[0, 0], [10, 0]], + bufferRange: [[0, 0], [10, Infinity]], startOffset: 0, endOffset: 100, }), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 6, endOffset: 7})), - new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [4, 0]], startOffset: 8, endOffset: 9})), - new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 10, endOffset: 11})), + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, Infinity]], startOffset: 6, endOffset: 7})), + new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [4, Infinity]], startOffset: 8, endOffset: 9})), + new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, Infinity]], startOffset: 10, endOffset: 11})), ], }); @@ -46,7 +46,7 @@ describe('Hunk', function() { assert.strictEqual(h.getNewRowCount(), 3); assert.strictEqual(h.getSectionHeading(), 'sectionHeading'); assert.deepEqual(h.getRowRange().serialize(), { - bufferRange: [[0, 0], [10, 0]], + bufferRange: [[0, 0], [10, Infinity]], startOffset: 0, endOffset: 100, }); @@ -61,28 +61,28 @@ describe('Hunk', function() { const h = new Hunk({ ...attrs, changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 6, endOffset: 7})), - new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [5, 0]], startOffset: 8, endOffset: 9})), - new NoNewline(new IndexedRowRange({bufferRange: [[10, 0], [10, 0]], startOffset: 100, endOffset: 120})), + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, Infinity]], startOffset: 6, endOffset: 7})), + new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [5, Infinity]], startOffset: 8, endOffset: 9})), + new NoNewline(new IndexedRowRange({bufferRange: [[10, 0], [10, Infinity]], startOffset: 100, endOffset: 120})), ], }); const nl = h.getNoNewlineRange(); assert.isNotNull(nl); - assert.deepEqual(nl.serialize(), [[10, 0], [10, 0]]); + assert.deepEqual(nl.serialize(), [[10, 0], [10, Infinity]]); }); it('creates its row range for decoration placement', function() { const h = new Hunk({ ...attrs, rowRange: new IndexedRowRange({ - bufferRange: [[3, 0], [6, 0]], + bufferRange: [[3, 0], [6, Infinity]], startOffset: 15, endOffset: 35, }), }); - assert.deepEqual(h.getBufferRange().serialize(), [[3, 0], [6, 0]]); + assert.deepEqual(h.getBufferRange().serialize(), [[3, 0], [6, Infinity]]); }); it('generates a patch section header', function() { @@ -101,14 +101,14 @@ describe('Hunk', function() { const h = new Hunk({ ...attrs, rowRange: new IndexedRowRange({ - bufferRange: [[0, 0], [11, 0]], + bufferRange: [[0, 0], [11, Infinity]], startOffset: 0, endOffset: 120, }), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [3, 0]], startOffset: 10, endOffset: 40})), - new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 50, endOffset: 70})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, 0]], startOffset: 70, endOffset: 100})), + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [3, Infinity]], startOffset: 10, endOffset: 40})), + new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, Infinity]], startOffset: 50, endOffset: 70})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, Infinity]], startOffset: 70, endOffset: 100})), ], }); @@ -116,36 +116,36 @@ describe('Hunk', function() { assert.lengthOf(regions, 6); assert.isTrue(regions[0].isUnchanged()); - assert.deepEqual(regions[0].range.serialize(), {bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 10}); + assert.deepEqual(regions[0].range.serialize(), {bufferRange: [[0, 0], [0, Infinity]], startOffset: 0, endOffset: 10}); assert.isTrue(regions[1].isAddition()); - assert.deepEqual(regions[1].range.serialize(), {bufferRange: [[1, 0], [3, 0]], startOffset: 10, endOffset: 40}); + assert.deepEqual(regions[1].range.serialize(), {bufferRange: [[1, 0], [3, Infinity]], startOffset: 10, endOffset: 40}); assert.isTrue(regions[2].isUnchanged()); - assert.deepEqual(regions[2].range.serialize(), {bufferRange: [[4, 0], [4, 0]], startOffset: 40, endOffset: 50}); + assert.deepEqual(regions[2].range.serialize(), {bufferRange: [[4, 0], [4, Infinity]], startOffset: 40, endOffset: 50}); assert.isTrue(regions[3].isDeletion()); - assert.deepEqual(regions[3].range.serialize(), {bufferRange: [[5, 0], [6, 0]], startOffset: 50, endOffset: 70}); + assert.deepEqual(regions[3].range.serialize(), {bufferRange: [[5, 0], [6, Infinity]], startOffset: 50, endOffset: 70}); assert.isTrue(regions[4].isDeletion()); - assert.deepEqual(regions[4].range.serialize(), {bufferRange: [[7, 0], [9, 0]], startOffset: 70, endOffset: 100}); + assert.deepEqual(regions[4].range.serialize(), {bufferRange: [[7, 0], [9, Infinity]], startOffset: 70, endOffset: 100}); assert.isTrue(regions[5].isUnchanged()); - assert.deepEqual(regions[5].range.serialize(), {bufferRange: [[10, 0], [11, 0]], startOffset: 100, endOffset: 120}); + assert.deepEqual(regions[5].range.serialize(), {bufferRange: [[10, 0], [11, Infinity]], startOffset: 100, endOffset: 120}); }); it('omits empty regions at the hunk beginning and end', function() { const h = new Hunk({ ...attrs, rowRange: new IndexedRowRange({ - bufferRange: [[1, 0], [9, 0]], + bufferRange: [[1, 0], [9, 20]], startOffset: 10, endOffset: 100, }), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [3, 0]], startOffset: 10, endOffset: 40})), - new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 50, endOffset: 70})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, 0]], startOffset: 70, endOffset: 100})), + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [3, Infinity]], startOffset: 10, endOffset: 40})), + new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, Infinity]], startOffset: 50, endOffset: 70})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, 20]], startOffset: 70, endOffset: 100})), ], }); @@ -153,23 +153,23 @@ describe('Hunk', function() { assert.lengthOf(regions, 4); assert.isTrue(regions[0].isAddition()); - assert.deepEqual(regions[0].range.serialize(), {bufferRange: [[1, 0], [3, 0]], startOffset: 10, endOffset: 40}); + assert.deepEqual(regions[0].range.serialize(), {bufferRange: [[1, 0], [3, Infinity]], startOffset: 10, endOffset: 40}); assert.isTrue(regions[1].isUnchanged()); - assert.deepEqual(regions[1].range.serialize(), {bufferRange: [[4, 0], [4, 0]], startOffset: 40, endOffset: 50}); + assert.deepEqual(regions[1].range.serialize(), {bufferRange: [[4, 0], [4, Infinity]], startOffset: 40, endOffset: 50}); assert.isTrue(regions[2].isDeletion()); - assert.deepEqual(regions[2].range.serialize(), {bufferRange: [[5, 0], [6, 0]], startOffset: 50, endOffset: 70}); + assert.deepEqual(regions[2].range.serialize(), {bufferRange: [[5, 0], [6, Infinity]], startOffset: 50, endOffset: 70}); assert.isTrue(regions[3].isDeletion()); - assert.deepEqual(regions[3].range.serialize(), {bufferRange: [[7, 0], [9, 0]], startOffset: 70, endOffset: 100}); + assert.deepEqual(regions[3].range.serialize(), {bufferRange: [[7, 0], [9, 20]], startOffset: 70, endOffset: 100}); }); it('returns a set of covered buffer rows', function() { const h = new Hunk({ ...attrs, rowRange: new IndexedRowRange({ - bufferRange: [[6, 0], [10, 0]], + bufferRange: [[6, 0], [10, 60]], startOffset: 30, endOffset: 55, }), @@ -181,7 +181,7 @@ describe('Hunk', function() { const h = new Hunk({ ...attrs, rowRange: new IndexedRowRange({ - bufferRange: [[3, 0], [5, 0]], + bufferRange: [[3, 0], [5, Infinity]], startOffset: 30, endOffset: 55, }), @@ -201,12 +201,12 @@ describe('Hunk', function() { oldRowCount: 6, newStartRow: 20, newRowCount: 7, - rowRange: new IndexedRowRange({bufferRange: [[2, 0], [12, 0]], startOffset: 0, endOffset: 0}), + rowRange: new IndexedRowRange({bufferRange: [[2, 0], [12, 10]], startOffset: 0, endOffset: 0}), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 0, endOffset: 0})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, 0]], startOffset: 0, endOffset: 0})), - new Addition(new IndexedRowRange({bufferRange: [[11, 0], [11, 0]], startOffset: 0, endOffset: 0})), - new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0})), + new Addition(new IndexedRowRange({bufferRange: [[3, 0], [5, Infinity]], startOffset: 0, endOffset: 0})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, Infinity]], startOffset: 0, endOffset: 0})), + new Addition(new IndexedRowRange({bufferRange: [[11, 0], [11, Infinity]], startOffset: 0, endOffset: 0})), + new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, Infinity]], startOffset: 0, endOffset: 0})), ], }); @@ -231,12 +231,12 @@ describe('Hunk', function() { oldRowCount: 6, newStartRow: 20, newRowCount: 7, - rowRange: new IndexedRowRange({bufferRange: [[2, 0], [12, 0]], startOffset: 0, endOffset: 0}), + rowRange: new IndexedRowRange({bufferRange: [[2, 0], [12, Infinity]], startOffset: 0, endOffset: 0}), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 0, endOffset: 0})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, 0]], startOffset: 0, endOffset: 0})), - new Addition(new IndexedRowRange({bufferRange: [[11, 0], [11, 0]], startOffset: 0, endOffset: 0})), - new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0})), + new Addition(new IndexedRowRange({bufferRange: [[3, 0], [5, Infinity]], startOffset: 0, endOffset: 0})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, Infinity]], startOffset: 0, endOffset: 0})), + new Addition(new IndexedRowRange({bufferRange: [[11, 0], [11, Infinity]], startOffset: 0, endOffset: 0})), + new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, Infinity]], startOffset: 0, endOffset: 0})), ], }); @@ -258,10 +258,10 @@ describe('Hunk', function() { const h0 = new Hunk({ ...attrs, changes: [ - new Addition(new IndexedRowRange({bufferRange: [[2, 0], [4, 0]], startOffset: 0, endOffset: 0})), - new Addition(new IndexedRowRange({bufferRange: [[6, 0], [6, 0]], startOffset: 0, endOffset: 0})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [10, 0]], startOffset: 0, endOffset: 0})), - new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, 0]], startOffset: 0, endOffset: 0})), + new Addition(new IndexedRowRange({bufferRange: [[2, 0], [4, Infinity]], startOffset: 0, endOffset: 0})), + new Addition(new IndexedRowRange({bufferRange: [[6, 0], [6, Infinity]], startOffset: 0, endOffset: 0})), + new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [10, Infinity]], startOffset: 0, endOffset: 0})), + new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, Infinity]], startOffset: 0, endOffset: 0})), ], }); assert.strictEqual(h0.changedLineCount(), 8); @@ -321,17 +321,17 @@ describe('Hunk', function() { oldRowCount: 6, newRowCount: 6, rowRange: new IndexedRowRange({ - bufferRange: [[1, 0], [13, 0]], + bufferRange: [[1, 0], [13, Infinity]], startOffset: 5, endOffset: 91, }), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20})), - new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [5, 0]], startOffset: 25, endOffset: 30})), - new Addition(new IndexedRowRange({bufferRange: [[7, 0], [7, 0]], startOffset: 35, endOffset: 40})), - new Deletion(new IndexedRowRange({bufferRange: [[8, 0], [9, 0]], startOffset: 40, endOffset: 50})), - new Addition(new IndexedRowRange({bufferRange: [[10, 0], [10, 0]], startOffset: 50, endOffset: 55})), - new NoNewline(new IndexedRowRange({bufferRange: [[13, 0], [13, 0]], startOffset: 65, endOffset: 92})), + new Addition(new IndexedRowRange({bufferRange: [[2, 0], [3, Infinity]], startOffset: 10, endOffset: 20})), + new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [5, Infinity]], startOffset: 25, endOffset: 30})), + new Addition(new IndexedRowRange({bufferRange: [[7, 0], [7, Infinity]], startOffset: 35, endOffset: 40})), + new Deletion(new IndexedRowRange({bufferRange: [[8, 0], [9, Infinity]], startOffset: 40, endOffset: 50})), + new Addition(new IndexedRowRange({bufferRange: [[10, 0], [10, Infinity]], startOffset: 50, endOffset: 55})), + new NoNewline(new IndexedRowRange({bufferRange: [[13, 0], [13, Infinity]], startOffset: 65, endOffset: 92})), ], }); @@ -362,10 +362,10 @@ describe('Hunk', function() { newStartRow: 1, oldRowCount: 1, newRowCount: 1, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [3, 0]], startOffset: 0, endOffset: 20}), + rowRange: new IndexedRowRange({bufferRange: [[0, 0], [3, Infinity]], startOffset: 0, endOffset: 20}), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), - new Deletion(new IndexedRowRange({bufferRange: [[2, 0], [2, 0]], startOffset: 10, endOffset: 15})), + new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, Infinity]], startOffset: 5, endOffset: 10})), + new Deletion(new IndexedRowRange({bufferRange: [[2, 0], [2, Infinity]], startOffset: 10, endOffset: 15})), ], }); From 2caafaa1f9ff080af70babc51d75991d1f0d0570 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 09:05:53 -0400 Subject: [PATCH 0176/4053] Don't assert against hunk row range columns --- test/helpers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/helpers.js b/test/helpers.js index 8f0caf2fba..31dcbc475c 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -163,7 +163,8 @@ class PatchBufferAssertions { const hunk = this.patch.getHunks()[hunkIndex]; assert.isDefined(hunk); - assert.deepEqual(hunk.getRowRange().serialize().bufferRange, [[startRow, 0], [endRow, 0]]); + assert.strictEqual(hunk.getRowRange().getStartBufferRow(), startRow); + assert.strictEqual(hunk.getRowRange().getEndBufferRow(), endRow); assert.strictEqual(hunk.getHeader(), header); assert.lengthOf(hunk.getChanges(), changes.length); From 79d86626b4d77fb79f58134d78eedfce3c237236 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 09:06:37 -0400 Subject: [PATCH 0177/4053] Patch coverage --- lib/models/patch/patch.js | 20 +-- test/models/patch/patch.test.js | 291 +++++++++++++++++++------------- 2 files changed, 184 insertions(+), 127 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 394839afc1..fc5d915c1e 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -316,7 +316,7 @@ export default class Patch { } const firstRow = firstChange.getStartBufferRow(); - return [[firstRow, 0], [firstRow, 0]]; + return [[firstRow, 0], [firstRow, Infinity]]; } getNextSelectionRange(lastPatch, lastSelectedRows) { @@ -328,9 +328,8 @@ export default class Patch { let lastSelectionIndex = 0; for (const hunk of lastPatch.getHunks()) { - let includesUnselectedChange = false; let includesMax = false; - let hunkSelectionIndex = 0; + let hunkSelectionOffset = 0; changeLoop: for (const change of hunk.getChanges()) { const intersections = change.getRowRange().intersectRowsIn(lastSelectedRows, this.getBufferText(), true); @@ -340,11 +339,8 @@ export default class Patch { const delta = includesMax ? lastMax - intersection.getStartBufferRow() + 1 : intersection.bufferRowCount(); if (gap) { - // This hunk includes at least one change row that was *not* selected. - includesUnselectedChange = true; - // Range of unselected changes. - hunkSelectionIndex += delta; + hunkSelectionOffset += delta; } if (includesMax) { @@ -353,9 +349,7 @@ export default class Patch { } } - if (includesUnselectedChange) { - lastSelectionIndex += hunkSelectionIndex; - } + lastSelectionIndex += hunkSelectionOffset; if (includesMax) { break; @@ -374,7 +368,7 @@ export default class Patch { } } - return [[newSelectionRow, 0], [newSelectionRow, 0]]; + return [[newSelectionRow, 0], [newSelectionRow, Infinity]]; } toString() { @@ -434,6 +428,10 @@ export const nullPatch = { return this; }, + getFirstChangeRange() { + return [[0, 0], [0, 0]]; + }, + getNextSelectionRange() { return [[0, 0], [0, 0]]; }, diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 320bef70bf..d17441d3a4 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -21,28 +21,22 @@ describe('Patch', function() { it('computes the total changed line count', function() { const hunks = [ new Hunk({ - oldStartRow: 0, - newStartRow: 0, - oldRowCount: 1, - newRowCount: 1, + oldStartRow: 0, newStartRow: 0, oldRowCount: 1, newRowCount: 1, sectionHeading: 'zero', - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [5, 0]], startOffset: 0, endOffset: 30}), + rowRange: buildRange(0, 5), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), - new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [4, 0]], startOffset: 15, endOffset: 25})), + new Addition(buildRange(1)), + new Deletion(buildRange(3, 4)), ], }), new Hunk({ - oldStartRow: 0, - newStartRow: 0, - oldRowCount: 1, - newRowCount: 1, + oldStartRow: 0, newStartRow: 0, oldRowCount: 1, newRowCount: 1, sectionHeading: 'one', - rowRange: new IndexedRowRange({bufferRange: [[6, 0], [15, 0]], startOffset: 30, endOffset: 80}), + rowRange: buildRange(6, 15), changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [7, 0]], startOffset: 35, endOffset: 40})), - new Deletion(new IndexedRowRange({bufferRange: [[9, 0], [11, 0]], startOffset: 45, endOffset: 60})), - new Addition(new IndexedRowRange({bufferRange: [[12, 0], [14, 0]], startOffset: 60, endOffset: 75})), + new Deletion(buildRange(7)), + new Deletion(buildRange(9, 11)), + new Addition(buildRange(12, 14)), ], }), ]; @@ -54,12 +48,9 @@ describe('Patch', function() { it('computes the maximum number of digits needed to display a diff line number', function() { const hunks = [ new Hunk({ - oldStartRow: 0, - oldRowCount: 1, - newStartRow: 0, - newRowCount: 1, + oldStartRow: 0, oldRowCount: 1, newStartRow: 0, newRowCount: 1, sectionHeading: 'zero', - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [5, 0]], startOffset: 0, endOffset: 30}), + rowRange: buildRange(0, 5), changes: [], }), new Hunk({ @@ -68,7 +59,7 @@ describe('Patch', function() { newStartRow: 95, newRowCount: 3, sectionHeading: 'one', - rowRange: new IndexedRowRange({bufferRange: [[6, 0], [15, 0]], startOffset: 30, endOffset: 80}), + rowRange: buildRange(6, 15), changes: [], }), ]; @@ -146,9 +137,9 @@ describe('Patch', function() { endRow: 9, header: '@@ -12,9 +12,7 @@', changes: [ - {kind: 'addition', string: '+0008\n', range: [[1, 0], [1, 0]]}, - {kind: 'deletion', string: '-0013\n-0014\n', range: [[5, 0], [6, 0]]}, - {kind: 'deletion', string: '-0016\n', range: [[8, 0], [8, 0]]}, + {kind: 'addition', string: '+0008\n', range: [[1, 0], [1, Infinity]]}, + {kind: 'deletion', string: '-0013\n-0014\n', range: [[5, 0], [6, Infinity]]}, + {kind: 'deletion', string: '-0016\n', range: [[8, 0], [8, Infinity]]}, ], }, ); @@ -172,8 +163,8 @@ describe('Patch', function() { endRow: 4, header: '@@ -3,4 +3,4 @@', changes: [ - {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, 0]]}, - {kind: 'addition', string: '+0005\n', range: [[3, 0], [3, 0]]}, + {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, Infinity]]}, + {kind: 'addition', string: '+0005\n', range: [[3, 0], [3, Infinity]]}, ], }, { @@ -181,8 +172,8 @@ describe('Patch', function() { endRow: 14, header: '@@ -12,9 +12,8 @@', changes: [ - {kind: 'deletion', string: '-0015\n-0016\n', range: [[11, 0], [12, 0]]}, - {kind: 'addition', string: '+0017\n', range: [[13, 0], [13, 0]]}, + {kind: 'deletion', string: '-0015\n-0016\n', range: [[11, 0], [12, Infinity]]}, + {kind: 'addition', string: '+0017\n', range: [[13, 0], [13, Infinity]]}, ], }, { @@ -190,8 +181,8 @@ describe('Patch', function() { endRow: 17, header: '@@ -32,1 +31,2 @@', changes: [ - {kind: 'addition', string: '+0025\n', range: [[16, 0], [16, 0]]}, - {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[17, 0], [17, 0]]}, + {kind: 'addition', string: '+0025\n', range: [[16, 0], [16, Infinity]]}, + {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[17, 0], [17, Infinity]]}, ], }, ); @@ -202,14 +193,11 @@ describe('Patch', function() { const hunks = [ new Hunk({ - oldStartRow: 1, - oldRowCount: 5, - newStartRow: 1, - newRowCount: 0, + oldStartRow: 1, oldRowCount: 5, newStartRow: 1, newRowCount: 0, sectionHeading: 'zero', - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [5, 0]], startOffset: 0, endOffset: 43}), + rowRange: buildRange(0, 5, 6), changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[0, 0], [5, 0]], startOffset: 0, endOffset: 43})), + new Deletion(buildRange(0, 5, 6)), ], }), ]; @@ -224,8 +212,8 @@ describe('Patch', function() { endRow: 5, header: '@@ -1,5 +1,3 @@', changes: [ - {kind: 'deletion', string: '-line-1\n', range: [[1, 0], [1, 0]]}, - {kind: 'deletion', string: '-line-3\n-line-4\n', range: [[3, 0], [4, 0]]}, + {kind: 'deletion', string: '-line-1\n', range: [[1, 0], [1, Infinity]]}, + {kind: 'deletion', string: '-line-3\n-line-4\n', range: [[3, 0], [4, Infinity]]}, ], }, ); @@ -235,13 +223,10 @@ describe('Patch', function() { const bufferText = '0000\n0001\n0002\n'; const hunks = [ new Hunk({ - oldStartRow: 1, - oldRowCount: 3, - newStartRow: 1, - newRowCount: 0, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 0, + rowRange: buildRange(0, 2), changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + new Deletion(buildRange(0, 2)), ], }), ]; @@ -271,8 +256,8 @@ describe('Patch', function() { endRow: 8, header: '@@ -13,7 +13,8 @@', changes: [ - {kind: 'deletion', string: '-0008\n', range: [[1, 0], [1, 0]]}, - {kind: 'addition', string: '+0012\n+0013\n', range: [[5, 0], [6, 0]]}, + {kind: 'deletion', string: '-0008\n', range: [[1, 0], [1, Infinity]]}, + {kind: 'addition', string: '+0012\n+0013\n', range: [[5, 0], [6, Infinity]]}, ], }, ); @@ -298,8 +283,8 @@ describe('Patch', function() { endRow: 5, header: '@@ -3,5 +3,4 @@', changes: [ - {kind: 'addition', string: '+0001\n', range: [[1, 0], [1, 0]]}, - {kind: 'deletion', string: '-0004\n-0005\n', range: [[3, 0], [4, 0]]}, + {kind: 'addition', string: '+0001\n', range: [[1, 0], [1, Infinity]]}, + {kind: 'deletion', string: '-0004\n-0005\n', range: [[3, 0], [4, Infinity]]}, ], }, { @@ -307,8 +292,8 @@ describe('Patch', function() { endRow: 13, header: '@@ -13,7 +12,7 @@', changes: [ - {kind: 'addition', string: '+0016\n', range: [[11, 0], [11, 0]]}, - {kind: 'deletion', string: '-0017\n', range: [[12, 0], [12, 0]]}, + {kind: 'addition', string: '+0016\n', range: [[11, 0], [11, Infinity]]}, + {kind: 'deletion', string: '-0017\n', range: [[12, 0], [12, Infinity]]}, ], }, { @@ -316,7 +301,7 @@ describe('Patch', function() { endRow: 16, header: '@@ -25,3 +24,2 @@', changes: [ - {kind: 'deletion', string: '-0020\n', range: [[15, 0], [15, 0]]}, + {kind: 'deletion', string: '-0020\n', range: [[15, 0], [15, Infinity]]}, ], }, { @@ -324,8 +309,8 @@ describe('Patch', function() { endRow: 19, header: '@@ -30,2 +28,1 @@', changes: [ - {kind: 'deletion', string: '-0025\n', range: [[18, 0], [18, 0]]}, - {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[19, 0], [19, 0]]}, + {kind: 'deletion', string: '-0025\n', range: [[18, 0], [18, Infinity]]}, + {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[19, 0], [19, Infinity]]}, ], }, ); @@ -342,8 +327,8 @@ describe('Patch', function() { endRow: 6, header: '@@ -3,5 +3,4 @@', changes: [ - {kind: 'addition', string: '+0001\n+0002\n', range: [[1, 0], [2, 0]]}, - {kind: 'deletion', string: '-0003\n-0004\n-0005\n', range: [[3, 0], [5, 0]]}, + {kind: 'addition', string: '+0001\n+0002\n', range: [[1, 0], [2, 4]]}, + {kind: 'deletion', string: '-0003\n-0004\n-0005\n', range: [[3, 0], [5, 4]]}, ], }, { @@ -351,9 +336,9 @@ describe('Patch', function() { endRow: 18, header: '@@ -13,7 +12,9 @@', changes: [ - {kind: 'deletion', string: '-0008\n-0009\n', range: [[8, 0], [9, 0]]}, - {kind: 'addition', string: '+0012\n+0013\n+0014\n+0015\n+0016\n', range: [[12, 0], [16, 0]]}, - {kind: 'deletion', string: '-0017\n', range: [[17, 0], [17, 0]]}, + {kind: 'deletion', string: '-0008\n-0009\n', range: [[8, 0], [9, 4]]}, + {kind: 'addition', string: '+0012\n+0013\n+0014\n+0015\n+0016\n', range: [[12, 0], [16, 4]]}, + {kind: 'deletion', string: '-0017\n', range: [[17, 0], [17, 4]]}, ], }, { @@ -361,8 +346,8 @@ describe('Patch', function() { endRow: 23, header: '@@ -25,3 +26,4 @@', changes: [ - {kind: 'deletion', string: '-0020\n', range: [[20, 0], [20, 0]]}, - {kind: 'addition', string: '+0021\n+0022\n', range: [[21, 0], [22, 0]]}, + {kind: 'deletion', string: '-0020\n', range: [[20, 0], [20, 4]]}, + {kind: 'addition', string: '+0021\n+0022\n', range: [[21, 0], [22, 4]]}, ], }, { @@ -370,8 +355,8 @@ describe('Patch', function() { endRow: 26, header: '@@ -30,2 +32,1 @@', changes: [ - {kind: 'deletion', string: '-0025\n', range: [[25, 0], [25, 0]]}, - {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[26, 0], [26, 0]]}, + {kind: 'deletion', string: '-0025\n', range: [[25, 0], [25, 4]]}, + {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[26, 0], [26, 26]]}, ], }, ); @@ -381,13 +366,10 @@ describe('Patch', function() { const bufferText = '0000\n0001\n0002\n'; const hunks = [ new Hunk({ - oldStartRow: 1, - oldRowCount: 0, - newStartRow: 1, - newRowCount: 3, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 3, + rowRange: buildRange(0, 2), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + new Addition(buildRange(0, 2)), ], }), ]; @@ -401,7 +383,7 @@ describe('Patch', function() { endRow: 2, header: '@@ -1,3 +1,1 @@', changes: [ - {kind: 'deletion', string: '-0001\n-0002\n', range: [[1, 0], [2, 0]]}, + {kind: 'deletion', string: '-0001\n-0002\n', range: [[1, 0], [2, Infinity]]}, ], }, ); @@ -415,9 +397,9 @@ describe('Patch', function() { oldRowCount: 0, newStartRow: 1, newRowCount: 3, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + rowRange: buildRange(0, 2), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + new Addition(buildRange(0, 2)), ], }), ]; @@ -436,6 +418,30 @@ describe('Patch', function() { }); }); + describe('getFirstChangeRange', function() { + it('accesses the range of the first change from the first hunk', function() { + const patch = buildPatchFixture(); + assert.deepEqual(patch.getFirstChangeRange(), [[1, 0], [1, Infinity]]); + }); + + it('returns the origin if the first hunk is empty', function() { + const hunks = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 0, + rowRange: buildRange(0), + changes: [], + }), + ]; + const patch = new Patch({status: 'modified', hunks, bufferText: ''}); + assert.deepEqual(patch.getFirstChangeRange(), [[0, 0], [0, 0]]); + }); + + it('returns the origin if the patch is empty', function() { + const patch = new Patch({status: 'modified', hunks: [], bufferText: ''}); + assert.deepEqual(patch.getFirstChangeRange(), [[0, 0], [0, 0]]); + }); + }); + describe('next selection range derivation', function() { it('selects the first change region after the highest buffer row', function() { const lastPatch = buildPatchFixture(); @@ -493,7 +499,76 @@ describe('Patch', function() { const nextRange = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); // Original buffer row 14 = the next changed row = new buffer row 11 - assert.deepEqual(nextRange, [[11, 0], [11, 0]]); + assert.deepEqual(nextRange, [[11, 0], [11, Infinity]]); + }); + + it('offsets the chosen selection index by hunks that were completely selected', function() { + const lastPatch = new Patch({ + status: 'modified', + hunks: [ + new Hunk({ + oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 3, + rowRange: buildRange(0, 5), + changes: [ + new Addition(buildRange(1, 2)), + new Deletion(buildRange(3, 4)), + ], + }), + new Hunk({ + oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, + rowRange: buildRange(6, 11), + changes: [ + new Addition(buildRange(7, 8)), + new Deletion(buildRange(9, 10)), + ], + }), + ], + bufferText: '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n0010\n0011\n', + }); + // Select: + // * all changes from hunk 0 + // * partial addition (8 of 7-8) from hunk 1 + const lastSelectedRows = new Set([1, 2, 3, 4, 8]); + + const nextPatch = new Patch({ + status: 'modified', + hunks: [ + new Hunk({ + oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, + rowRange: buildRange(0, 5), + changes: [ + new Addition(buildRange(1, 1)), + new Deletion(buildRange(3, 4)), + ], + }), + ], + bufferText: '0006\n0007\n0008\n0009\n0010\n0011\n', + }); + + const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); + assert.deepEqual(range, [[3, 0], [3, Infinity]]); + }); + + it('selects the first row of the first change of the patch if no rows were selected before', function() { + const lastPatch = buildPatchFixture(); + const lastSelectedRows = new Set(); + + const nextPatch = new Patch({ + status: 'modified', + hunks: [ + new Hunk({ + oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 4, + rowRange: buildRange(0, 4), + changes: [ + new Addition(buildRange(1, 2)), + new Deletion(buildRange(3, 3)), + ], + }), + ], + }); + + const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); + assert.deepEqual(range, [[1, 0], [1, Infinity]]); }); }); @@ -504,26 +579,20 @@ describe('Patch', function() { // patch buffer: 0000.1111.2222.3333.4444.5555.6666.7777.8888.9999. const hunk0 = new Hunk({ - oldStartRow: 0, - newStartRow: 0, - oldRowCount: 2, - newRowCount: 3, + oldStartRow: 0, newStartRow: 0, oldRowCount: 2, newRowCount: 3, sectionHeading: 'zero', - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + rowRange: buildRange(0, 2), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + new Addition(buildRange(1)), ], }); const hunk1 = new Hunk({ - oldStartRow: 5, - newStartRow: 6, - oldRowCount: 4, - newRowCount: 2, + oldStartRow: 5, newStartRow: 6, oldRowCount: 4, newRowCount: 2, sectionHeading: 'one', - rowRange: new IndexedRowRange({bufferRange: [[6, 0], [9, 0]], startOffset: 30, endOffset: 50}), + rowRange: buildRange(6, 9), changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [8, 0]], startOffset: 35, endOffset: 45})), + new Deletion(buildRange(7, 8)), ], }); @@ -551,14 +620,16 @@ describe('Patch', function() { assert.strictEqual(nullPatch.toString(), ''); assert.strictEqual(nullPatch.getChangedLineCount(), 0); assert.strictEqual(nullPatch.getMaxLineNumberWidth(), 0); + assert.deepEqual(nullPatch.getFirstChangeRange(), [[0, 0], [0, 0]]); + assert.deepEqual(nullPatch.getNextSelectionRange(), [[0, 0], [0, 0]]); }); }); -function buildRange(startRow, endRow = startRow, rowLength = 5) { +function buildRange(startRow, endRow = startRow, rowLength = 5, endRowLength = rowLength) { return new IndexedRowRange({ - bufferRange: [[startRow, 0], [endRow, 0]], + bufferRange: [[startRow, 0], [endRow, endRowLength - 1]], startOffset: startRow * rowLength, - endOffset: (endRow + 1) * rowLength, + endOffset: endRow * rowLength + endRowLength, }); } @@ -571,52 +642,40 @@ function buildPatchFixture() { const hunks = [ new Hunk({ - oldStartRow: 3, - oldRowCount: 4, - newStartRow: 3, - newRowCount: 5, + oldStartRow: 3, oldRowCount: 4, newStartRow: 3, newRowCount: 5, sectionHeading: 'zero', - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [6, 0]], startOffset: 0, endOffset: 35}), + rowRange: buildRange(0, 6), changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 5, endOffset: 15})), - new Addition(new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 15, endOffset: 30})), + new Deletion(buildRange(1, 2)), + new Addition(buildRange(3, 5)), ], }), new Hunk({ - oldStartRow: 12, - oldRowCount: 9, - newStartRow: 13, - newRowCount: 7, + oldStartRow: 12, oldRowCount: 9, newStartRow: 13, newRowCount: 7, sectionHeading: 'one', - rowRange: new IndexedRowRange({bufferRange: [[7, 0], [18, 0]], startOffset: 35, endOffset: 95}), + rowRange: buildRange(7, 18), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[8, 0], [9, 0]], startOffset: 40, endOffset: 50})), - new Deletion(new IndexedRowRange({bufferRange: [[12, 0], [16, 0]], startOffset: 60, endOffset: 85})), - new Addition(new IndexedRowRange({bufferRange: [[17, 0], [17, 0]], startOffset: 85, endOffset: 90})), + new Addition(buildRange(8, 9)), + new Deletion(buildRange(12, 16)), + new Addition(buildRange(17, 17)), ], }), new Hunk({ - oldStartRow: 26, - oldRowCount: 4, - newStartRow: 25, - newRowCount: 3, + oldStartRow: 26, oldRowCount: 4, newStartRow: 25, newRowCount: 3, sectionHeading: 'two', - rowRange: new IndexedRowRange({bufferRange: [[19, 0], [23, 0]], startOffset: 95, endOffset: 120}), + rowRange: buildRange(19, 23), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[20, 0], [20, 0]], startOffset: 100, endOffset: 105})), - new Deletion(new IndexedRowRange({bufferRange: [[21, 0], [22, 0]], startOffset: 105, endOffset: 115})), + new Addition(buildRange(20)), + new Deletion(buildRange(21, 22)), ], }), new Hunk({ - oldStartRow: 32, - oldRowCount: 1, - newStartRow: 30, - newRowCount: 2, + oldStartRow: 32, oldRowCount: 1, newStartRow: 30, newRowCount: 2, sectionHeading: 'three', - rowRange: new IndexedRowRange({bufferRange: [[24, 0], [26, 0]], startOffset: 120, endOffset: 157}), + rowRange: buildRange(24, 26), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[25, 0], [25, 0]], startOffset: 125, endOffset: 130})), - new NoNewline(new IndexedRowRange({bufferRange: [[26, 0], [26, 0]], startOffset: 130, endOffset: 157})), + new Addition(buildRange(25)), + new NoNewline(buildRange(26, 26, 5, 27)), ], }), ]; From c6b7eb82e4b688974a46d5491fdc39049fa1156a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 09:42:02 -0400 Subject: [PATCH 0178/4053] FilePatch coverage --- test/helpers.js | 11 ++ test/models/patch/file-patch.test.js | 231 ++++++++++++--------------- test/models/patch/patch.test.js | 11 +- 3 files changed, 112 insertions(+), 141 deletions(-) diff --git a/test/helpers.js b/test/helpers.js index 31dcbc475c..ea359743a4 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -16,6 +16,7 @@ import WorkerManager from '../lib/worker-manager'; import ContextMenuInterceptor from '../lib/context-menu-interceptor'; import getRepoPipelineManager from '../lib/get-repo-pipeline-manager'; import {clearRelayExpectations} from '../lib/relay-network-layer-manager'; +import IndexedRowRange from '../lib/models/indexed-row-range'; assert.autocrlfEqual = (actual, expected, ...args) => { const newActual = actual.replace(/\r\n/g, '\n'); @@ -154,6 +155,16 @@ export function assertEqualSortedArraysByKey(arr1, arr2, key) { // Helpers for test/models/patch classes +// Quickly construct an IndexedRowRange into a buffer that has uniform line lengths (except for possibly the final +// line.) The default parameters are chosen to match buffers of the form "0000\n0001\n0002\n....". +export function buildRange(startRow, endRow = startRow, rowLength = 5, endRowLength = rowLength) { + return new IndexedRowRange({ + bufferRange: [[startRow, 0], [endRow, endRowLength - 1]], + startOffset: startRow * rowLength, + endOffset: endRow * rowLength + endRowLength, + }); +} + class PatchBufferAssertions { constructor(patch) { this.patch = patch; diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 81ad43e0a9..b621425466 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -3,21 +3,17 @@ import File, {nullFile} from '../../../lib/models/patch/file'; import Patch from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; -import IndexedRowRange from '../../../lib/models/indexed-row-range'; -import {assertInFilePatch} from '../../helpers'; +import {assertInFilePatch, buildRange} from '../../helpers'; describe('FilePatch', function() { it('delegates methods to its files and patch', function() { - const bufferText = '0000\n0001\n'; + const bufferText = '0000\n0001\n0002\n'; const hunks = [ new Hunk({ - oldStartRow: 2, - oldRowCount: 1, - newStartRow: 2, - newRowCount: 2, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 10}), + oldStartRow: 2, oldRowCount: 1, newStartRow: 2, newRowCount: 3, + rowRange: buildRange(0, 2), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + new Addition(buildRange(1, 2)), ], }), ]; @@ -36,9 +32,27 @@ describe('FilePatch', function() { assert.strictEqual(filePatch.getNewMode(), '100755'); assert.isUndefined(filePatch.getNewSymlink()); - assert.strictEqual(filePatch.getByteSize(), 10); + assert.strictEqual(filePatch.getByteSize(), 15); assert.strictEqual(filePatch.getBufferText(), bufferText); assert.strictEqual(filePatch.getMaxLineNumberWidth(), 1); + + assert.deepEqual(filePatch.getFirstChangeRange(), [[1, 0], [1, Infinity]]); + + const nBufferText = '0001\n0002\n'; + const nHunks = [ + new Hunk({ + oldStartRow: 3, oldRowCount: 1, newStartRow: 3, newRowCount: 2, + rowRange: buildRange(0, 1), + changes: [ + new Addition(buildRange(1)), + ], + }), + ]; + const nPatch = new Patch({status: 'modified', hunks: nHunks, bufferText: nBufferText}); + const nFilePatch = new FilePatch(oldFile, newFile, nPatch); + + const range = nFilePatch.getNextSelectionRange(filePatch, new Set([1])); + assert.deepEqual(range, [[1, 0], [1, Infinity]]); }); it('accesses a file path from either side of the patch', function() { @@ -56,18 +70,15 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n'; const hunks = [ new Hunk({ - oldStartRow: 1, - oldRowCount: 0, - newStartRow: 1, - newRowCount: 0, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [9, 0]], startOffset: 0, endOffset: 50}), + oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 0, + rowRange: buildRange(0, 9), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), - new Addition(new IndexedRowRange({bufferRange: [[3, 0], [3, 0]], startOffset: 15, endOffset: 20})), - new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 20, endOffset: 25})), - new Addition(new IndexedRowRange({bufferRange: [[5, 0], [6, 0]], startOffset: 25, endOffset: 35})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [7, 0]], startOffset: 35, endOffset: 40})), - new Addition(new IndexedRowRange({bufferRange: [[8, 0], [8, 0]], startOffset: 40, endOffset: 45})), + new Addition(buildRange(1)), + new Addition(buildRange(3)), + new Deletion(buildRange(4)), + new Addition(buildRange(5, 6)), + new Deletion(buildRange(7)), + new Addition(buildRange(8)), ], }), ]; @@ -78,16 +89,16 @@ describe('FilePatch', function() { const additionRanges = filePatch.getAdditionRanges(); assert.deepEqual(additionRanges.map(range => range.serialize()), [ - [[1, 0], [1, 0]], - [[3, 0], [3, 0]], - [[5, 0], [6, 0]], - [[8, 0], [8, 0]], + [[1, 0], [1, 4]], + [[3, 0], [3, 4]], + [[5, 0], [6, 4]], + [[8, 0], [8, 4]], ]); const deletionRanges = filePatch.getDeletionRanges(); assert.deepEqual(deletionRanges.map(range => range.serialize()), [ - [[4, 0], [4, 0]], - [[7, 0], [7, 0]], + [[4, 0], [4, 4]], + [[7, 0], [7, 4]], ]); const noNewlineRanges = filePatch.getNoNewlineRanges(); @@ -107,14 +118,11 @@ describe('FilePatch', function() { const bufferText = '0000\n No newline at end of file\n'; const hunks = [ new Hunk({ - oldStartRow: 1, - oldRowCount: 0, - newStartRow: 1, - newRowCount: 0, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 32}), + oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 0, + rowRange: buildRange(0, 1), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 5})), - new NoNewline(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 32})), + new Addition(buildRange(0)), + new NoNewline(buildRange(1, 1, 5, 26)), ], }), ]; @@ -125,7 +133,7 @@ describe('FilePatch', function() { const noNewlineRanges = filePatch.getNoNewlineRanges(); assert.deepEqual(noNewlineRanges.map(range => range.serialize()), [ - [[1, 0], [1, 0]], + [[1, 0], [1, 25]], ]); }); @@ -213,14 +221,11 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n0002\n0003\n0004\n'; const hunks = [ new Hunk({ - oldStartRow: 5, - oldRowCount: 3, - newStartRow: 5, - newRowCount: 4, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [4, 0]], startOffset: 0, endOffset: 25}), + oldStartRow: 5, oldRowCount: 3, newStartRow: 5, newRowCount: 4, + rowRange: buildRange(0, 4), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 5, endOffset: 15})), - new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [3, 0]], startOffset: 15, endOffset: 20})), + new Addition(buildRange(1, 2)), + new Deletion(buildRange(3)), ], }), ]; @@ -242,8 +247,8 @@ describe('FilePatch', function() { endRow: 3, header: '@@ -5,3 +5,3 @@', changes: [ - {kind: 'addition', string: '+0001\n', range: [[1, 0], [1, 0]]}, - {kind: 'deletion', string: '-0003\n', range: [[2, 0], [2, 0]]}, + {kind: 'addition', string: '+0001\n', range: [[1, 0], [1, Infinity]]}, + {kind: 'deletion', string: '-0003\n', range: [[2, 0], [2, Infinity]]}, ], }, ); @@ -256,13 +261,10 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n0002\n'; const hunks = [ new Hunk({ - oldStartRow: 1, - oldRowCount: 3, - newStartRow: 1, - newRowCount: 0, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 0, + rowRange: buildRange(0, 2), changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + new Deletion(buildRange(0, 2)), ], }), ]; @@ -286,7 +288,7 @@ describe('FilePatch', function() { endRow: 2, header: '@@ -1,3 +1,1 @@', changes: [ - {kind: 'deletion', string: '-0001\n-0002\n', range: [[1, 0], [2, 0]]}, + {kind: 'deletion', string: '-0001\n-0002\n', range: [[1, 0], [2, Infinity]]}, ], }, ); @@ -305,7 +307,7 @@ describe('FilePatch', function() { endRow: 2, header: '@@ -1,3 +1,0 @@', changes: [ - {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 0]]}, + {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 4]]}, ], }, ); @@ -315,13 +317,10 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n0002\n'; const hunks = [ new Hunk({ - oldStartRow: 1, - oldRowCount: 3, - newStartRow: 1, - newRowCount: 0, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 0, + rowRange: buildRange(0, 2), changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + new Deletion(buildRange(0, 2)), ], }), ]; @@ -341,13 +340,10 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n'; const hunks = [ new Hunk({ - oldStartRow: 10, - oldRowCount: 2, - newStartRow: 10, - newRowCount: 3, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + oldStartRow: 10, oldRowCount: 2, newStartRow: 10, newRowCount: 3, + rowRange: buildRange(0, 2), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + new Addition(buildRange(1)), ], }), new Hunk({ @@ -355,9 +351,9 @@ describe('FilePatch', function() { oldRowCount: 3, newStartRow: 19, newRowCount: 2, - rowRange: new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 15, endOffset: 35}), + rowRange: buildRange(3, 5), changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 20, endOffset: 25})), + new Deletion(buildRange(4)), ], }), ]; @@ -374,7 +370,7 @@ describe('FilePatch', function() { endRow: 2, header: '@@ -20,3 +18,2 @@', changes: [ - {kind: 'deletion', string: '-0004\n', range: [[1, 0], [1, 0]]}, + {kind: 'deletion', string: '-0004\n', range: [[1, 0], [1, Infinity]]}, ], }, ); @@ -385,14 +381,11 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n0002\n0003\n0004\n'; const hunks = [ new Hunk({ - oldStartRow: 5, - oldRowCount: 3, - newStartRow: 5, - newRowCount: 4, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [4, 0]], startOffset: 0, endOffset: 25}), + oldStartRow: 5, oldRowCount: 3, newStartRow: 5, newRowCount: 4, + rowRange: buildRange(0, 4), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 5, endOffset: 15})), - new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [3, 0]], startOffset: 15, endOffset: 20})), + new Addition(buildRange(1, 2)), + new Deletion(buildRange(3)), ], }), ]; @@ -414,8 +407,8 @@ describe('FilePatch', function() { endRow: 4, header: '@@ -5,4 +5,4 @@', changes: [ - {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, 0]]}, - {kind: 'addition', string: '+0003\n', range: [[3, 0], [3, 0]]}, + {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, Infinity]]}, + {kind: 'addition', string: '+0003\n', range: [[3, 0], [3, Infinity]]}, ], }, ); @@ -428,13 +421,10 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n0002\n'; const hunks = [ new Hunk({ - oldStartRow: 1, - oldRowCount: 0, - newStartRow: 1, - newRowCount: 3, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 3, + rowRange: buildRange(0, 2), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15})), + new Addition(buildRange(0, 2)), ], }), ]; @@ -452,7 +442,7 @@ describe('FilePatch', function() { endRow: 2, header: '@@ -1,3 +1,2 @@', changes: [ - {kind: 'deletion', string: '-0002\n', range: [[2, 0], [2, 0]]}, + {kind: 'deletion', string: '-0002\n', range: [[2, 0], [2, Infinity]]}, ], }, ); @@ -467,7 +457,7 @@ describe('FilePatch', function() { endRow: 2, header: '@@ -1,3 +1,0 @@', changes: [ - {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 0]]}, + {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 4]]}, ], }, ); @@ -484,7 +474,7 @@ describe('FilePatch', function() { endRow: 2, header: '@@ -1,3 +1,0 @@', changes: [ - {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 0]]}, + {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 4]]}, ], }, ); @@ -496,23 +486,17 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n'; const hunks = [ new Hunk({ - oldStartRow: 10, - oldRowCount: 2, - newStartRow: 10, - newRowCount: 3, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), + oldStartRow: 10, oldRowCount: 2, newStartRow: 10, newRowCount: 3, + rowRange: buildRange(0, 2), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), + new Addition(buildRange(1)), ], }), new Hunk({ - oldStartRow: 20, - oldRowCount: 3, - newStartRow: 19, - newRowCount: 2, - rowRange: new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 15, endOffset: 35}), + oldStartRow: 20, oldRowCount: 3, newStartRow: 19, newRowCount: 2, + rowRange: buildRange(3, 5), changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 20, endOffset: 25})), + new Deletion(buildRange(4)), ], }), ]; @@ -529,7 +513,7 @@ describe('FilePatch', function() { endRow: 2, header: '@@ -10,3 +10,2 @@', changes: [ - {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, 0]]}, + {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, Infinity]]}, ], }, ); @@ -540,24 +524,18 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n'; const hunks = [ new Hunk({ - oldStartRow: 10, - oldRowCount: 4, - newStartRow: 10, - newRowCount: 3, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [4, 0]], startOffset: 0, endOffset: 25}), + oldStartRow: 10, oldRowCount: 4, newStartRow: 10, newRowCount: 3, + rowRange: buildRange(0, 4), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), - new Deletion(new IndexedRowRange({bufferRange: [[2, 0], [3, 0]], startOffset: 10, endOffset: 20})), + new Addition(buildRange(1)), + new Deletion(buildRange(2, 3)), ], }), new Hunk({ - oldStartRow: 20, - oldRowCount: 2, - newStartRow: 20, - newRowCount: 3, - rowRange: new IndexedRowRange({bufferRange: [[5, 0], [7, 0]], startOffset: 25, endOffset: 40}), + oldStartRow: 20, oldRowCount: 2, newStartRow: 20, newRowCount: 3, + rowRange: buildRange(5, 7), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[6, 0], [6, 0]], startOffset: 30, endOffset: 35})), + new Addition(buildRange(6)), ], }), ]; @@ -587,14 +565,11 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n No newline at end of file\n'; const hunks = [ new Hunk({ - oldStartRow: 1, - oldRowCount: 1, - newStartRow: 1, - newRowCount: 2, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 37}), + oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 2, + rowRange: buildRange(0, 2), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), - new NoNewline(new IndexedRowRange({bufferRange: [[2, 0], [2, 0]], startOffset: 10, endOffset: 37})), + new Addition(buildRange(1)), + new NoNewline(buildRange(2, 2, 5, 27)), ], }), ]; @@ -619,13 +594,10 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n'; const hunks = [ new Hunk({ - oldStartRow: 1, - oldRowCount: 0, - newStartRow: 1, - newRowCount: 2, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 10}), + oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 2, + rowRange: buildRange(0, 2), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 10})), + new Addition(buildRange(0, 2)), ], }), ]; @@ -656,13 +628,10 @@ describe('FilePatch', function() { const bufferText = '0000\n0001\n'; const hunks = [ new Hunk({ - oldStartRow: 1, - oldRowCount: 2, - newStartRow: 1, - newRowCount: 0, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 10}), + oldStartRow: 1, oldRowCount: 2, newStartRow: 1, newRowCount: 0, + rowRange: buildRange(0, 2), changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 10})), + new Deletion(buildRange(0, 2)), ], }), ]; @@ -692,7 +661,7 @@ describe('FilePatch', function() { }); it('has a nullFilePatch that stubs all FilePatch methods', function() { - const rowRange = new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 10}); + const rowRange = buildRange(0, 1); assert.isFalse(nullFilePatch.isPresent()); assert.isFalse(nullFilePatch.getOldFile().isPresent()); diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index d17441d3a4..00f115e09c 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -1,8 +1,7 @@ import Patch, {nullPatch} from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; -import IndexedRowRange from '../../../lib/models/indexed-row-range'; import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; -import {assertInPatch} from '../../helpers'; +import {assertInPatch, buildRange} from '../../helpers'; describe('Patch', function() { it('has some standard accessors', function() { @@ -625,14 +624,6 @@ describe('Patch', function() { }); }); -function buildRange(startRow, endRow = startRow, rowLength = 5, endRowLength = rowLength) { - return new IndexedRowRange({ - bufferRange: [[startRow, 0], [endRow, endRowLength - 1]], - startOffset: startRow * rowLength, - endOffset: endRow * rowLength + endRowLength, - }); -} - function buildPatchFixture() { const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n' + From 2ca4b5359b6117ca17d9b5346eebb3d456030abf Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 09:58:02 -0400 Subject: [PATCH 0179/4053] Build FilePatches with row ranges that end at the end of the final row --- lib/models/patch/builder.js | 8 ++++++-- test/models/patch/builder.test.js | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 5270aeebe5..3bce9deb72 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -112,6 +112,8 @@ function buildHunks(diff) { let LastChangeKind = null; let currentRangeStart = bufferRow; + let lastLineLength = 0; + let nextLineLength = 0; const finishCurrentRange = () => { if (currentRangeStart === bufferRow) { @@ -122,7 +124,7 @@ function buildHunks(diff) { changes.push( new LastChangeKind( new IndexedRowRange({ - bufferRange: [[currentRangeStart, 0], [bufferRow - 1, 0]], + bufferRange: [[currentRangeStart, 0], [bufferRow - 1, lastLineLength - 1]], startOffset, endOffset: bufferOffset, }), @@ -135,6 +137,7 @@ function buildHunks(diff) { for (const lineText of hunkData.lines) { const bufferLine = lineText.slice(1) + '\n'; + nextLineLength = bufferLine.length - 1; bufferText += bufferLine; const ChangeKind = CHANGEKIND[lineText[0]]; @@ -149,6 +152,7 @@ function buildHunks(diff) { LastChangeKind = ChangeKind; bufferOffset += bufferLine.length; bufferRow++; + lastLineLength = nextLineLength; } finishCurrentRange(); @@ -159,7 +163,7 @@ function buildHunks(diff) { newRowCount: hunkData.newLineCount, sectionHeading: hunkData.heading, rowRange: new IndexedRowRange({ - bufferRange: [[bufferStartRow, 0], [bufferRow - 1, 0]], + bufferRange: [[bufferStartRow, 0], [bufferRow - 1, nextLineLength - 1]], startOffset: bufferStartOffset, endOffset: bufferOffset, }), diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 6539a4e824..4794be5530 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -81,8 +81,8 @@ describe('buildFilePatch', function() { endRow: 8, header: '@@ -0,7 +0,6 @@', changes: [ - {kind: 'deletion', string: '-line-1\n-line-2\n-line-3\n', range: [[1, 0], [3, 0]]}, - {kind: 'addition', string: '+line-5\n+line-6\n', range: [[5, 0], [6, 0]]}, + {kind: 'deletion', string: '-line-1\n-line-2\n-line-3\n', range: [[1, 0], [3, 5]]}, + {kind: 'addition', string: '+line-5\n+line-6\n', range: [[5, 0], [6, 5]]}, ], }, { @@ -90,8 +90,8 @@ describe('buildFilePatch', function() { endRow: 12, header: '@@ -10,3 +11,3 @@', changes: [ - {kind: 'deletion', string: '-line-9\n', range: [[9, 0], [9, 0]]}, - {kind: 'addition', string: '+line-12\n', range: [[12, 0], [12, 0]]}, + {kind: 'deletion', string: '-line-9\n', range: [[9, 0], [9, 5]]}, + {kind: 'addition', string: '+line-12\n', range: [[12, 0], [12, 6]]}, ], }, { @@ -99,8 +99,8 @@ describe('buildFilePatch', function() { endRow: 18, header: '@@ -20,4 +21,4 @@', changes: [ - {kind: 'deletion', string: '-line-14\n-line-15\n', range: [[14, 0], [15, 0]]}, - {kind: 'addition', string: '+line-16\n+line-17\n', range: [[16, 0], [17, 0]]}, + {kind: 'deletion', string: '-line-14\n-line-15\n', range: [[14, 0], [15, 6]]}, + {kind: 'addition', string: '+line-16\n+line-17\n', range: [[16, 0], [17, 6]]}, ], }, ); @@ -214,7 +214,7 @@ describe('buildFilePatch', function() { endRow: 3, header: '@@ -1,4 +0,0 @@', changes: [ - {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n-line-3\n', range: [[0, 0], [3, 0]]}, + {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n-line-3\n', range: [[0, 0], [3, 5]]}, ], }, ); @@ -257,7 +257,7 @@ describe('buildFilePatch', function() { endRow: 2, header: '@@ -0,0 +1,3 @@', changes: [ - {kind: 'addition', string: '+line-0\n+line-1\n+line-2\n', range: [[0, 0], [2, 0]]}, + {kind: 'addition', string: '+line-0\n+line-1\n+line-2\n', range: [[0, 0], [2, 5]]}, ], }, ); @@ -295,9 +295,9 @@ describe('buildFilePatch', function() { endRow: 2, header: '@@ -0,1 +0,1 @@', changes: [ - {kind: 'addition', string: '+line-0\n', range: [[0, 0], [0, 0]]}, - {kind: 'deletion', string: '-line-1\n', range: [[1, 0], [1, 0]]}, - {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[2, 0], [2, 0]]}, + {kind: 'addition', string: '+line-0\n', range: [[0, 0], [0, 5]]}, + {kind: 'deletion', string: '-line-1\n', range: [[1, 0], [1, 5]]}, + {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[2, 0], [2, 25]]}, ], }); }); @@ -354,7 +354,7 @@ describe('buildFilePatch', function() { endRow: 1, header: '@@ -0,0 +0,2 @@', changes: [ - {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 0]]}, + {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 5]]}, ], }); }); @@ -409,7 +409,7 @@ describe('buildFilePatch', function() { endRow: 1, header: '@@ -0,2 +0,0 @@', changes: [ - {kind: 'deletion', string: '-line-0\n-line-1\n', range: [[0, 0], [1, 0]]}, + {kind: 'deletion', string: '-line-0\n-line-1\n', range: [[0, 0], [1, 5]]}, ], }); }); @@ -463,7 +463,7 @@ describe('buildFilePatch', function() { endRow: 1, header: '@@ -0,0 +0,2 @@', changes: [ - {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 0]]}, + {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 5]]}, ], }); }); From 95e9a35983551bc397e04a79edd2af329f5d7802 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 10:19:56 -0400 Subject: [PATCH 0180/4053] FilePatchItem coverage --- lib/items/file-patch-item.js | 1 + test/items/file-patch-item.test.js | 55 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/lib/items/file-patch-item.js b/lib/items/file-patch-item.js index db087e81f0..c8ed878ce2 100644 --- a/lib/items/file-patch-item.js +++ b/lib/items/file-patch-item.js @@ -59,6 +59,7 @@ export default class FilePatchItem extends React.Component { } destroy() { + /* istanbul ignore else */ if (!this.isDestroyed) { this.emitter.emit('did-destroy'); this.isDestroyed = true; diff --git a/test/items/file-patch-item.test.js b/test/items/file-patch-item.test.js index 84cbe3429b..660ab60b4f 100644 --- a/test/items/file-patch-item.test.js +++ b/test/items/file-patch-item.test.js @@ -102,4 +102,59 @@ describe('FilePatchItem', function() { assert.strictEqual(item.getTitle(), 'Staged Changes: a.txt'); }); }); + + it('terminates pending state', async function() { + const wrapper = mount(buildPaneApp()); + + const item = await open(wrapper); + const callback = sinon.spy(); + const sub = item.onDidTerminatePendingState(callback); + + assert.strictEqual(callback.callCount, 0); + item.terminatePendingState(); + assert.strictEqual(callback.callCount, 1); + item.terminatePendingState(); + assert.strictEqual(callback.callCount, 1); + + sub.dispose(); + }); + + it('may be destroyed once', async function() { + const wrapper = mount(buildPaneApp()); + + const item = await open(wrapper); + const callback = sinon.spy(); + const sub = item.onDidDestroy(callback); + + assert.strictEqual(callback.callCount, 0); + item.destroy(); + assert.strictEqual(callback.callCount, 1); + + sub.dispose(); + }); + + it('serializes itself as a FilePatchControllerStub', async function() { + const wrapper = mount(buildPaneApp()); + const item0 = await open(wrapper, {relPath: 'a.txt', workingDirectory: '/dir0', stagingStatus: 'unstaged'}); + assert.deepEqual(item0.serialize(), { + deserializer: 'FilePatchControllerStub', + uri: 'atom-github://file-patch/a.txt?workdir=%2Fdir0&stagingStatus=unstaged', + }); + + const item1 = await open(wrapper, {relPath: 'b.txt', workingDirectory: '/dir1', stagingStatus: 'staged'}); + assert.deepEqual(item1.serialize(), { + deserializer: 'FilePatchControllerStub', + uri: 'atom-github://file-patch/b.txt?workdir=%2Fdir1&stagingStatus=staged', + }); + }); + + it('has some item-level accessors', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(wrapper, {relPath: 'a.txt', workingDirectory: '/dir', stagingStatus: 'unstaged'}); + + assert.strictEqual(item.getStagingStatus(), 'unstaged'); + assert.strictEqual(item.getFilePath(), 'a.txt'); + assert.strictEqual(item.getWorkingDirectory(), '/dir'); + assert.isTrue(item.isFilePatchItem()); + }); }); From f301562d7e1725ae1b6484a85f2b0243bd9389b9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 13:15:38 -0400 Subject: [PATCH 0181/4053] FilePatchController coverage --- lib/controllers/file-patch-controller.js | 6 +- .../controllers/file-patch-controller.test.js | 302 +++++++++++++++++- 2 files changed, 302 insertions(+), 6 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 905f6caaea..63c9642df2 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -79,13 +79,13 @@ export default class FilePatchController extends React.Component { return this.props.undoLastDiscard(this.props.relPath, this.props.repository); } - async diveIntoMirrorPatch() { + diveIntoMirrorPatch() { const mirrorStatus = this.withStagingStatus({staged: 'unstaged', unstaged: 'staged'}); const workingDirectory = this.props.repository.getWorkingDirectoryPath(); const uri = FilePatchItem.buildURI(this.props.relPath, workingDirectory, mirrorStatus); this.props.destroy(); - await this.props.workspace.open(uri); + return this.props.workspace.open(uri); } async openFile(positions) { @@ -98,6 +98,7 @@ export default class FilePatchController extends React.Component { } editor.scrollToBufferPosition(positions[positions.length - 1], {center: true}); } + return editor; } toggleFile() { @@ -167,6 +168,7 @@ export default class FilePatchController extends React.Component { withStagingStatus(callbacks) { const callback = callbacks[this.props.stagingStatus]; + /* istanbul ignore if */ if (!callback) { throw new Error(`Unknown staging status: ${this.props.stagingStatus}`); } diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index 0e5b358c45..c08c57bc3b 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -1,7 +1,7 @@ import path from 'path'; -import fs from 'fs'; +import fs from 'fs-extra'; import React from 'react'; -import {mount} from 'enzyme'; +import {shallow} from 'enzyme'; import FilePatchController from '../../lib/controllers/file-patch-controller'; import {cloneRepository, buildRepository} from '../helpers'; @@ -16,7 +16,7 @@ describe('FilePatchController', function() { repository = await buildRepository(workdirPath); // a.txt: unstaged changes - await fs.writeFile(path.join(workdirPath, 'a.txt'), 'changed\n'); + await fs.writeFile(path.join(workdirPath, 'a.txt'), '00\n01\n02\n03\n04\n05\n06'); filePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); }); @@ -46,8 +46,302 @@ describe('FilePatchController', function() { it('passes extra props to the FilePatchView', function() { const extra = Symbol('extra'); - const wrapper = mount(buildApp({extra})); + const wrapper = shallow(buildApp({extra})); assert.strictEqual(wrapper.find('FilePatchView').prop('extra'), extra); }); + + it('calls undoLastDiscard through with set arguments', function() { + const undoLastDiscard = sinon.spy(); + const wrapper = shallow(buildApp({relPath: 'b.txt', undoLastDiscard})); + wrapper.find('FilePatchView').prop('undoLastDiscard')(); + + assert.isTrue(undoLastDiscard.calledWith('b.txt', repository)); + }); + + describe('diveIntoMirrorPatch()', function() { + it('destroys the current pane and opens the staged changes', async function() { + const destroy = sinon.spy(); + sinon.stub(atomEnv.workspace, 'open').resolves(); + const wrapper = shallow(buildApp({relPath: 'c.txt', stagingStatus: 'unstaged', destroy})); + + await wrapper.find('FilePatchView').prop('diveIntoMirrorPatch')(); + + assert.isTrue(destroy.called); + assert.isTrue(atomEnv.workspace.open.calledWith( + 'atom-github://file-patch/c.txt' + + `?workdir=${encodeURIComponent(repository.getWorkingDirectoryPath())}&stagingStatus=staged`, + )); + }); + + it('destroys the current pane and opens the unstaged changes', async function() { + const destroy = sinon.spy(); + sinon.stub(atomEnv.workspace, 'open').resolves(); + const wrapper = shallow(buildApp({relPath: 'd.txt', stagingStatus: 'staged', destroy})); + + await wrapper.find('FilePatchView').prop('diveIntoMirrorPatch')(); + + assert.isTrue(destroy.called); + assert.isTrue(atomEnv.workspace.open.calledWith( + 'atom-github://file-patch/d.txt' + + `?workdir=${encodeURIComponent(repository.getWorkingDirectoryPath())}&stagingStatus=unstaged`, + )); + }); + }); + + describe('openFile()', function() { + it('opens an editor on the current file', async function() { + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + const editor = await wrapper.find('FilePatchView').prop('openFile')([]); + + assert.strictEqual(editor.getPath(), path.join(repository.getWorkingDirectoryPath(), 'a.txt')); + }); + + it('sets the cursor to a single position', async function() { + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + const editor = await wrapper.find('FilePatchView').prop('openFile')([[1, 1]]); + + assert.deepEqual(editor.getCursorBufferPositions().map(p => p.serialize()), [[1, 1]]); + }); + + it('adds cursors at a set of positions', async function() { + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + const editor = await wrapper.find('FilePatchView').prop('openFile')([[1, 1], [3, 1], [5, 0]]); + + assert.deepEqual(editor.getCursorBufferPositions().map(p => p.serialize()), [[1, 1], [3, 1], [5, 0]]); + }); + }); + + describe('toggleFile()', function() { + it('stages the current file if unstaged', async function() { + sinon.spy(repository, 'stageFiles'); + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + + await wrapper.find('FilePatchView').prop('toggleFile')(); + + assert.isTrue(repository.stageFiles.calledWith(['a.txt'])); + }); + + it('unstages the current file if staged', async function() { + sinon.spy(repository, 'unstageFiles'); + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'staged'})); + + await wrapper.find('FilePatchView').prop('toggleFile')(); + + assert.isTrue(repository.unstageFiles.calledWith(['a.txt'])); + }); + + it('is a no-op if a staging operation is already in progress', async function() { + sinon.stub(repository, 'stageFiles').resolves('staged'); + sinon.stub(repository, 'unstageFiles').resolves('unstaged'); + + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + assert.strictEqual(await wrapper.find('FilePatchView').prop('toggleFile')(), 'staged'); + + wrapper.setProps({stagingStatus: 'staged'}); + assert.isNull(await wrapper.find('FilePatchView').prop('toggleFile')()); + + const promise = wrapper.instance().patchChangePromise; + wrapper.setProps({filePatch: filePatch.clone()}); + await promise; + + assert.strictEqual(await wrapper.find('FilePatchView').prop('toggleFile')(), 'unstaged'); + }); + }); + + describe('selected row tracking', function() { + it('captures the selected row set', function() { + const wrapper = shallow(buildApp()); + assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); + + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); + assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); + }); + + it('does not re-render if the row set is unchanged', function() { + const wrapper = shallow(buildApp()); + assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); + + assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); + sinon.spy(wrapper.instance(), 'render'); + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([2, 1])); + assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); + assert.isFalse(wrapper.instance().render.called); + }); + }); + + describe('toggleRows()', function() { + it('is a no-op with no selected rows', async function() { + const wrapper = shallow(buildApp()); + assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); + + sinon.spy(repository, 'applyPatchToIndex'); + + await wrapper.find('FilePatchView').prop('toggleRows')(); + assert.isFalse(repository.applyPatchToIndex.called); + }); + + it('applies a stage patch to the index', async function() { + const wrapper = shallow(buildApp()); + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1])); + + sinon.spy(filePatch, 'getStagePatchForLines'); + sinon.spy(repository, 'applyPatchToIndex'); + + await wrapper.find('FilePatchView').prop('toggleRows')(); + + assert.isTrue(filePatch.getStagePatchForLines.called); + assert.isTrue(repository.applyPatchToIndex.calledWith(filePatch.getStagePatchForLines.returnValues[0])); + }); + + it('applies an unstage patch to the index', async function() { + await repository.stageFiles(['a.txt']); + const otherPatch = await repository.getFilePatchForPath('a.txt', {staged: true}); + const wrapper = shallow(buildApp({filePatch: otherPatch, stagingStatus: 'staged'})); + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([2])); + + sinon.spy(otherPatch, 'getUnstagePatchForLines'); + sinon.spy(repository, 'applyPatchToIndex'); + + await wrapper.find('FilePatchView').prop('toggleRows')(); + + assert.isTrue(otherPatch.getUnstagePatchForLines.called); + assert.isTrue(repository.applyPatchToIndex.calledWith(otherPatch.getUnstagePatchForLines.returnValues[0])); + }); + }); + + describe('toggleModeChange()', function() { + it("it stages an unstaged file's new mode", async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); + await fs.chmod(p, 0o755); + repository.refresh(); + const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); + + const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'unstaged'})); + + sinon.spy(repository, 'stageFileModeChange'); + await wrapper.find('FilePatchView').prop('toggleModeChange')(); + + assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100755')); + }); + + it("it stages a staged file's old mode", async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); + await fs.chmod(p, 0o755); + await repository.stageFiles(['a.txt']); + repository.refresh(); + const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: true}); + + const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'staged'})); + + sinon.spy(repository, 'stageFileModeChange'); + await wrapper.find('FilePatchView').prop('toggleModeChange')(); + + assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100644')); + }); + }); + + describe('toggleSymlinkChange', function() { + it('handles an addition and typechange with a special repository method', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.symlink(dest, p); + + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); + + await fs.unlink(p); + await fs.writeFile(p, 'fdsa\n', 'utf8'); + + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); + + sinon.spy(repository, 'stageFileSymlinkChange'); + + await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); + + assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); + }); + + it('stages non-addition typechanges normally', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.symlink(dest, p); + + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); + + await fs.unlink(p); + + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); + + sinon.spy(repository, 'stageFiles'); + + await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); + + assert.isTrue(repository.stageFiles.calledWith(['waslink.txt'])); + }); + + it('handles a deletion and typechange with a special repository method', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.writeFile(p, 'fdsa\n', 'utf8'); + + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); + + await fs.unlink(p); + await fs.symlink(dest, p); + await repository.stageFiles(['waslink.txt']); + + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); + + sinon.spy(repository, 'stageFileSymlinkChange'); + + await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); + + assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); + }); + + it('unstages non-deletion typechanges normally', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.symlink(dest, p); + + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); + + await fs.unlink(p); + + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); + + sinon.spy(repository, 'unstageFiles'); + + await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); + + assert.isTrue(repository.unstageFiles.calledWith(['waslink.txt'])); + }); + }); + + it('calls discardLines with selected rows', function() { + const discardLines = sinon.spy(); + const wrapper = shallow(buildApp({discardLines})); + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); + + wrapper.find('FilePatchView').prop('discardRows')(); + + assert.isTrue(discardLines.calledWith(filePatch, new Set([1, 2]), repository)); + }); }); From 646ebf4eb51a8d38f0869e4d3638835380c5f58d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 14:08:32 -0400 Subject: [PATCH 0182/4053] Remove unused hunk navigation commands (for now) --- lib/views/file-patch-view.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 7832b4b917..a2528b2288 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -136,8 +136,6 @@ export default class FilePatchView extends React.Component { return ( - - ); From 66bae4938b082930c27cba424e55b9980e34fb30 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 5 Sep 2018 14:08:59 -0400 Subject: [PATCH 0183/4053] Use the real FilePatch builder --- test/views/file-patch-view.test.js | 229 ++++++++++++----------------- 1 file changed, 93 insertions(+), 136 deletions(-) diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index dcfb81a89e..9f18096e1f 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -1,16 +1,11 @@ -import path from 'path'; -import fs from 'fs-extra'; import React from 'react'; import {shallow, mount} from 'enzyme'; import {cloneRepository, buildRepository} from '../helpers'; import FilePatchView from '../../lib/views/file-patch-view'; -import FilePatchSelection from '../../lib/models/file-patch-selection'; -import {nullFilePatch} from '../../lib/models/patch/file-patch'; +import {buildFilePatch} from '../../lib/models/patch'; import {nullFile} from '../../lib/models/patch/file'; -import Hunk from '../../lib/models/patch/hunk'; -import {Addition, Deletion, NoNewline} from '../../lib/models/patch/region'; -import IndexedRowRange from '../../lib/models/indexed-row-range'; +import {nullFilePatch} from '../../lib/models/patch/file-patch'; describe('FilePatchView', function() { let atomEnv, repository, filePatch; @@ -22,8 +17,20 @@ describe('FilePatchView', function() { repository = await buildRepository(workdirPath); // a.txt: unstaged changes - await fs.writeFile(path.join(workdirPath, 'a.txt'), 'zero\none\ntwo\nthree\nfour\n'); - filePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); + filePatch = buildFilePatch([{ + oldPath: 'path.txt', + oldMode: '100644', + newPath: 'path.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 5, oldLineCount: 3, newStartLine: 5, newLineCount: 3, + heading: 'heading', + lines: [' 0000', '+0001', '-0002', ' 0003'], + }, + ], + }]); }); afterEach(function() { @@ -32,35 +39,26 @@ describe('FilePatchView', function() { function buildApp(overrideProps = {}) { const props = { - relPath: 'a.txt', + relPath: 'path.txt', stagingStatus: 'unstaged', isPartiallyStaged: false, filePatch, - selection: new FilePatchSelection(filePatch.getHunks()), + selectedRows: new Set(), repository, commands: atomEnv.commands, tooltips: atomEnv.tooltips, - mouseDownOnHeader: () => {}, - mouseDownOnLineNumber: () => {}, - mouseMoveOnLineNumber: () => {}, - mouseUp: () => {}, selectedRowsChanged: () => {}, diveIntoMirrorPatch: () => {}, openFile: () => {}, toggleFile: () => {}, - selectAndToggleHunk: () => {}, - toggleLines: () => {}, + toggleRows: () => {}, toggleModeChange: () => {}, toggleSymlinkChange: () => {}, undoLastDiscard: () => {}, - discardLines: () => {}, - selectAndDiscardHunk: () => {}, - selectNextHunk: () => {}, - selectPreviousHunk: () => {}, - togglePatchSelectionMode: () => {}, + discardRows: () => {}, ...overrideProps, }; @@ -203,28 +201,27 @@ describe('FilePatchView', function() { }); it('renders a header for each hunk', function() { - const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n'; - const hunks = [ - new Hunk({ - oldStartRow: 1, oldRowCount: 2, newStartRow: 1, newRowCount: 3, - sectionHeading: 'first hunk', - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), - ], - }), - new Hunk({ - oldStartRow: 10, oldRowCount: 3, newStartRow: 11, newRowCount: 2, - sectionHeading: 'second hunk', - rowRange: new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 15, endOffset: 30}), - changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 5, endOffset: 10})), - ], - }), - ]; - const fp = filePatch.clone({ - patch: filePatch.getPatch().clone({hunks, bufferText}), - }); + const fp = buildFilePatch([{ + oldPath: 'path.txt', + oldMode: '100644', + newPath: 'path.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 2, newStartLine: 1, newLineCount: 3, + heading: 'first hunk', + lines: [' 0000', '+0001', ' 0002'], + }, + { + oldStartLine: 10, oldLineCount: 3, newStartLine: 11, newLineCount: 2, + heading: 'second hunk', + lines: [' 0003', '-0004', ' 0005'], + }, + ], + }]); + const hunks = fp.getHunks(); + const wrapper = mount(buildApp({filePatch: fp})); assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[0])); assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[1])); @@ -234,37 +231,28 @@ describe('FilePatchView', function() { let linesPatch; beforeEach(function() { - const bufferText = - '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n' + - '0010\n0011\n0012\n0013\n0014\n0015\n0016\n' + - ' No newline at end of file\n'; - const hunks = [ - new Hunk({ - oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 6, - sectionHeading: 'first hunk', - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [6, 0]], startOffset: 0, endOffset: 35}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 5, endOffset: 15})), - new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [3, 0]], startOffset: 15, endOffset: 20})), - new Addition(new IndexedRowRange({bufferRange: [[4, 0], [5, 0]], startOffset: 20, endOffset: 30})), - ], - }), - new Hunk({ - oldStartRow: 10, oldRowCount: 0, newStartRow: 13, newRowCount: 0, - sectionHeading: 'second hunk', - rowRange: new IndexedRowRange({bufferRange: [[7, 0], [17, 0]], startOffset: 35, endOffset: 112}), - changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[8, 0], [10, 0]], startOffset: 40, endOffset: 55})), - new Addition(new IndexedRowRange({bufferRange: [[12, 0], [14, 0]], startOffset: 60, endOffset: 75})), - new Deletion(new IndexedRowRange({bufferRange: [[15, 0], [15, 0]], startOffset: 75, endOffset: 80})), - new NoNewline(new IndexedRowRange({bufferRange: [[17, 0], [17, 0]], startOffset: 85, endOffset: 112})), - ], - }), - ]; - - linesPatch = filePatch.clone({ - patch: filePatch.getPatch().clone({hunks, bufferText}), - }); + linesPatch = buildFilePatch([{ + oldPath: 'file.txt', + oldMode: '100644', + newPath: 'file.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 6, + heading: 'first hunk', + lines: [' 0000', '+0001', '+0002', '-0003', '+0004', '+0005', ' 0006'], + }, + { + oldStartLine: 10, oldLineCount: 0, newStartLine: 13, newLineCount: 0, + heading: 'second hunk', + lines: [ + ' 0007', '-0008', '-0009', '-0010', ' 0011', '+0012', '+0013', '+0014', '-0015', ' 0016', + '\\ No newline at end of file', + ], + }, + ], + }]); }); it('decorates added lines', function() { @@ -277,9 +265,9 @@ describe('FilePatchView', function() { const layer = wrapper.find('MarkerLayer').filterWhere(each => each.find(decorationSelector).exists()); const markers = layer.find('Marker').map(marker => marker.prop('bufferRange').serialize()); assert.deepEqual(markers, [ - [[1, 0], [2, 0]], - [[4, 0], [5, 0]], - [[12, 0], [14, 0]], + [[1, 0], [2, 3]], + [[4, 0], [5, 3]], + [[12, 0], [14, 3]], ]); }); @@ -293,9 +281,9 @@ describe('FilePatchView', function() { const layer = wrapper.find('MarkerLayer').filterWhere(each => each.find(decorationSelector).exists()); const markers = layer.find('Marker').map(marker => marker.prop('bufferRange').serialize()); assert.deepEqual(markers, [ - [[3, 0], [3, 0]], - [[8, 0], [10, 0]], - [[15, 0], [15, 0]], + [[3, 0], [3, 3]], + [[8, 0], [10, 3]], + [[15, 0], [15, 3]], ]); }); @@ -309,7 +297,7 @@ describe('FilePatchView', function() { const layer = wrapper.find('MarkerLayer').filterWhere(each => each.find(decorationSelector).exists()); const markers = layer.find('Marker').map(marker => marker.prop('bufferRange').serialize()); assert.deepEqual(markers, [ - [[17, 0], [17, 0]], + [[17, 0], [17, 25]], ]); }); }); @@ -319,7 +307,7 @@ describe('FilePatchView', function() { const wrapper = mount(buildApp({selectedRowsChanged})); const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); - assert.isFalse(selectedRowsChanged.called); + selectedRowsChanged.resetHistory(); editor.addSelectionForBufferRange([[3, 1], [4, 0]]); @@ -340,69 +328,38 @@ describe('FilePatchView', function() { }); describe('registers Atom commands', function() { - it('toggles the patch selection mode', function() { - const togglePatchSelectionMode = sinon.spy(); - const wrapper = mount(buildApp({togglePatchSelectionMode})); - - atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:toggle-patch-selection-mode'); - - assert.isTrue(togglePatchSelectionMode.called); - }); - it('toggles the current selection', function() { - const toggleLines = sinon.spy(); - const wrapper = mount(buildApp({toggleLines})); + const toggleRows = sinon.spy(); + const wrapper = mount(buildApp({toggleRows})); atomEnv.commands.dispatch(wrapper.getDOMNode(), 'core:confirm'); - assert.isTrue(toggleLines.called); - }); - - it('selects the next hunk', function() { - const selectNextHunk = sinon.spy(); - const wrapper = mount(buildApp({selectNextHunk})); - - atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:select-next-hunk'); - - assert.isTrue(selectNextHunk.called); - }); - - it('selects the previous hunk', function() { - const selectPreviousHunk = sinon.spy(); - const wrapper = mount(buildApp({selectPreviousHunk})); - - atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:select-previous-hunk'); - - assert.isTrue(selectPreviousHunk.called); + assert.isTrue(toggleRows.called); }); describe('opening the file', function() { let fp; beforeEach(function() { - const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n'; - const hunks = [ - new Hunk({ - oldStartRow: 2, oldRowCount: 2, newStartRow: 2, newRowCount: 3, - sectionHeading: 'first hunk', - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 15}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 5, endOffset: 10})), - ], - }), - new Hunk({ - oldStartRow: 10, oldRowCount: 6, newStartRow: 11, newRowCount: 3, - sectionHeading: 'second hunk', - rowRange: new IndexedRowRange({bufferRange: [[3, 0], [8, 0]], startOffset: 15, endOffset: 45}), - changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 20, endOffset: 25})), - new Deletion(new IndexedRowRange({bufferRange: [[6, 0], [7, 0]], startOffset: 30, endOffset: 40})), - ], - }), - ]; - fp = filePatch.clone({ - patch: filePatch.getPatch().clone({hunks, bufferText}), - }); + fp = buildFilePatch([{ + oldPath: 'path.txt', + oldMode: '100644', + newPath: 'path.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 2, oldLineCount: 2, newStartLine: 2, newLineCount: 3, + heading: 'first hunk', + lines: [' 0000', '+0001', ' 0002'], + }, + { + oldStartLine: 10, oldLineCount: 6, newStartLine: 11, newLineCount: 3, + heading: 'second hunk', + lines: [' 0003', '-0004', ' 0005', '-0006', '-0007', ' 0008'], + }, + ], + }]); }); it('opens the file at the current unchanged row', function() { From 34d222d1cd702da5ef88498614b5b49a86b8194e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 13:22:52 -0400 Subject: [PATCH 0184/4053] Remove the unused hunk selection methods (for now) --- lib/views/file-patch-view.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index a2528b2288..f2932ec6ce 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -53,7 +53,7 @@ export default class FilePatchView extends React.Component { this, 'didMouseDownOnHeader', 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', 'didOpenFile', 'didAddSelection', 'didChangeSelectionRange', 'didDestroySelection', - 'oldLineNumberLabel', 'newLineNumberLabel', 'selectNextHunk', 'selectPreviousHunk', + 'oldLineNumberLabel', 'newLineNumberLabel', ); this.mouseSelectionInProgress = false; @@ -679,14 +679,6 @@ export default class FilePatchView extends React.Component { return this.pad(newRow); } - selectNextHunk() { - // - } - - selectPreviousHunk() { - // - } - getHunkAt(bufferRow) { const hunkFromMarker = this.hunkMarkerLayerHolder.map(layer => { const markers = layer.findMarkers({intersectsRow: bufferRow}); From ec4ab28628234743ec696e4521591d1a367a9add Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 13:23:08 -0400 Subject: [PATCH 0185/4053] :hocho: that case we could never actually get to --- lib/views/file-patch-view.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index f2932ec6ce..9c61f44023 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -292,7 +292,7 @@ export default class FilePatchView extends React.Component { ); title = 'Symlink deleted'; - } else if (!oldSymlink && newSymlink) { + } else { detail = ( Symlink @@ -303,8 +303,6 @@ export default class FilePatchView extends React.Component { ); title = 'Symlink created'; - } else { - return null; } const attrs = this.props.stagingStatus === 'unstaged' From e0bd870459902378957847806eee556670a8acbd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 13:23:27 -0400 Subject: [PATCH 0186/4053] Turns out Range.fromObject() normalizes start and end already --- lib/views/file-patch-view.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 9c61f44023..436de7b031 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -484,8 +484,7 @@ export default class FilePatchView extends React.Component { // Normalize the target selection range const converted = Range.fromObject(rangeLike); - const flipped = converted.start.isLessThanOrEqual(converted.end) ? converted : converted.negate(); - const range = this.refEditor.map(editor => editor.clipBufferRange(flipped)).getOr(flipped); + const range = this.refEditor.map(editor => editor.clipBufferRange(converted)).getOr(converted); if (event.metaKey || (event.ctrlKey && isWindows)) { this.refEditor.map(editor => { From 0fb0a5da10a57b844b3c4bc51f1c4808ca5fba5b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 13:23:50 -0400 Subject: [PATCH 0187/4053] Handle eliminating the last row of a selection with ctrl-click --- lib/views/file-patch-view.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 436de7b031..7a95149eec 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -489,6 +489,7 @@ export default class FilePatchView extends React.Component { if (event.metaKey || (event.ctrlKey && isWindows)) { this.refEditor.map(editor => { let intersects = false; + let without = null; for (const selection of editor.getSelections()) { if (selection.intersectsBufferRange(range)) { @@ -526,10 +527,21 @@ export default class FilePatchView extends React.Component { for (const newRange of newRanges.slice(1)) { editor.addSelectionForBufferRange(newRange, {reversed: selection.isReversed()}); } + } else { + without = selection; } } } + if (without !== null) { + const replacementRanges = editor.getSelections() + .filter(each => each !== without) + .map(each => each.getBufferRange()); + if (replacementRanges.length > 0) { + editor.setSelectedBufferRanges(replacementRanges); + } + } + if (!intersects) { // Add this range as a new, distinct selection. editor.addSelectionForBufferRange(range); From 46897eecba8a8cab4ba45381efea5341aee29adc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 13:24:18 -0400 Subject: [PATCH 0188/4053] lastSelectionRange is always start-before-end --- lib/views/file-patch-view.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 7a95149eec..e2466f0568 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -556,10 +556,9 @@ export default class FilePatchView extends React.Component { const lastSelectionRange = lastSelection.getBufferRange(); // You are now entering the wall of ternery operators. This is your last exit before the tollbooth - const cursorHead = lastSelection.isReversed() ? lastSelectionRange.end : lastSelectionRange.start; - const isBefore = range.start.isLessThan(cursorHead); + const isBefore = range.start.isLessThan(lastSelectionRange.start); const farEdge = isBefore ? range.start : range.end; - const newRange = isBefore ? [farEdge, cursorHead] : [cursorHead, farEdge]; + const newRange = isBefore ? [farEdge, lastSelectionRange.end] : [lastSelectionRange.start, farEdge]; lastSelection.setBufferRange(newRange, {reversed: isBefore}); return null; From 42c8c6790fefd9f8f0d57b0ba6b85b5a80e50a84 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 13:24:35 -0400 Subject: [PATCH 0189/4053] Pad newLine labels without a hunk --- lib/views/file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index e2466f0568..29f72cc99b 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -677,7 +677,7 @@ export default class FilePatchView extends React.Component { newLineNumberLabel({bufferRow, softWrapped}) { const hunk = this.getHunkAt(bufferRow); if (hunk === undefined) { - return ''; + return this.pad(''); } const newRow = hunk.getNewRowAt(bufferRow); From 8b47f45c598589c595c39e5dc504aaeb583bd458 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 13:24:59 -0400 Subject: [PATCH 0190/4053] Coverage-ignore branches that are platform-specific --- lib/views/file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 29f72cc99b..b5169d897e 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -486,7 +486,7 @@ export default class FilePatchView extends React.Component { const converted = Range.fromObject(rangeLike); const range = this.refEditor.map(editor => editor.clipBufferRange(converted)).getOr(converted); - if (event.metaKey || (event.ctrlKey && isWindows)) { + if (event.metaKey || /* istanbul ignore next */ (event.ctrlKey && isWindows)) { this.refEditor.map(editor => { let intersects = false; let without = null; From f5cb601610fde2eb06becfb02070843583e12ffd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 13:25:23 -0400 Subject: [PATCH 0191/4053] Coverate for FilePatchView --- test/views/file-patch-view.test.js | 418 ++++++++++++++++++++++++++--- 1 file changed, 377 insertions(+), 41 deletions(-) diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 9f18096e1f..06d07d35a0 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -16,7 +16,7 @@ describe('FilePatchView', function() { const workdirPath = await cloneRepository(); repository = await buildRepository(workdirPath); - // a.txt: unstaged changes + // path.txt: unstaged changes filePatch = buildFilePatch([{ oldPath: 'path.txt', oldMode: '100644', @@ -25,9 +25,14 @@ describe('FilePatchView', function() { status: 'modified', hunks: [ { - oldStartLine: 5, oldLineCount: 3, newStartLine: 5, newLineCount: 3, - heading: 'heading', - lines: [' 0000', '+0001', '-0002', ' 0003'], + oldStartLine: 4, oldLineCount: 3, newStartLine: 4, newLineCount: 4, + heading: 'zero', + lines: [' 0000', '+0001', '+0002', '-0003', ' 0004'], + }, + { + oldStartLine: 8, oldLineCount: 3, newStartLine: 9, newLineCount: 3, + heading: 'one', + lines: [' 0005', '+0006', '-0007', ' 0008'], }, ], }]); @@ -78,6 +83,74 @@ describe('FilePatchView', function() { assert.strictEqual(editor.instance().getModel().getText(), filePatch.getBufferText()); }); + it('preserves the selection index when a new file patch arrives', function() { + let lastSelectedRows = new Set([2]); + const selectedRowsChanged = sinon.stub().callsFake(rows => { + lastSelectedRows = rows; + }); + const wrapper = mount(buildApp({selectedRows: new Set([2]), selectedRowsChanged})); + + const nextPatch = buildFilePatch([{ + oldPath: 'path.txt', + oldMode: '100644', + newPath: 'path.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 5, oldLineCount: 4, newStartLine: 5, newLineCount: 3, + heading: 'heading', + lines: [' 0000', '+0001', ' 0002', '-0003', ' 0004'], + }, + ], + }]); + + wrapper.setProps({filePatch: nextPatch}); + assert.sameMembers(Array.from(lastSelectedRows), [3]); + + selectedRowsChanged.resetHistory(); + wrapper.setProps({isPartiallyStaged: true}); + assert.isFalse(selectedRowsChanged.called); + }); + + it('unregisters the mouseup handler on unmount', function() { + sinon.spy(window, 'addEventListener'); + sinon.spy(window, 'removeEventListener'); + + const wrapper = shallow(buildApp()); + assert.strictEqual(window.addEventListener.callCount, 1); + const addCall = window.addEventListener.getCall(0); + assert.strictEqual(addCall.args[0], 'mouseup'); + const handler = window.addEventListener.getCall(0).args[1]; + + wrapper.unmount(); + + assert.isTrue(window.removeEventListener.calledWith('mouseup', handler)); + }); + + it('locates hunks for a buffer row with or without markers', function() { + const [hunk0, hunk1] = filePatch.getHunks(); + const instance = mount(buildApp()).instance(); + + for (let i = 0; i <= 4; i++) { + assert.strictEqual(instance.getHunkAt(i), hunk0, `buffer row ${i} should retrieve hunk 0 with markers`); + } + for (let j = 5; j <= 8; j++) { + assert.strictEqual(instance.getHunkAt(j), hunk1, `buffer row ${j} should retrieve hunk 1 with markers`); + } + assert.isUndefined(instance.getHunkAt(9)); + + instance.hunkMarkerLayerHolder.map(layer => layer.destroy()); + + for (let i = 0; i <= 4; i++) { + assert.strictEqual(instance.getHunkAt(i), hunk0, `buffer row ${i} should retrieve hunk 0 without markers`); + } + for (let j = 5; j <= 8; j++) { + assert.strictEqual(instance.getHunkAt(j), hunk1, `buffer row ${j} should retrieve hunk 1 without markers`); + } + assert.isUndefined(instance.getHunkAt(9)); + }); + describe('executable mode changes', function() { it('does not render if the mode has not changed', function() { const fp = filePatch.clone({ @@ -200,31 +273,292 @@ describe('FilePatchView', function() { }); }); - it('renders a header for each hunk', function() { - const fp = buildFilePatch([{ - oldPath: 'path.txt', - oldMode: '100644', - newPath: 'path.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 1, oldLineCount: 2, newStartLine: 1, newLineCount: 3, - heading: 'first hunk', - lines: [' 0000', '+0001', ' 0002'], - }, - { - oldStartLine: 10, oldLineCount: 3, newStartLine: 11, newLineCount: 2, - heading: 'second hunk', - lines: [' 0003', '-0004', ' 0005'], - }, - ], - }]); - const hunks = fp.getHunks(); + describe('hunk headers', function() { + it('renders one for each hunk', function() { + const fp = buildFilePatch([{ + oldPath: 'path.txt', + oldMode: '100644', + newPath: 'path.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 2, newStartLine: 1, newLineCount: 3, + heading: 'first hunk', + lines: [' 0000', '+0001', ' 0002'], + }, + { + oldStartLine: 10, oldLineCount: 3, newStartLine: 11, newLineCount: 2, + heading: 'second hunk', + lines: [' 0003', '-0004', ' 0005'], + }, + ], + }]); + const hunks = fp.getHunks(); + + const wrapper = mount(buildApp({filePatch: fp})); + assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[0])); + assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[1])); + }); + + it('pluralizes the toggle and discard button labels', function() { + const wrapper = shallow(buildApp({selectedRows: new Set([2])})); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Selected Change'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('discardSelectionLabel'), 'Discard Selected Change'); + + wrapper.setProps({selectedRows: new Set([1, 2, 3])}); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Selected Changes'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('discardSelectionLabel'), 'Discard Selected Changes'); + }); + + it('uses the appropriate staging action verb in hunk header button labels', function() { + const wrapper = shallow(buildApp({selectedRows: new Set([2]), stagingStatus: 'unstaged'})); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Selected Change'); - const wrapper = mount(buildApp({filePatch: fp})); - assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[0])); - assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[1])); + wrapper.setProps({stagingStatus: 'staged'}); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Unstage Selected Change'); + }); + + it('handles mousedown as a selection event', function() { + const fp = buildFilePatch([{ + oldPath: 'path.txt', + oldMode: '100644', + newPath: 'path.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 2, newStartLine: 1, newLineCount: 3, + heading: 'first hunk', + lines: [' 0000', '+0001', ' 0002'], + }, + { + oldStartLine: 10, oldLineCount: 3, newStartLine: 11, newLineCount: 2, + heading: 'second hunk', + lines: [' 0003', '-0004', ' 0005'], + }, + ], + }]); + + const selectedRowsChanged = sinon.spy(); + const wrapper = mount(buildApp({filePatch: fp, selectedRowsChanged})); + + wrapper.find('HunkHeaderView').at(1).prop('mouseDown')({button: 0}, fp.getHunks()[1]); + + assert.isTrue(selectedRowsChanged.calledWith(new Set([4]))); + }); + }); + + describe('custom gutters', function() { + let wrapper, instance, editor; + + beforeEach(function() { + wrapper = mount(buildApp()); + instance = wrapper.instance(); + editor = wrapper.find('AtomTextEditor').getDOMNode().getModel(); + }); + + it('computes the old line number for a buffer row', function() { + assert.strictEqual(instance.oldLineNumberLabel({bufferRow: 5, softWrapped: false}), '\u00a08'); + assert.strictEqual(instance.oldLineNumberLabel({bufferRow: 6, softWrapped: false}), '\u00a0\u00a0'); + assert.strictEqual(instance.oldLineNumberLabel({bufferRow: 6, softWrapped: true}), '\u00a0\u00a0'); + assert.strictEqual(instance.oldLineNumberLabel({bufferRow: 7, softWrapped: false}), '\u00a09'); + assert.strictEqual(instance.oldLineNumberLabel({bufferRow: 8, softWrapped: false}), '10'); + assert.strictEqual(instance.oldLineNumberLabel({bufferRow: 8, softWrapped: true}), '\u00a0•'); + + assert.strictEqual(instance.oldLineNumberLabel({bufferRow: 999, softWrapped: false}), '\u00a0\u00a0'); + }); + + it('computes the new line number for a buffer row', function() { + assert.strictEqual(instance.newLineNumberLabel({bufferRow: 5, softWrapped: false}), '\u00a09'); + assert.strictEqual(instance.newLineNumberLabel({bufferRow: 6, softWrapped: false}), '10'); + assert.strictEqual(instance.newLineNumberLabel({bufferRow: 6, softWrapped: true}), '\u00a0•'); + assert.strictEqual(instance.newLineNumberLabel({bufferRow: 7, softWrapped: false}), '\u00a0\u00a0'); + assert.strictEqual(instance.newLineNumberLabel({bufferRow: 7, softWrapped: true}), '\u00a0\u00a0'); + assert.strictEqual(instance.newLineNumberLabel({bufferRow: 8, softWrapped: false}), '11'); + + assert.strictEqual(instance.newLineNumberLabel({bufferRow: 999, softWrapped: false}), '\u00a0\u00a0'); + }); + + it('selects a single line on click', function() { + instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [2, 4]], + ]); + }); + + it('ignores right clicks', function() { + instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {button: 1}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[1, 0], [1, 4]], + ]); + }); + + if (process.platform !== 'win32') { + it('ignores ctrl-clicks on non-Windows platforms', function() { + instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {button: 0, ctrlKey: true}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[1, 0], [1, 4]], + ]); + }); + } + + it('selects a range of lines on click and drag', function() { + instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [2, 4]], + ]); + + instance.didMouseMoveOnLineNumber({bufferRow: 2, domEvent: {button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [2, 4]], + ]); + + instance.didMouseMoveOnLineNumber({bufferRow: 3, domEvent: {button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [3, 4]], + ]); + + instance.didMouseMoveOnLineNumber({bufferRow: 3, domEvent: {button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [3, 4]], + ]); + + instance.didMouseMoveOnLineNumber({bufferRow: 4, domEvent: {button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [4, 4]], + ]); + + instance.didMouseUp(); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [4, 4]], + ]); + + instance.didMouseMoveOnLineNumber({bufferRow: 5, domEvent: {button: 0}}); + // Unchanged after mouse up + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [4, 4]], + ]); + }); + + describe('shift-click', function() { + it('selects a range of lines', function() { + instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [2, 4]], + ]); + instance.didMouseUp(); + + instance.didMouseDownOnLineNumber({bufferRow: 4, domEvent: {shiftKey: true, button: 0}}); + instance.didMouseUp(); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [4, 4]], + ]); + }); + + it("extends to the range's beginning when the selection is reversed", function() { + editor.setSelectedBufferRange([[4, 4], [2, 0]], {reversed: true}); + + instance.didMouseDownOnLineNumber({bufferRow: 6, domEvent: {shiftKey: true, button: 0}}); + assert.isFalse(editor.getLastSelection().isReversed()); + assert.deepEqual(editor.getLastSelection().getBufferRange().serialize(), [[2, 0], [6, 4]]); + }); + + it('reverses the selection if the extension line is before the existing selection', function() { + editor.setSelectedBufferRange([[3, 0], [4, 4]]); + + instance.didMouseDownOnLineNumber({bufferRow: 1, domEvent: {shiftKey: true, button: 0}}); + assert.isTrue(editor.getLastSelection().isReversed()); + assert.deepEqual(editor.getLastSelection().getBufferRange().serialize(), [[1, 0], [4, 4]]); + }); + }); + + describe('ctrl- or meta-click', function() { + beforeEach(function() { + // Select an initial row range. + instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {button: 0}}); + instance.didMouseDownOnLineNumber({bufferRow: 5, domEvent: {shiftKey: true, button: 0}}); + instance.didMouseUp(); + // [[2, 0], [5, 4]] + }); + + it('deselects a line at the beginning of an existing selection', function() { + instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {metaKey: true, button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[3, 0], [5, 4]], + ]); + }); + + it('deselects a line within an existing selection', function() { + instance.didMouseDownOnLineNumber({bufferRow: 3, domEvent: {metaKey: true, button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [2, 4]], + [[4, 0], [5, 4]], + ]); + }); + + it('deselects a line at the end of an existing selection', function() { + instance.didMouseDownOnLineNumber({bufferRow: 5, domEvent: {metaKey: true, button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [4, 4]], + ]); + }); + + it('selects a line outside of an existing selection', function() { + instance.didMouseDownOnLineNumber({bufferRow: 8, domEvent: {metaKey: true, button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [5, 4]], + [[8, 0], [8, 4]], + ]); + }); + + it('deselects the only line within an existing selection', function() { + instance.didMouseDownOnLineNumber({bufferRow: 7, domEvent: {metaKey: true, button: 0}}); + instance.didMouseUp(); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [5, 4]], + [[7, 0], [7, 4]], + ]); + + instance.didMouseDownOnLineNumber({bufferRow: 7, domEvent: {metaKey: true, button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [5, 4]], + ]); + }); + + it('cannot deselect the only selection', function() { + instance.didMouseDownOnLineNumber({bufferRow: 7, domEvent: {button: 0}}); + instance.didMouseUp(); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[7, 0], [7, 4]], + ]); + + instance.didMouseDownOnLineNumber({bufferRow: 7, domEvent: {metaKey: true, button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[7, 0], [7, 4]], + ]); + }); + + it('bonus points: understands ranges that do not cleanly align with editor rows', function() { + instance.handleSelectionEvent({metaKey: true, button: 0}, [[3, 1], [5, 2]]); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[2, 0], [3, 1]], + [[5, 2], [5, 4]], + ]); + }); + }); + + it('does nothing on a click without a buffer row', function() { + instance.didMouseDownOnLineNumber({bufferRow: NaN, domEvent: {button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[1, 0], [1, 4]], + ]); + + instance.didMouseDownOnLineNumber({bufferRow: undefined, domEvent: {button: 0}}); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[1, 0], [1, 4]], + ]); + }); }); describe('hunk lines', function() { @@ -351,12 +685,14 @@ describe('FilePatchView', function() { { oldStartLine: 2, oldLineCount: 2, newStartLine: 2, newLineCount: 3, heading: 'first hunk', + // 2 3 4 lines: [' 0000', '+0001', ' 0002'], }, { - oldStartLine: 10, oldLineCount: 6, newStartLine: 11, newLineCount: 3, + oldStartLine: 10, oldLineCount: 5, newStartLine: 11, newLineCount: 6, heading: 'second hunk', - lines: [' 0003', '-0004', ' 0005', '-0006', '-0007', ' 0008'], + // 11 12 13 14 15 16 + lines: [' 0003', '+0004', '+0005', '-0006', ' 0007', '+0008', '-0009', ' 0010'], }, ], }]); @@ -367,11 +703,11 @@ describe('FilePatchView', function() { const wrapper = mount(buildApp({filePatch: fp, openFile})); const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); - editor.setCursorBufferPosition([3, 2]); + editor.setCursorBufferPosition([7, 2]); atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); - assert.isTrue(openFile.calledWith([[11, 2]])); + assert.isTrue(openFile.calledWith([[14, 2]])); }); it('opens the file at a current added row', function() { @@ -379,11 +715,11 @@ describe('FilePatchView', function() { const wrapper = mount(buildApp({filePatch: fp, openFile})); const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); - editor.setCursorBufferPosition([1, 3]); + editor.setCursorBufferPosition([8, 3]); atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); - assert.isTrue(openFile.calledWith([[3, 3]])); + assert.isTrue(openFile.calledWith([[15, 3]])); }); it('opens the file at the beginning of the previous added or unchanged row', function() { @@ -391,11 +727,11 @@ describe('FilePatchView', function() { const wrapper = mount(buildApp({filePatch: fp, openFile})); const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); - editor.setCursorBufferPosition([4, 2]); + editor.setCursorBufferPosition([9, 2]); atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); - assert.isTrue(openFile.calledWith([[11, 0]])); + assert.isTrue(openFile.calledWith([[15, 0]])); }); it('preserves multiple cursors', function() { @@ -406,18 +742,18 @@ describe('FilePatchView', function() { editor.setCursorBufferPosition([3, 2]); editor.addCursorAtBufferPosition([4, 2]); editor.addCursorAtBufferPosition([1, 3]); - editor.addCursorAtBufferPosition([6, 2]); - editor.addCursorAtBufferPosition([7, 1]); + editor.addCursorAtBufferPosition([9, 2]); + editor.addCursorAtBufferPosition([9, 3]); - // The cursors at [6, 2] and [7, 1] are both collapsed to a single one on the unchanged line. + // [9, 2] and [9, 3] should be collapsed into a single cursor at [15, 0] atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); assert.isTrue(openFile.calledWith([ [11, 2], - [11, 0], + [12, 2], [3, 3], - [12, 0], + [15, 0], ])); }); }); From f4d569701bb031f893298773896bf71eff07ed67 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 15:30:00 -0400 Subject: [PATCH 0192/4053] Band-aids to get the CommitView tests passing --- lib/atom/atom-text-editor.js | 1 - lib/views/commit-view.js | 43 ++++++++++++++-------------------- test/views/commit-view.test.js | 22 ++++++++++++----- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index e5c2edca5c..2c02030017 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -92,7 +92,6 @@ export default class AtomTextEditor extends React.PureComponent { this.refElement.map(element => { const editor = element.getModel(); editor.setText(this.props.text, {bypassReadOnly: true}); - this.getRefModel().setter(editor); this.subs.add( diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index d6e9e3c28b..dcea46823b 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -80,14 +80,8 @@ export default class CommitView extends React.Component { this.refCoAuthorToggle = new RefHolder(); this.refCoAuthorSelect = new RefHolder(); this.refCoAuthorForm = new RefHolder(); - this.refEditor = new RefHolder(); - this.editor = null; - - this.subscriptions.add( - this.refEditor.observe(e => { - this.editor = e.getModel(); - }), - ); + this.refEditorComponent = new RefHolder(); + this.refEditorModel = new RefHolder(); } proxyKeyCode(keyCode) { @@ -162,7 +156,8 @@ export default class CommitView extends React.Component {
{this.commitButtonText()} - {this.commitIsEnabled() && + disabled={!this.commitIsEnabled(false)}>{this.commitButtonText()} + {this.commitIsEnabled(false) && { + if (editor.getCursorBufferPosition().row === 0) { + return (this.props.maximumCharacterLimit - editor.lineTextForBufferRow(0).length).toString(); } else { return '∞'; } - } else { - return this.props.maximumCharacterLimit || ''; - } + }).getOr(this.props.maximumCharacterLimit || ''); } // We don't want the user to see the UI flicker in the case @@ -469,7 +462,7 @@ export default class CommitView extends React.Component { } commitIsEnabled(amend) { - const messageExists = this.editor && this.editor.getText().length !== 0; + const messageExists = this.props.message.length > 0; return !this.props.isCommitting && (amend || this.props.stagedChangesExist) && !this.props.mergeConflictsExist && @@ -490,7 +483,7 @@ export default class CommitView extends React.Component { } toggleExpandedCommitMessageEditor() { - return this.props.toggleExpandedCommitMessageEditor(this.editor && this.editor.getText()); + return this.props.toggleExpandedCommitMessageEditor(this.props.message); } matchAuthors(authors, filterText, selectedAuthors) { @@ -556,11 +549,11 @@ export default class CommitView extends React.Component { } hasFocusEditor() { - return this.refEditor.map(editor => editor.contains(document.activeElement)).getOr(false); + return this.refEditorComponent.map(editor => editor.contains(document.activeElement)).getOr(false); } rememberFocus(event) { - if (this.refEditor.map(editor => editor.contains(event.target)).getOr(false)) { + if (this.refEditorComponent.map(editor => editor.contains(event.target)).getOr(false)) { return CommitView.focus.EDITOR; } @@ -587,7 +580,7 @@ export default class CommitView extends React.Component { }; if (focus === CommitView.focus.EDITOR) { - if (this.refEditor.map(focusElement).getOr(false)) { + if (this.refEditorComponent.map(focusElement).getOr(false)) { return true; } } @@ -613,7 +606,7 @@ export default class CommitView extends React.Component { fallback = true; } - if (fallback && this.refEditor.map(focusElement).getOr(false)) { + if (fallback && this.refEditorComponent.map(focusElement).getOr(false)) { return true; } diff --git a/test/views/commit-view.test.js b/test/views/commit-view.test.js index fbac9e15ff..f88f15ea53 100644 --- a/test/views/commit-view.test.js +++ b/test/views/commit-view.test.js @@ -137,17 +137,22 @@ describe('CommitView', function() { const wrapper = mount(app); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '72'); + // It takes two renders for the remaining characters field to update based on editor state. + // FIXME: make sure this doesn't regress in the actual component wrapper.setProps({message: 'abcde fghij'}); + wrapper.setProps({}); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '61'); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); wrapper.setProps({message: '\nklmno'}); + wrapper.setProps({}); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '∞'); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); wrapper.setProps({message: 'abcde\npqrst'}); + wrapper.setProps({}); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '∞'); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); @@ -159,16 +164,19 @@ describe('CommitView', function() { assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); wrapper.setProps({stagedChangesExist: true, maximumCharacterLimit: 50}); + wrapper.setProps({}); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '45'); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); - wrapper.setProps({message: 'a'.repeat(41)}).update(); + wrapper.setProps({message: 'a'.repeat(41)}); + wrapper.setProps({}); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '9'); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isTrue(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); wrapper.setProps({message: 'a'.repeat(58)}).update(); + wrapper.setProps({}); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '-8'); assert.isTrue(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); @@ -207,10 +215,12 @@ describe('CommitView', function() { }); it('is disabled when the commit message is empty', function() { - wrapper.setProps({message: ''}).update(); + wrapper.setProps({message: ''}); + wrapper.setProps({}); assert.isTrue(wrapper.find('.github-CommitView-commit').prop('disabled')); - wrapper.setProps({message: 'Not empty'}).update(); + wrapper.setProps({message: 'Not empty'}); + wrapper.setProps({}); assert.isFalse(wrapper.find('.github-CommitView-commit').prop('disabled')); }); @@ -347,7 +357,7 @@ describe('CommitView', function() { assert.isFalse(wrapper.instance().hasFocusEditor()); editorNode.contains.returns(true); - wrapper.instance().refEditor.setter(null); + wrapper.instance().refEditorComponent.setter(null); assert.isFalse(wrapper.instance().hasFocusEditor()); }); @@ -370,7 +380,7 @@ describe('CommitView', function() { assert.isNull(wrapper.instance().rememberFocus({target: document.body})); const holders = [ - 'refEditor', 'refAbortMergeButton', 'refCommitButton', 'refCoAuthorSelect', + 'refEditorComponent', 'refEditorModel', 'refAbortMergeButton', 'refCommitButton', 'refCoAuthorSelect', ].map(ivar => wrapper.instance()[ivar]); for (const holder of holders) { holder.setter(null); @@ -434,7 +444,7 @@ describe('CommitView', function() { // Simulate an unmounted component by clearing out RefHolders manually. const holders = [ - 'refEditor', 'refAbortMergeButton', 'refCommitButton', 'refCoAuthorSelect', + 'refEditorComponent', 'refEditorModel', 'refAbortMergeButton', 'refCommitButton', 'refCoAuthorSelect', ].map(ivar => wrapper.instance()[ivar]); for (const holder of holders) { holder.setter(null); From b43be3a7fd6586a17f041cfdbcb046c9452b3645 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 15:34:04 -0400 Subject: [PATCH 0193/4053] :fire: more unused code --- styles/hunk-view.old.less | 168 -------------------------- test/views/hunk-view.test.pending.js | 170 --------------------------- 2 files changed, 338 deletions(-) delete mode 100644 styles/hunk-view.old.less delete mode 100644 test/views/hunk-view.test.pending.js diff --git a/styles/hunk-view.old.less b/styles/hunk-view.old.less deleted file mode 100644 index 85d5707eb3..0000000000 --- a/styles/hunk-view.old.less +++ /dev/null @@ -1,168 +0,0 @@ -@import "ui-variables"; - -@hunk-fg-color: @text-color-subtle; -@hunk-bg-color: @pane-item-background-color; - -.github-HunkView { - font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace; - border-bottom: 1px solid @pane-item-border-color; - background-color: @hunk-bg-color; - - &-header { - display: flex; - align-items: stretch; - font-size: .9em; - background-color: @panel-heading-background-color; - border-bottom: 1px solid @panel-heading-border-color; - position: sticky; - top: 0; - } - - &-title { - flex: 1; - line-height: 2.4; - padding: 0 @component-padding; - color: @text-color-subtle; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - -webkit-font-smoothing: antialiased; - } - - &-stageButton, - &-discardButton { - line-height: 1; - padding-left: @component-padding; - padding-right: @component-padding; - font-family: @font-family; - border: none; - border-left: 1px solid @panel-heading-border-color; - background-color: transparent; - cursor: default; - &:hover { background-color: @button-background-color-hover; } - &:active { background-color: @panel-heading-border-color; } - } - - // pixel fit the icon - &-discardButton:before { - text-align: left; - width: auto; - } - - &-line { - display: table-row; - line-height: 1.5em; - color: @hunk-fg-color; - &.is-unchanged { - -webkit-font-smoothing: antialiased; - } - } - - &-lineNumber { - display: table-cell; - min-width: 3.5em; // min 4 chars - overflow: hidden; - padding: 0 .5em; - text-align: right; - border-right: 1px solid @base-border-color; - -webkit-font-smoothing: antialiased; - } - - &-plusMinus { - margin-right: 1ch; - color: fade(@text-color, 50%); - vertical-align: top; - } - - &-lineContent { - display: table-cell; - padding: 0 .5em 0 3ch; // indent 3 characters - text-indent: -2ch; // remove indentation for the +/- - white-space: pre-wrap; - word-break: break-word; - width: 100%; - vertical-align: top; - } - - &-lineText { - display: inline-block; - text-indent: 0; - } -} - - -// -// States -// ------------------------------- - -.github-HunkView.is-selected.is-hunkMode .github-HunkView-header { - background-color: @background-color-selected; - .github-HunkView-title { - color: @text-color; - } - .github-HunkView-stageButton, .github-HunkView-discardButton { - border-color: mix(@text-color, @background-color-selected, 25%); - } -} - -.github-HunkView-title:hover { - color: @text-color-highlight; -} - -.github-HunkView-line { - - // mixin - .hunk-line-mixin(@fg; @bg) { - &:hover { - background-color: @background-color-highlight; - } - &.is-selected { - color: @text-color; - background-color: @background-color-selected; - } - .github-HunkView-lineContent { - color: saturate( mix(@fg, @text-color-highlight, 20%), 20%); - background-color: saturate( mix(@bg, @hunk-bg-color, 15%), 20%); - } - // hightlight when focused + selected - .github-FilePatchView:focus &.is-selected .github-HunkView-lineContent { - color: saturate( mix(@fg, @text-color-highlight, 10%), 10%); - background-color: saturate( mix(@bg, @hunk-bg-color, 25%), 10%); - } - } - - &.is-deleted { - .hunk-line-mixin(@text-color-error, @background-color-error); - } - - &.is-added { - .hunk-line-mixin(@text-color-success, @background-color-success); - } - - // divider line between added and deleted lines - &.is-deleted + .is-added .github-HunkView-lineContent { - box-shadow: 0 -1px 0 hsla(0,0%,50%,.1); - } - -} - -// focus colors -.github-FilePatchView:focus { - .github-HunkView.is-selected.is-hunkMode .github-HunkView-title, - .github-HunkView.is-selected.is-hunkMode .github-HunkView-header, - .github-HunkView-line.is-selected .github-HunkView-lineNumber { - color: contrast(@button-background-color-selected); - background: @button-background-color-selected; - } - .github-HunkView-line.is-selected .github-HunkView-lineNumber { - border-color: mix(@button-border-color, @button-background-color-selected, 25%); - } - .github-HunkView.is-selected.is-hunkMode .github-HunkView { - &-stageButton, - &-discardButton { - border-color: mix(@hunk-bg-color, @button-background-color-selected, 30%); - &:hover { background-color: mix(@hunk-bg-color, @button-background-color-selected, 10%); } - &:active { background-color: @button-background-color-selected; } - } - } -} diff --git a/test/views/hunk-view.test.pending.js b/test/views/hunk-view.test.pending.js deleted file mode 100644 index 52bf4ede7d..0000000000 --- a/test/views/hunk-view.test.pending.js +++ /dev/null @@ -1,170 +0,0 @@ -import React from 'react'; -import {shallow} from 'enzyme'; - -import Hunk from '../../lib/models/hunk'; -import HunkLine from '../../lib/models/hunk-line'; -import HunkView from '../../lib/views/hunk-view'; - -describe('HunkView', function() { - let component, mousedownOnHeader, mousedownOnLine, mousemoveOnLine, contextMenuOnItem, didClickStageButton, didClickDiscardButton; - - beforeEach(function() { - const onlyLine = new HunkLine('only', 'added', 1, 1); - const emptyHunk = new Hunk(1, 1, 1, 1, 'heading', [ - onlyLine, - ]); - - mousedownOnHeader = sinon.spy(); - mousedownOnLine = sinon.spy(); - mousemoveOnLine = sinon.spy(); - contextMenuOnItem = sinon.spy(); - didClickStageButton = sinon.spy(); - didClickDiscardButton = sinon.spy(); - - component = ( - - ); - }); - - it('renders the hunk header and its lines', function() { - const hunk0 = new Hunk(5, 5, 2, 1, 'function fn {', [ - new HunkLine('line-1', 'unchanged', 5, 5), - new HunkLine('line-2', 'deleted', 6, -1), - new HunkLine('line-3', 'deleted', 7, -1), - new HunkLine('line-4', 'added', -1, 6), - ]); - - const wrapper = shallow(React.cloneElement(component, {hunk: hunk0})); - - assert.equal( - wrapper.find('.github-HunkView-header').render().text().trim(), - `${hunk0.getHeader().trim()} ${hunk0.getSectionHeading().trim()}`, - ); - - const lines0 = wrapper.find('LineView'); - assertHunkLineElementEqual( - lines0.at(0), - {oldLineNumber: '5', newLineNumber: '5', origin: ' ', content: 'line-1', isSelected: false}, - ); - assertHunkLineElementEqual( - lines0.at(1), - {oldLineNumber: '6', newLineNumber: ' ', origin: '-', content: 'line-2', isSelected: false}, - ); - assertHunkLineElementEqual( - lines0.at(2), - {oldLineNumber: '7', newLineNumber: ' ', origin: '-', content: 'line-3', isSelected: false}, - ); - assertHunkLineElementEqual( - lines0.at(3), - {oldLineNumber: ' ', newLineNumber: '6', origin: '+', content: 'line-4', isSelected: false}, - ); - - const hunk1 = new Hunk(8, 8, 1, 1, 'function fn2 {', [ - new HunkLine('line-1', 'deleted', 8, -1), - new HunkLine('line-2', 'added', -1, 8), - ]); - wrapper.setProps({hunk: hunk1}); - - assert.equal( - wrapper.find('.github-HunkView-header').render().text().trim(), - `${hunk1.getHeader().trim()} ${hunk1.getSectionHeading().trim()}`, - ); - - const lines1 = wrapper.find('LineView'); - assertHunkLineElementEqual( - lines1.at(0), - {oldLineNumber: '8', newLineNumber: ' ', origin: '-', content: 'line-1', isSelected: false}, - ); - assertHunkLineElementEqual( - lines1.at(1), - {oldLineNumber: ' ', newLineNumber: '8', origin: '+', content: 'line-2', isSelected: false}, - ); - - wrapper.setProps({ - selectedLines: new Set([hunk1.getLines()[1]]), - }); - - const lines2 = wrapper.find('LineView'); - assertHunkLineElementEqual( - lines2.at(0), - {oldLineNumber: '8', newLineNumber: ' ', origin: '-', content: 'line-1', isSelected: false}, - ); - assertHunkLineElementEqual( - lines2.at(1), - {oldLineNumber: ' ', newLineNumber: '8', origin: '+', content: 'line-2', isSelected: true}, - ); - }); - - it('adds the is-selected class based on the isSelected property', function() { - const wrapper = shallow(React.cloneElement(component, {isSelected: true})); - assert.isTrue(wrapper.find('.github-HunkView').hasClass('is-selected')); - - wrapper.setProps({isSelected: false}); - - assert.isFalse(wrapper.find('.github-HunkView').hasClass('is-selected')); - }); - - it('calls the didClickStageButton handler when the staging button is clicked', function() { - const wrapper = shallow(component); - - wrapper.find('.github-HunkView-stageButton').simulate('click'); - assert.isTrue(didClickStageButton.called); - }); - - describe('line selection', function() { - it('calls the mousedownOnLine and mousemoveOnLine handlers on mousedown and mousemove events', function() { - const hunk = new Hunk(1234, 1234, 1234, 1234, '', [ - new HunkLine('line-1', 'added', 1234, 1234), - new HunkLine('line-2', 'added', 1234, 1234), - new HunkLine('line-3', 'added', 1234, 1234), - new HunkLine('line-4', 'unchanged', 1234, 1234), - new HunkLine('line-5', 'deleted', 1234, 1234), - ]); - - // selectLine callback not called when selectionEnabled = false - const wrapper = shallow(React.cloneElement(component, {hunk, selectionEnabled: false})); - const lineDivAt = index => wrapper.find('LineView').at(index).shallow().find('.github-HunkView-line'); - - const payload0 = {}; - lineDivAt(0).simulate('mousedown', payload0); - assert.isTrue(mousedownOnLine.calledWith(payload0, hunk, hunk.lines[0])); - - const payload1 = {}; - lineDivAt(1).simulate('mousemove', payload1); - assert.isTrue(mousemoveOnLine.calledWith(payload1, hunk, hunk.lines[1])); - - // we don't call handler with redundant events - assert.equal(mousemoveOnLine.callCount, 1); - lineDivAt(1).simulate('mousemove'); - assert.equal(mousemoveOnLine.callCount, 1); - lineDivAt(2).simulate('mousemove'); - assert.equal(mousemoveOnLine.callCount, 2); - }); - }); -}); - -function assertHunkLineElementEqual(lineWrapper, {oldLineNumber, newLineNumber, origin, content, isSelected}) { - const subWrapper = lineWrapper.shallow(); - - assert.equal(subWrapper.find('.github-HunkView-lineNumber.is-old').render().text(), oldLineNumber); - assert.equal(subWrapper.find('.github-HunkView-lineNumber.is-new').render().text(), newLineNumber); - assert.equal(subWrapper.find('.github-HunkView-lineContent').render().text(), origin + content); - assert.equal(subWrapper.find('.github-HunkView-line').hasClass('is-selected'), isSelected); -} From 6af10df606ff8076d8b35b2af0006283f7e3fcbc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 15:55:25 -0400 Subject: [PATCH 0194/4053] Coverage for HunkHeaderView --- ...st.pending.js => hunk-header-view.test.js} | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) rename test/views/{hunk-header-view.test.pending.js => hunk-header-view.test.js} (71%) diff --git a/test/views/hunk-header-view.test.pending.js b/test/views/hunk-header-view.test.js similarity index 71% rename from test/views/hunk-header-view.test.pending.js rename to test/views/hunk-header-view.test.js index 525aa9a10c..8d7625d10d 100644 --- a/test/views/hunk-header-view.test.pending.js +++ b/test/views/hunk-header-view.test.js @@ -2,14 +2,16 @@ import React from 'react'; import {shallow} from 'enzyme'; import HunkHeaderView from '../../lib/views/hunk-header-view'; -import Hunk from '../../lib/models/hunk'; +import Hunk from '../../lib/models/patch/hunk'; describe('HunkHeaderView', function() { let atomEnv, hunk; beforeEach(function() { atomEnv = global.buildAtomEnvironment(); - hunk = new Hunk(0, 1, 10, 11, 'section heading', []); + hunk = new Hunk({ + oldStartRow: 0, oldRowCount: 10, newStartRow: 1, newRowCount: 11, sectionHeading: 'section heading', changes: [], + }); }); afterEach(function() { @@ -38,18 +40,18 @@ describe('HunkHeaderView', function() { it('applies a CSS class when selected', function() { const wrapper = shallow(buildApp({isSelected: true})); - assert.isTrue(wrapper.find('.github-HunkHeaderView').hasClass('is-selected')); + assert.isTrue(wrapper.find('.github-HunkHeaderView').hasClass('github-HunkHeaderView--isSelected')); wrapper.setProps({isSelected: false}); - assert.isFalse(wrapper.find('.github-HunkHeaderView').hasClass('is-selected')); + assert.isFalse(wrapper.find('.github-HunkHeaderView').hasClass('github-HunkHeaderView--isSelected')); }); it('applies a CSS class in hunk selection mode', function() { const wrapper = shallow(buildApp({selectionMode: 'hunk'})); - assert.isTrue(wrapper.find('.github-HunkHeaderView').hasClass('is-hunkMode')); + assert.isTrue(wrapper.find('.github-HunkHeaderView').hasClass('github-HunkHeaderView--isHunkMode')); wrapper.setProps({selectionMode: 'line'}); - assert.isFalse(wrapper.find('.github-HunkHeaderView').hasClass('is-hunkMode')); + assert.isFalse(wrapper.find('.github-HunkHeaderView').hasClass('github-HunkHeaderView--isHunkMode')); }); it('renders the hunk header title', function() { @@ -75,4 +77,24 @@ describe('HunkHeaderView', function() { button.simulate('click'); assert.isTrue(discardSelection.called); }); + + it('triggers the mousedown handler', function() { + const mouseDown = sinon.spy(); + const wrapper = shallow(buildApp({mouseDown})); + + wrapper.find('.github-HunkHeaderView').simulate('mousedown'); + + assert.isTrue(mouseDown.called); + }); + + it('stops mousedown events on the toggle button from propagating', function() { + const mouseDown = sinon.spy(); + const wrapper = shallow(buildApp({mouseDown})); + + const evt = {stopPropagation: sinon.spy()}; + wrapper.find('.github-HunkHeaderView-stageButton').simulate('mousedown', evt); + + assert.isFalse(mouseDown.called); + assert.isTrue(evt.stopPropagation.called); + }); }); From bc1f0231e51ac1dd6b7c2042e530ee854e1020c9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 16:04:01 -0400 Subject: [PATCH 0195/4053] ETOOMANYREFS --- test/controllers/git-tab-controller.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/controllers/git-tab-controller.test.js b/test/controllers/git-tab-controller.test.js index 890b39dfaf..1afaa57952 100644 --- a/test/controllers/git-tab-controller.test.js +++ b/test/controllers/git-tab-controller.test.js @@ -290,7 +290,7 @@ describe('GitTabController', function() { focusElement = stagingView.element; const commitViewElements = []; - commitView.refEditor.map(c => c.refElement.map(e => commitViewElements.push(e))); + commitView.refEditorComponent.map(e => commitViewElements.push(e)); commitView.refAbortMergeButton.map(e => commitViewElements.push(e)); commitView.refCommitButton.map(e => commitViewElements.push(e)); @@ -647,7 +647,7 @@ describe('GitTabController', function() { // new commit message const newMessage = 'such new very message'; const commitView = wrapper.find('CommitView'); - commitView.instance().editor.setText(newMessage); + commitView.instance().refEditorModel.map(e => e.setText(newMessage)); // no staged changes assert.lengthOf(wrapper.find('GitTabView').prop('stagedChanges'), 0); @@ -701,7 +701,7 @@ describe('GitTabController', function() { commitView.setState({showCoAuthorInput: true}); commitView.onSelectedCoAuthorsChanged([author]); const newMessage = 'Star Wars: A New Message'; - commitView.editor.setText(newMessage); + commitView.refEditorModel.map(e => e.setText(newMessage)); commandRegistry.dispatch(workspaceElement, 'github:amend-last-commit'); // verify that coAuthor was passed @@ -735,7 +735,7 @@ describe('GitTabController', function() { // buh bye co author const commitView = wrapper.find('CommitView').instance(); - assert.strictEqual(commitView.editor.getText(), ''); + assert.strictEqual(commitView.refEditorModel.map(e => e.getText()).getOr(''), ''); commitView.onSelectedCoAuthorsChanged([]); // amend again From e8bea900bdecc5ff4b0ce326c0bcd33f345442c7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 16:26:27 -0400 Subject: [PATCH 0196/4053] PASS YOU COWARDS --- test/controllers/git-tab-controller.test.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/controllers/git-tab-controller.test.js b/test/controllers/git-tab-controller.test.js index 1afaa57952..ddbed7873e 100644 --- a/test/controllers/git-tab-controller.test.js +++ b/test/controllers/git-tab-controller.test.js @@ -365,12 +365,12 @@ describe('GitTabController', function() { commandRegistry.dispatch(rootElement, 'core:focus-next'); assertSelected(['staged-1.txt']); - await assert.async.strictEqual(focusElement, wrapper.find('atom-text-editor').getDOMNode()); + await assert.async.strictEqual(focusElement, wrapper.find('AtomTextEditor').instance()); // This should be a no-op. (Actually, it'll insert a tab in the CommitView editor.) commandRegistry.dispatch(rootElement, 'core:focus-next'); assertSelected(['staged-1.txt']); - assert.strictEqual(focusElement, wrapper.find('atom-text-editor').getDOMNode()); + assert.strictEqual(focusElement, wrapper.find('AtomTextEditor').instance()); }); it('retreats focus from the CommitView through StagingView groups, but does not cycle', async function() { @@ -415,18 +415,18 @@ describe('GitTabController', function() { }); it('focuses the CommitView on github:commit with an empty commit message', async function() { - commitView.editor.setText(''); + commitView.refEditorModel.map(e => e.setText('')); sinon.spy(wrapper.instance(), 'commit'); wrapper.update(); commandRegistry.dispatch(workspaceElement, 'github:commit'); - await assert.async.strictEqual(focusElement, wrapper.find('atom-text-editor').getDOMNode()); + await assert.async.strictEqual(focusElement, wrapper.find('AtomTextEditor').instance()); assert.isFalse(wrapper.instance().commit.called); }); it('creates a commit on github:commit with a nonempty commit message', async function() { - commitView.editor.setText('I fixed the things'); + commitView.refEditorModel.map(e => e.setText('I fixed the things')); sinon.spy(repository, 'commit'); commandRegistry.dispatch(workspaceElement, 'github:commit'); @@ -625,7 +625,10 @@ describe('GitTabController', function() { assert.lengthOf(wrapper.find('GitTabView').prop('stagedChanges'), 1); // ensure that the commit editor is empty - assert.strictEqual(wrapper.find('CommitView').instance().editor.getText(), ''); + assert.strictEqual( + wrapper.find('CommitView').instance().refEditorModel.map(e => e.getText()).getOr(undefined), + '', + ); commandRegistry.dispatch(workspaceElement, 'github:amend-last-commit'); await assert.async.deepEqual( From d993c3b9fcede933b010d6be7ba1c8fbda859046 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 6 Sep 2018 16:37:57 -0400 Subject: [PATCH 0197/4053] :shirt: --- lib/github-package.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/github-package.js b/lib/github-package.js index 11040a3381..86e7271182 100644 --- a/lib/github-package.js +++ b/lib/github-package.js @@ -294,7 +294,6 @@ export default class GithubPackage { startOpen={this.startOpen} startRevealed={this.startRevealed} removeFilePatchItem={this.removeFilePatchItem} - workdirContextPool={this.contextPool} />, this.element, callback, ); } From 89e693f13d0559d67a8225c9c8637e4769d5a28a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 7 Sep 2018 08:00:13 -0400 Subject: [PATCH 0198/4053] Disable symlink tests on Windows --- .../controllers/file-patch-controller.test.js | 130 +++++++++--------- 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index c08c57bc3b..f69923022a 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -242,98 +242,100 @@ describe('FilePatchController', function() { }); }); - describe('toggleSymlinkChange', function() { - it('handles an addition and typechange with a special repository method', async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.symlink(dest, p); + if (process.platform !== 'win32') { + describe('toggleSymlinkChange', function() { + it('handles an addition and typechange with a special repository method', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.symlink(dest, p); - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); - await fs.unlink(p); - await fs.writeFile(p, 'fdsa\n', 'utf8'); + await fs.unlink(p); + await fs.writeFile(p, 'fdsa\n', 'utf8'); - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); - sinon.spy(repository, 'stageFileSymlinkChange'); + sinon.spy(repository, 'stageFileSymlinkChange'); - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); + await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); - }); + assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); + }); - it('stages non-addition typechanges normally', async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.symlink(dest, p); + it('stages non-addition typechanges normally', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.symlink(dest, p); - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); - await fs.unlink(p); + await fs.unlink(p); - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); - sinon.spy(repository, 'stageFiles'); + sinon.spy(repository, 'stageFiles'); - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); + await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - assert.isTrue(repository.stageFiles.calledWith(['waslink.txt'])); - }); + assert.isTrue(repository.stageFiles.calledWith(['waslink.txt'])); + }); - it('handles a deletion and typechange with a special repository method', async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.writeFile(p, 'fdsa\n', 'utf8'); + it('handles a deletion and typechange with a special repository method', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.writeFile(p, 'fdsa\n', 'utf8'); - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); - await fs.unlink(p); - await fs.symlink(dest, p); - await repository.stageFiles(['waslink.txt']); + await fs.unlink(p); + await fs.symlink(dest, p); + await repository.stageFiles(['waslink.txt']); - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); - sinon.spy(repository, 'stageFileSymlinkChange'); + sinon.spy(repository, 'stageFileSymlinkChange'); - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); + await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); - }); + assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); + }); - it('unstages non-deletion typechanges normally', async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.symlink(dest, p); + it('unstages non-deletion typechanges normally', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.symlink(dest, p); - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); - await fs.unlink(p); + await fs.unlink(p); - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); - sinon.spy(repository, 'unstageFiles'); + sinon.spy(repository, 'unstageFiles'); - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); + await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - assert.isTrue(repository.unstageFiles.calledWith(['waslink.txt'])); + assert.isTrue(repository.unstageFiles.calledWith(['waslink.txt'])); + }); }); - }); + } it('calls discardLines with selected rows', function() { const discardLines = sinon.spy(); From a1a531e0d4294645743aee385b6006f70c0740b2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 7 Sep 2018 08:17:04 -0400 Subject: [PATCH 0199/4053] Mode changes aren't a thing on Windows either --- .../controllers/file-patch-controller.test.js | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index f69923022a..d95211a04e 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -211,38 +211,38 @@ describe('FilePatchController', function() { }); }); - describe('toggleModeChange()', function() { - it("it stages an unstaged file's new mode", async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); - await fs.chmod(p, 0o755); - repository.refresh(); - const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); + if (process.platform !== 'win32') { + describe('toggleModeChange()', function() { + it("it stages an unstaged file's new mode", async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); + await fs.chmod(p, 0o755); + repository.refresh(); + const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); - const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'unstaged'})); + const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'unstaged'})); - sinon.spy(repository, 'stageFileModeChange'); - await wrapper.find('FilePatchView').prop('toggleModeChange')(); + sinon.spy(repository, 'stageFileModeChange'); + await wrapper.find('FilePatchView').prop('toggleModeChange')(); - assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100755')); - }); + assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100755')); + }); - it("it stages a staged file's old mode", async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); - await fs.chmod(p, 0o755); - await repository.stageFiles(['a.txt']); - repository.refresh(); - const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: true}); + it("it stages a staged file's old mode", async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); + await fs.chmod(p, 0o755); + await repository.stageFiles(['a.txt']); + repository.refresh(); + const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'staged'})); + const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'staged'})); - sinon.spy(repository, 'stageFileModeChange'); - await wrapper.find('FilePatchView').prop('toggleModeChange')(); + sinon.spy(repository, 'stageFileModeChange'); + await wrapper.find('FilePatchView').prop('toggleModeChange')(); - assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100644')); + assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100644')); + }); }); - }); - if (process.platform !== 'win32') { describe('toggleSymlinkChange', function() { it('handles an addition and typechange with a special repository method', async function() { const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); From 51b921f45aa1d9fe7189bd6fed7dd7566d4a90bb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 7 Sep 2018 10:53:29 -0400 Subject: [PATCH 0200/4053] Track selection mode in FilePatchController --- lib/controllers/file-patch-controller.js | 38 +++++++--- .../controllers/file-patch-controller.test.js | 76 ++++++++++++++++--- 2 files changed, 92 insertions(+), 22 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 63c9642df2..95423b2946 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -33,6 +33,7 @@ export default class FilePatchController extends React.Component { this.state = { lastFilePatch: this.props.filePatch, + selectionMode: 'line', selectedRows: new Set(), }; @@ -59,6 +60,7 @@ export default class FilePatchController extends React.Component { {...this.props} selectedRows={this.state.selectedRows} + selectionMode={this.state.selectionMode} selectedRowsChanged={this.selectedRowsChanged} diveIntoMirrorPatch={this.diveIntoMirrorPatch} @@ -108,15 +110,22 @@ export default class FilePatchController extends React.Component { }); } - toggleRows() { - if (this.state.selectedRows.size === 0) { + async toggleRows(rowSet, nextSelectionMode) { + let chosenRows = rowSet; + if (chosenRows) { + await this.selectedRowsChanged(chosenRows, nextSelectionMode); + } else { + chosenRows = this.state.selectedRows; + } + + if (chosenRows.size === 0) { return Promise.resolve(); } return this.stagingOperation(() => { const patch = this.withStagingStatus({ - staged: () => this.props.filePatch.getUnstagePatchForLines(this.state.selectedRows), - unstaged: () => this.props.filePatch.getStagePatchForLines(this.state.selectedRows), + staged: () => this.props.filePatch.getUnstagePatchForLines(chosenRows), + unstaged: () => this.props.filePatch.getStagePatchForLines(chosenRows), }); return this.props.repository.applyPatchToIndex(patch); }); @@ -154,16 +163,25 @@ export default class FilePatchController extends React.Component { }); } - discardRows() { - return this.props.discardLines(this.props.filePatch, this.state.selectedRows, this.props.repository); + async discardRows(rowSet, nextSelectionMode) { + let chosenRows = rowSet; + if (chosenRows) { + await this.selectedRowsChanged(chosenRows, nextSelectionMode); + } else { + chosenRows = this.state.selectedRows; + } + + return this.props.discardLines(this.props.filePatch, chosenRows, this.props.repository); } - selectedRowsChanged(rows) { - if (equalSets(this.state.selectedRows, rows)) { - return; + selectedRowsChanged(rows, nextSelectionMode) { + if (equalSets(this.state.selectedRows, rows) && this.state.selectionMode === nextSelectionMode) { + return Promise.resolve(); } - this.setState({selectedRows: rows}); + return new Promise(resolve => { + this.setState({selectedRows: rows, selectionMode: nextSelectionMode}, resolve); + }); } withStagingStatus(callbacks) { diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index d95211a04e..61681ecf43 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -149,32 +149,49 @@ describe('FilePatchController', function() { }); }); - describe('selected row tracking', function() { + describe('selected row and selection mode tracking', function() { it('captures the selected row set', function() { const wrapper = shallow(buildApp()); assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); + assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'hunk'); assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); + assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); }); - it('does not re-render if the row set is unchanged', function() { + it('does not re-render if the row set and selection mode are unchanged', function() { const wrapper = shallow(buildApp()); assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); + assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); sinon.spy(wrapper.instance(), 'render'); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([2, 1])); + + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'line'); + + assert.isTrue(wrapper.instance().render.called); assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); + assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); + + wrapper.instance().render.resetHistory(); + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([2, 1]), 'line'); + + assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); + assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); assert.isFalse(wrapper.instance().render.called); + + wrapper.instance().render.resetHistory(); + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'hunk'); + + assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); + assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); + assert.isTrue(wrapper.instance().render.called); }); }); describe('toggleRows()', function() { it('is a no-op with no selected rows', async function() { const wrapper = shallow(buildApp()); - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); sinon.spy(repository, 'applyPatchToIndex'); @@ -191,8 +208,24 @@ describe('FilePatchController', function() { await wrapper.find('FilePatchView').prop('toggleRows')(); - assert.isTrue(filePatch.getStagePatchForLines.called); + assert.sameMembers(Array.from(filePatch.getStagePatchForLines.lastCall.args[0]), [1]); + assert.isTrue(repository.applyPatchToIndex.calledWith(filePatch.getStagePatchForLines.returnValues[0])); + }); + + it('toggles a different row set if provided', async function() { + const wrapper = shallow(buildApp()); + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1]), 'line'); + + sinon.spy(filePatch, 'getStagePatchForLines'); + sinon.spy(repository, 'applyPatchToIndex'); + + await wrapper.find('FilePatchView').prop('toggleRows')(new Set([2]), 'hunk'); + + assert.sameMembers(Array.from(filePatch.getStagePatchForLines.lastCall.args[0]), [2]); assert.isTrue(repository.applyPatchToIndex.calledWith(filePatch.getStagePatchForLines.returnValues[0])); + + assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [2]); + assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); }); it('applies an unstage patch to the index', async function() { @@ -206,7 +239,7 @@ describe('FilePatchController', function() { await wrapper.find('FilePatchView').prop('toggleRows')(); - assert.isTrue(otherPatch.getUnstagePatchForLines.called); + assert.sameMembers(Array.from(otherPatch.getUnstagePatchForLines.lastCall.args[0]), [2]); assert.isTrue(repository.applyPatchToIndex.calledWith(otherPatch.getUnstagePatchForLines.returnValues[0])); }); }); @@ -337,13 +370,32 @@ describe('FilePatchController', function() { }); } - it('calls discardLines with selected rows', function() { + it('calls discardLines with selected rows', async function() { + const discardLines = sinon.spy(); + const wrapper = shallow(buildApp({discardLines})); + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); + + await wrapper.find('FilePatchView').prop('discardRows')(); + + const lastArgs = discardLines.lastCall.args; + assert.strictEqual(lastArgs[0], filePatch); + assert.sameMembers(Array.from(lastArgs[1]), [1, 2]); + assert.strictEqual(lastArgs[2], repository); + }); + + it('calls discardLines with explicitly provided rows', async function() { const discardLines = sinon.spy(); const wrapper = shallow(buildApp({discardLines})); wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); - wrapper.find('FilePatchView').prop('discardRows')(); + await wrapper.find('FilePatchView').prop('discardRows')(new Set([4, 5]), 'hunk'); + + const lastArgs = discardLines.lastCall.args; + assert.strictEqual(lastArgs[0], filePatch); + assert.sameMembers(Array.from(lastArgs[1]), [4, 5]); + assert.strictEqual(lastArgs[2], repository); - assert.isTrue(discardLines.calledWith(filePatch, new Set([1, 2]), repository)); + assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [4, 5]); + assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); }); }); From 370ce170f721edc6a799c8b6078b326a582bff29 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 7 Sep 2018 16:08:01 -0400 Subject: [PATCH 0201/4053] Let's see if bumping node helps? --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index c18ed6b030..e084059b99 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,7 @@ environment: - ATOM_CHANNEL: dev install: - - ps: Install-Product node 6 + - ps: Install-Product node 8 build_script: - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) From 909f9e5415df462f81ddf230588690e2432b4b15 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 10 Sep 2018 11:06:13 -0400 Subject: [PATCH 0202/4053] WIP --- lib/models/patch/builder.js | 7 +- lib/views/file-patch-view.js | 28 ++++- test/views/file-patch-view.test.js | 193 +++++++++++++++++++++++++---- 3 files changed, 204 insertions(+), 24 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 3bce9deb72..3a419961cd 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -97,6 +97,7 @@ const CHANGEKIND = { }; function buildHunks(diff) { + console.log('--- building hunks ---'); let bufferText = ''; const hunks = []; @@ -105,6 +106,7 @@ function buildHunks(diff) { let startOffset = 0; for (const hunkData of diff.hunks) { + console.log('-- starting hunk'); const bufferStartRow = bufferRow; const bufferStartOffset = bufferOffset; @@ -121,6 +123,7 @@ function buildHunks(diff) { } if (LastChangeKind !== null) { + console.log(`finishing change: currentRangeStart = ${currentRangeStart} bufferRow = ${bufferRow} lastLineLength = ${lastLineLength}`); changes.push( new LastChangeKind( new IndexedRowRange({ @@ -137,9 +140,11 @@ function buildHunks(diff) { for (const lineText of hunkData.lines) { const bufferLine = lineText.slice(1) + '\n'; - nextLineLength = bufferLine.length - 1; + nextLineLength = lineText.length - 1; bufferText += bufferLine; + console.log(`bufferLine = ${bufferLine}`); + const ChangeKind = CHANGEKIND[lineText[0]]; if (ChangeKind === undefined) { throw new Error(`Unknown diff status character: "${lineText[0]}"`); diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index b5169d897e..58242886b6 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -29,6 +29,7 @@ export default class FilePatchView extends React.Component { stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, isPartiallyStaged: PropTypes.bool.isRequired, filePatch: PropTypes.object.isRequired, + selectionMode: PropTypes.oneOf(['hunk', 'line']).isRequired, selectedRows: PropTypes.object.isRequired, repository: PropTypes.object.isRequired, @@ -58,6 +59,7 @@ export default class FilePatchView extends React.Component { this.mouseSelectionInProgress = false; this.lastMouseMoveLine = null; + this.nextSelectionMode = this.props.selectionMode; this.hunksByMarkerID = new Map(); this.hunkMarkerLayerHolder = new RefHolder(); this.refRoot = new RefHolder(); @@ -77,10 +79,23 @@ export default class FilePatchView extends React.Component { prevProps.filePatch, prevProps.selectedRows, ); - editor.setSelectedBufferRange(newSelectionRange); + + if (this.props.selectionMode === 'line') { + editor.setSelectedBufferRange(newSelectionRange); + } else { + this.nextSelectionMode = 'hunk'; + const nextHunks = new Set( + Range.fromObject(newSelectionRange).getRows().map(row => this.getHunkAt(row)), + ); + editor.setSelectedBufferRanges( + Array.from(nextHunks, hunk => hunk.getBufferRange()), + ); + } return null; }); + } else { + this.nextSelectionMode = this.props.selectionMode; } } @@ -657,7 +672,7 @@ export default class FilePatchView extends React.Component { } didChangeSelectedRows() { - this.props.selectedRowsChanged(this.getSelectedRows()); + this.props.selectedRowsChanged(this.getSelectedRows(), this.nextSelectionMode); } oldLineNumberLabel({bufferRow, softWrapped}) { @@ -721,6 +736,15 @@ export default class FilePatchView extends React.Component { return false; } + withSelectionMode(callbacks) { + const callback = callbacks[this.props.selectionMode]; + /* istanbul ignore if */ + if (!callback) { + throw new Error(`Unknown selection mode: ${this.props.selectionMode}`); + } + return callback(); + } + pad(num) { const maxDigits = this.props.filePatch.getMaxLineNumberWidth(); if (num === null) { diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 06d07d35a0..8d39b85f74 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -7,7 +7,7 @@ import {buildFilePatch} from '../../lib/models/patch'; import {nullFile} from '../../lib/models/patch/file'; import {nullFilePatch} from '../../lib/models/patch/file-patch'; -describe('FilePatchView', function() { +describe.only('FilePatchView', function() { let atomEnv, repository, filePatch; beforeEach(async function() { @@ -48,6 +48,7 @@ describe('FilePatchView', function() { stagingStatus: 'unstaged', isPartiallyStaged: false, filePatch, + selectionMode: 'line', selectedRows: new Set(), repository, @@ -83,12 +84,13 @@ describe('FilePatchView', function() { assert.strictEqual(editor.instance().getModel().getText(), filePatch.getBufferText()); }); - it('preserves the selection index when a new file patch arrives', function() { - let lastSelectedRows = new Set([2]); - const selectedRowsChanged = sinon.stub().callsFake(rows => { - lastSelectedRows = rows; - }); - const wrapper = mount(buildApp({selectedRows: new Set([2]), selectedRowsChanged})); + it('preserves the selection index when a new file patch arrives in line selection mode', function() { + const selectedRowsChanged = sinon.spy(); + const wrapper = mount(buildApp({ + selectedRows: new Set([2]), + selectionMode: 'line', + selectedRowsChanged, + })); const nextPatch = buildFilePatch([{ oldPath: 'path.txt', @@ -106,13 +108,95 @@ describe('FilePatchView', function() { }]); wrapper.setProps({filePatch: nextPatch}); - assert.sameMembers(Array.from(lastSelectedRows), [3]); + assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [3]); + assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'line'); + + const editor = wrapper.find('AtomTextEditor').getDOMNode().getModel(); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[3, 0], [3, 4]], + ]); selectedRowsChanged.resetHistory(); wrapper.setProps({isPartiallyStaged: true}); assert.isFalse(selectedRowsChanged.called); }); + it.only('selects the next full hunk when a new file patch arrives in hunk selection mode', function() { + const multiHunkPatch = buildFilePatch([{ + oldPath: 'path.txt', + oldMode: '100644', + newPath: 'path.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 10, oldLineCount: 4, newStartLine: 10, newLineCount: 4, + heading: '0', + lines: [' 0000', '+0001', ' 0002', '-0003', ' 0004'], + }, + { + oldStartLine: 20, oldLineCount: 3, newStartLine: 20, newLineCount: 4, + heading: '1', + lines: [' 0005', '+0006', '+0007', '-0008', ' 0009'], + }, + { + oldStartLine: 30, oldLineCount: 3, newStartLine: 31, newLineCount: 3, + heading: '2', + lines: [' 0010', '+0011', '-0012', ' 0013'], + }, + { + oldStartLine: 40, oldLineCount: 4, newStartLine: 41, newLineCount: 4, + heading: '3', + lines: [' 0014', '-0015', ' 0016', '+0017', ' 0018'], + }, + ], + }]); + + const selectedRowsChanged = sinon.spy(); + const wrapper = mount(buildApp({ + filePatch: multiHunkPatch, + selectedRows: new Set([6, 7, 8]), + selectionMode: 'hunk', + selectedRowsChanged, + })); + + const nextPatch = buildFilePatch([{ + oldPath: 'path.txt', + oldMode: '100644', + newPath: 'path.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 10, oldLineCount: 4, newStartLine: 10, newLineCount: 4, + heading: '0', + lines: [' 0000', '+0001', ' 0002', '-0003', ' 0004'], + }, + { + oldStartLine: 30, oldLineCount: 3, newStartLine: 30, newLineCount: 3, + heading: '2', + // 5 6 7 8 + lines: [' 0010', '+0011', '-0012', ' 0013'], + }, + { + oldStartLine: 40, oldLineCount: 4, newStartLine: 40, newLineCount: 4, + heading: '3', + lines: [' 0014', '-0015', ' 0016', '+0017', ' 0018'], + }, + ], + }]); + console.log(nextPatch.getHunks()[1].getBufferRange().toString()); + + wrapper.setProps({filePatch: nextPatch}); + + assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [6, 7]); + assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'hunk'); + const editor = wrapper.find('AtomTextEditor').getDOMNode().getModel(); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[5, 0], [8, 3]], + ]); + }); + it('unregisters the mouseup handler on unmount', function() { sinon.spy(window, 'addEventListener'); sinon.spy(window, 'removeEventListener'); @@ -302,21 +386,59 @@ describe('FilePatchView', function() { }); it('pluralizes the toggle and discard button labels', function() { - const wrapper = shallow(buildApp({selectedRows: new Set([2])})); - assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Selected Change'); - assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('discardSelectionLabel'), 'Discard Selected Change'); - - wrapper.setProps({selectedRows: new Set([1, 2, 3])}); - assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Selected Changes'); - assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('discardSelectionLabel'), 'Discard Selected Changes'); + const wrapper = shallow(buildApp({selectedRows: new Set([2]), selectionMode: 'line'})); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Selection'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('discardSelectionLabel'), 'Discard Selection'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('toggleSelectionLabel'), 'Stage Hunk'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('discardSelectionLabel'), 'Discard Hunk'); + + wrapper.setProps({selectedRows: new Set([1, 2, 3]), selectionMode: 'line'}); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Selections'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('discardSelectionLabel'), 'Discard Selections'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('toggleSelectionLabel'), 'Stage Hunk'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('discardSelectionLabel'), 'Discard Hunk'); + + wrapper.setProps({selectedRows: new Set([1, 2, 3]), selectionMode: 'hunk'}); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Hunk'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('discardSelectionLabel'), 'Discard Hunk'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('toggleSelectionLabel'), 'Stage Hunk'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('discardSelectionLabel'), 'Discard Hunk'); + + wrapper.setProps({selectedRows: new Set([1, 2, 3, 6, 7]), selectionMode: 'hunk'}); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Hunks'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('discardSelectionLabel'), 'Discard Hunks'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('toggleSelectionLabel'), 'Stage Hunks'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('discardSelectionLabel'), 'Discard Hunks'); }); it('uses the appropriate staging action verb in hunk header button labels', function() { - const wrapper = shallow(buildApp({selectedRows: new Set([2]), stagingStatus: 'unstaged'})); - assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Selected Change'); + const wrapper = shallow(buildApp({ + selectedRows: new Set([2]), + stagingStatus: 'unstaged', + selectionMode: 'line', + })); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Selection'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('toggleSelectionLabel'), 'Stage Hunk'); wrapper.setProps({stagingStatus: 'staged'}); - assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Unstage Selected Change'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Unstage Selection'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('toggleSelectionLabel'), 'Unstage Hunk'); + }); + + it('uses the appropriate staging action noun in hunk header button labels', function() { + const wrapper = shallow(buildApp({ + selectedRows: new Set([1, 2, 3]), + stagingStatus: 'unstaged', + selectionMode: 'line', + })); + + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Selections'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('toggleSelectionLabel'), 'Stage Hunk'); + + wrapper.setProps({selectionMode: 'hunk'}); + + assert.strictEqual(wrapper.find('HunkHeaderView').at(0).prop('toggleSelectionLabel'), 'Stage Hunk'); + assert.strictEqual(wrapper.find('HunkHeaderView').at(1).prop('toggleSelectionLabel'), 'Stage Hunk'); }); it('handles mousedown as a selection event', function() { @@ -341,11 +463,30 @@ describe('FilePatchView', function() { }]); const selectedRowsChanged = sinon.spy(); - const wrapper = mount(buildApp({filePatch: fp, selectedRowsChanged})); + const wrapper = mount(buildApp({filePatch: fp, selectedRowsChanged, selectionMode: 'line'})); wrapper.find('HunkHeaderView').at(1).prop('mouseDown')({button: 0}, fp.getHunks()[1]); - assert.isTrue(selectedRowsChanged.calledWith(new Set([4]))); + assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [4]); + assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'hunk'); + }); + + it('handles a toggle click on a hunk containing a selection', function() { + const toggleRows = sinon.spy(); + const wrapper = mount(buildApp({selectedRows: new Set([2]), toggleRows, selectionMode: 'line'})); + + wrapper.find('HunkHeaderView').at(0).prop('toggleSelection')(); + assert.sameMembers(Array.from(toggleRows.lastCall.args[0]), [2]); + assert.strictEqual(toggleRows.args[1], 'line'); + }); + + it('handles a toggle click on a hunk not containing a selection', function() { + const toggleRows = sinon.spy(); + const wrapper = mount(buildApp({selectedRows: new Set([2]), toggleRows, selectionMode: 'line'})); + + wrapper.find('HunkHeaderView').at(1).prop('toggleSelection')(); + assert.sameMembers(Array.from(toggleRows.lastCall.args[0]), [6, 7]); + assert.strictEqual(toggleRows.args[1], 'hunk'); }); }); @@ -387,6 +528,15 @@ describe('FilePatchView', function() { ]); }); + it('changes to line selection mode on click', function() { + const selectedRowsChanged = sinon.spy(); + wrapper.setProps({selectedRowsChanged, selectionMode: 'hunk'}); + + instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {button: 0}}); + assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [2]); + assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'line'); + }); + it('ignores right clicks', function() { instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {button: 1}}); assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ @@ -645,7 +795,8 @@ describe('FilePatchView', function() { editor.addSelectionForBufferRange([[3, 1], [4, 0]]); - assert.isTrue(selectedRowsChanged.calledWith(new Set([3, 4]))); + assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [3, 4]); + assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'line'); }); describe('when viewing an empty patch', function() { From 579b703a4165ccacf1ceda92cfa24eada47becd8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 10 Sep 2018 13:05:15 -0400 Subject: [PATCH 0203/4053] :fire: console.logs --- lib/models/patch/builder.js | 5 ----- test/views/file-patch-view.test.js | 1 - 2 files changed, 6 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 3a419961cd..c918ed671c 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -97,7 +97,6 @@ const CHANGEKIND = { }; function buildHunks(diff) { - console.log('--- building hunks ---'); let bufferText = ''; const hunks = []; @@ -106,7 +105,6 @@ function buildHunks(diff) { let startOffset = 0; for (const hunkData of diff.hunks) { - console.log('-- starting hunk'); const bufferStartRow = bufferRow; const bufferStartOffset = bufferOffset; @@ -123,7 +121,6 @@ function buildHunks(diff) { } if (LastChangeKind !== null) { - console.log(`finishing change: currentRangeStart = ${currentRangeStart} bufferRow = ${bufferRow} lastLineLength = ${lastLineLength}`); changes.push( new LastChangeKind( new IndexedRowRange({ @@ -143,8 +140,6 @@ function buildHunks(diff) { nextLineLength = lineText.length - 1; bufferText += bufferLine; - console.log(`bufferLine = ${bufferLine}`); - const ChangeKind = CHANGEKIND[lineText[0]]; if (ChangeKind === undefined) { throw new Error(`Unknown diff status character: "${lineText[0]}"`); diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 8d39b85f74..25e381b9ac 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -185,7 +185,6 @@ describe.only('FilePatchView', function() { }, ], }]); - console.log(nextPatch.getHunks()[1].getBufferRange().toString()); wrapper.setProps({filePatch: nextPatch}); From a619e7e0b539bc4e7a132167ed76ff382d7e95c0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 11 Sep 2018 11:33:43 -0400 Subject: [PATCH 0204/4053] Rewrite AtomTextEditor, now with test coverage :stars: --- lib/atom/atom-text-editor.js | 120 +++----------- test/atom/atom-text-editor.test.js | 244 +++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+), 96 deletions(-) create mode 100644 test/atom/atom-text-editor.test.js diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index 2c02030017..a68686fea2 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -7,78 +7,56 @@ import {RefHolderPropType} from '../prop-types'; import {autobind, extractProps} from '../helpers'; const editorProps = { - autoIndent: PropTypes.bool, - autoIndentOnPaste: PropTypes.bool, - undoGroupingInterval: PropTypes.number, - scrollSensitivity: PropTypes.number, - encoding: PropTypes.string, - softTabs: PropTypes.bool, - atomicSoftTabs: PropTypes.bool, - tabLength: PropTypes.number, - softWrapped: PropTypes.bool, - softWrapHangingIndentLenth: PropTypes.number, - softWrapAtPreferredLineLength: PropTypes.bool, - preferredLineLength: PropTypes.number, - maxScreenLineLength: PropTypes.number, + buffer: PropTypes.object, // FIXME make proptype more specific mini: PropTypes.bool, readOnly: PropTypes.bool, placeholderText: PropTypes.string, lineNumberGutterVisible: PropTypes.bool, - showIndentGuide: PropTypes.bool, - showLineNumbers: PropTypes.bool, - showInvisibles: PropTypes.bool, - invisibles: PropTypes.string, - editorWidthInChars: PropTypes.number, - width: PropTypes.number, - scrollPastEnd: PropTypes.bool, autoHeight: PropTypes.bool, autoWidth: PropTypes.bool, - showCursorOnSelection: PropTypes.bool, }; export const TextEditorContext = React.createContext(); -export default class AtomTextEditor extends React.PureComponent { +export default class AtomTextEditor extends React.Component { static propTypes = { ...editorProps, - text: PropTypes.string, - didChange: PropTypes.func, + + workspace: PropTypes.object.isRequired, // FIXME make more specific + didChangeCursorPosition: PropTypes.func, didAddSelection: PropTypes.func, didChangeSelectionRange: PropTypes.func, didDestroySelection: PropTypes.func, observeSelections: PropTypes.func, - preserveMarkers: PropTypes.bool, + refModel: RefHolderPropType, + children: PropTypes.node, } static defaultProps = { - text: '', - didChange: () => {}, didChangeCursorPosition: () => {}, didAddSelection: () => {}, didChangeSelectionRange: () => {}, didDestroySelection: () => {}, } - constructor(props, context) { - super(props, context); - autobind(this, 'didChange', 'didChangeCursorPosition', 'observeSelections'); + constructor(props) { + super(props); + autobind(this, 'observeSelections'); this.subs = new CompositeDisposable(); - this.suppressChange = false; + this.refParent = new RefHolder(); this.refElement = new RefHolder(); this.refModel = null; } render() { - this.getRefModel().map(() => this.quietlySetText(this.props.text)); - return ( - +
{this.props.children} @@ -87,90 +65,44 @@ export default class AtomTextEditor extends React.PureComponent { } componentDidMount() { - this.setAttributesOnElement(this.props); + const modelProps = extractProps(this.props, editorProps); - this.refElement.map(element => { - const editor = element.getModel(); - editor.setText(this.props.text, {bypassReadOnly: true}); + this.refParent.map(element => { + const editor = this.props.workspace.buildTextEditor(modelProps); + element.appendChild(editor.getElement()); this.getRefModel().setter(editor); + this.refElement.setter(editor.getElement()); this.subs.add( - editor.onDidChange(this.didChange), - editor.onDidChangeCursorPosition(this.didChangeCursorPosition), + editor.onDidChangeCursorPosition(this.props.didChangeCursorPosition), editor.observeSelections(this.observeSelections), ); - // shhh, eslint. shhhh return null; }); } componentDidUpdate(prevProps) { - this.setAttributesOnElement(this.props); + const modelProps = extractProps(this.props, editorProps); + this.getRefModel().map(editor => editor.update(modelProps)); } componentWillUnmount() { + this.getRefModel().map(editor => editor.destroy()); this.subs.dispose(); } - quietlySetText(text) { - try { - const editor = this.getModel(); - if (editor.getText() === text) { - return; - } - - this.suppressChange = true; - if (this.props.preserveMarkers) { - editor.getBuffer().setTextViaDiff(text); - } else { - editor.setText(text, {bypassReadOnly: true}); - } - } finally { - this.suppressChange = false; - } - } - - setAttributesOnElement(theProps) { - const modelProps = extractProps(this.props, editorProps); - this.getModel().update(modelProps); - } - - didChange() { - if (this.suppressChange) { - return; - } - - this.props.didChange(this.getModel()); - } - - didChangeCursorPosition() { - if (!this.suppressChange) { - this.props.didChangeCursorPosition(this.getModel()); - } - } - observeSelections(selection) { const selectionSubs = new CompositeDisposable( - selection.onDidChangeRange(event => { - if (!this.suppressChange) { - this.props.didChangeSelectionRange(event); - } - }), + selection.onDidChangeRange(this.props.didChangeSelectionRange), selection.onDidDestroy(() => { selectionSubs.dispose(); this.subs.remove(selectionSubs); - - if (!this.suppressChange) { - this.props.didDestroySelection(selection); - } + this.props.didDestroySelection(selection); }), ); this.subs.add(selectionSubs); - - if (!this.suppressChange) { - this.props.didAddSelection(selection); - } + this.props.didAddSelection(selection); } contains(element) { @@ -181,10 +113,6 @@ export default class AtomTextEditor extends React.PureComponent { this.refElement.map(e => e.focus()); } - getModel() { - return this.refElement.map(e => e.getModel()).getOr(null); - } - getRefModel() { if (this.props.refModel) { return this.props.refModel; diff --git a/test/atom/atom-text-editor.test.js b/test/atom/atom-text-editor.test.js new file mode 100644 index 0000000000..81290782f9 --- /dev/null +++ b/test/atom/atom-text-editor.test.js @@ -0,0 +1,244 @@ +import React from 'react'; +import {mount} from 'enzyme'; +import {TextBuffer} from 'atom'; + +import RefHolder from '../../lib/models/ref-holder'; +import AtomTextEditor from '../../lib/atom/atom-text-editor'; + +describe('AtomTextEditor', function() { + let atomEnv, workspace, refModel; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + workspace = atomEnv.workspace; + refModel = new RefHolder(); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + it('creates a text editor element', function() { + const app = mount( + , + ); + + const children = app.find('div').getDOMNode().children; + assert.lengthOf(children, 1); + const child = children[0]; + assert.isTrue(workspace.isTextEditor(child.getModel())); + assert.strictEqual(child.getModel(), refModel.getOr(undefined)); + }); + + it('creates its own model ref if one is not provided by a parent', function() { + const app = mount(); + assert.isTrue(workspace.isTextEditor(app.instance().refModel.get())); + }); + + it('configures the created text editor with props', function() { + mount( + , + ); + + const editor = refModel.get(); + + assert.isTrue(editor.isMini()); + assert.isTrue(editor.isReadOnly()); + assert.strictEqual(editor.getPlaceholderText(), 'hooray'); + assert.isFalse(editor.lineNumberGutter.isVisible()); + assert.isTrue(editor.getAutoWidth()); + assert.isFalse(editor.getAutoHeight()); + }); + + it('accepts a precreated buffer', function() { + const buffer = new TextBuffer(); + buffer.setText('precreated'); + + mount( + , + ); + + const editor = refModel.get(); + + assert.strictEqual(editor.getText(), 'precreated'); + + buffer.setText('changed'); + assert.strictEqual(editor.getText(), 'changed'); + }); + + it('updates changed attributes on re-render', function() { + const app = mount( + , + ); + + const editor = refModel.get(); + assert.isTrue(editor.isReadOnly()); + + app.setProps({readOnly: false}); + + assert.isFalse(editor.isReadOnly()); + }); + + it('destroys its text editor on unmount', function() { + const app = mount( + , + ); + + const editor = refModel.get(); + sinon.spy(editor, 'destroy'); + + app.unmount(); + + assert.isTrue(editor.destroy.called); + }); + + describe('event subscriptions', function() { + let handler, buffer; + + beforeEach(function() { + handler = sinon.spy(); + + buffer = new TextBuffer({ + text: 'one\ntwo\nthree\nfour\nfive\n', + }); + }); + + it('triggers didChangeCursorPosition when the cursor position changes', function() { + mount( + , + ); + + const editor = refModel.get(); + editor.setCursorBufferPosition([2, 3]); + + assert.isTrue(handler.called); + const [{newBufferPosition}] = handler.lastCall.args; + assert.deepEqual(newBufferPosition.serialize(), [2, 3]); + + handler.resetHistory(); + editor.setCursorBufferPosition([2, 3]); + assert.isFalse(handler.called); + }); + + it('triggers didAddSelection when a selection is added', function() { + mount( + , + ); + + const editor = refModel.get(); + editor.addSelectionForBufferRange([[1, 0], [3, 3]]); + + assert.isTrue(handler.called); + const [selection] = handler.lastCall.args; + assert.deepEqual(selection.getBufferRange().serialize(), [[1, 0], [3, 3]]); + }); + + it("triggers didChangeSelectionRange when an existing selection's range is altered", function() { + mount( + , + ); + + const editor = refModel.get(); + editor.setSelectedBufferRange([[2, 0], [2, 1]]); + const [selection] = editor.getSelections(); + assert.isTrue(handler.called); + handler.resetHistory(); + + selection.setBufferRange([[2, 2], [2, 3]]); + assert.isTrue(handler.called); + const [payload] = handler.lastCall.args; + assert.deepEqual(payload.oldBufferRange.serialize(), [[2, 0], [2, 1]]); + assert.deepEqual(payload.oldScreenRange.serialize(), [[2, 0], [2, 1]]); + assert.deepEqual(payload.newBufferRange.serialize(), [[2, 2], [2, 3]]); + assert.deepEqual(payload.newScreenRange.serialize(), [[2, 2], [2, 3]]); + assert.strictEqual(payload.selection, selection); + }); + + it('triggers didDestroySelection when an existing selection is destroyed', function() { + mount( + , + ); + + const editor = refModel.get(); + editor.setSelectedBufferRanges([ + [[2, 0], [2, 1]], + [[3, 0], [3, 1]], + ]); + const selection1 = editor.getSelections()[1]; + assert.isFalse(handler.called); + + editor.setSelectedBufferRange([[1, 0], [1, 2]]); + assert.isTrue(handler.calledWith(selection1)); + }); + }); + + it('detects DOM node membership', function() { + const wrapper = mount( + , + ); + + const children = wrapper.find('div').getDOMNode().children; + assert.lengthOf(children, 1); + const child = children[0]; + const instance = wrapper.instance(); + + assert.isTrue(instance.contains(child)); + assert.isFalse(instance.contains(document.body)); + }); + + it('focuses its editor element', function() { + const wrapper = mount( + , + ); + + const children = wrapper.find('div').getDOMNode().children; + assert.lengthOf(children, 1); + const child = children[0]; + sinon.spy(child, 'focus'); + + const instance = wrapper.instance(); + instance.focus(); + assert.isTrue(child.focus.called); + }); +}); From 8f981fc99e5e7b9f110e883d3fb2f0f985564169 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 11 Sep 2018 11:33:56 -0400 Subject: [PATCH 0205/4053] :fire: dot only --- test/views/file-patch-view.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 25e381b9ac..6188d9accd 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -7,7 +7,7 @@ import {buildFilePatch} from '../../lib/models/patch'; import {nullFile} from '../../lib/models/patch/file'; import {nullFilePatch} from '../../lib/models/patch/file-patch'; -describe.only('FilePatchView', function() { +describe('FilePatchView', function() { let atomEnv, repository, filePatch; beforeEach(async function() { @@ -121,7 +121,7 @@ describe.only('FilePatchView', function() { assert.isFalse(selectedRowsChanged.called); }); - it.only('selects the next full hunk when a new file patch arrives in hunk selection mode', function() { + it('selects the next full hunk when a new file patch arrives in hunk selection mode', function() { const multiHunkPatch = buildFilePatch([{ oldPath: 'path.txt', oldMode: '100644', From abcc686cecb0f7823f427602c0ea0922c45ccc5a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 11 Sep 2018 14:35:15 -0400 Subject: [PATCH 0206/4053] Store Patch text as a TextBuffer --- lib/models/patch/patch.js | 52 +++++++++-------- test/helpers.js | 2 +- test/models/patch/patch.test.js | 98 +++++++++++++++++---------------- 3 files changed, 80 insertions(+), 72 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index fc5d915c1e..5de54972cc 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -1,10 +1,12 @@ +import {TextBuffer} from 'atom'; + import Hunk from './hunk'; import {Addition, Deletion, NoNewline} from './region'; class BufferBuilder { constructor(original) { - this.originalBufferText = original; - this.bufferText = ''; + this.originalBuffer = original; + this.buffer = new TextBuffer(); this.positionOffset = 0; this.rowOffset = 0; @@ -17,7 +19,7 @@ class BufferBuilder { } append(rowRange) { - this.hunkBufferText += this.originalBufferText.slice(rowRange.startOffset, rowRange.endOffset); + this.hunkBufferText += this.originalBuffer.getTextInRange(rowRange.bufferRange) + '\n'; this.hunkRowCount += rowRange.bufferRowCount(); } @@ -27,7 +29,7 @@ class BufferBuilder { } latestHunkWasIncluded() { - this.bufferText += this.hunkBufferText; + this.buffer.append(this.hunkBufferText); this.hunkBufferText = ''; this.hunkRowCount = 0; @@ -56,16 +58,16 @@ class BufferBuilder { ); } - getBufferText() { - return this.bufferText; + getBuffer() { + return this.buffer; } } export default class Patch { - constructor({status, hunks, bufferText}) { + constructor({status, hunks, buffer}) { this.status = status; this.hunks = hunks; - this.bufferText = bufferText; + this.buffer = buffer; this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); } @@ -78,12 +80,12 @@ export default class Patch { return this.hunks; } - getBufferText() { - return this.bufferText; + getBuffer() { + return this.buffer; } getByteSize() { - return Buffer.byteLength(this.bufferText, 'utf8'); + return Buffer.byteLength(this.buffer.getText(), 'utf8'); } getChangedLineCount() { @@ -99,12 +101,12 @@ export default class Patch { return new this.constructor({ status: opts.status !== undefined ? opts.status : this.getStatus(), hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), - bufferText: opts.bufferText !== undefined ? opts.bufferText : this.getBufferText(), + buffer: opts.buffer !== undefined ? opts.buffer : this.getBuffer(), }); } getStagePatchForLines(rowSet) { - const builder = new BufferBuilder(this.getBufferText()); + const builder = new BufferBuilder(this.getBuffer()); const hunks = []; let newRowDelta = 0; @@ -116,7 +118,8 @@ export default class Patch { let noNewlineRowCount = 0; for (const region of hunk.getRegions()) { - for (const {intersection, gap} of region.getRowRange().intersectRowsIn(rowSet, this.getBufferText(), true)) { + const intersections = region.getRowRange().intersectRowsIn(rowSet, this.getBuffer().getText(), true); + for (const {intersection, gap} of intersections) { region.when({ addition: () => { if (gap) { @@ -190,11 +193,11 @@ export default class Patch { const wholeFile = rowSet.size === this.changedLineCount; const status = this.getStatus() === 'deleted' && !wholeFile ? 'modified' : this.getStatus(); - return this.clone({hunks, status, bufferText: builder.getBufferText()}); + return this.clone({hunks, status, buffer: builder.getBuffer()}); } getUnstagePatchForLines(rowSet) { - const builder = new BufferBuilder(this.getBufferText()); + const builder = new BufferBuilder(this.getBuffer()); const hunks = []; let newRowDelta = 0; @@ -206,7 +209,8 @@ export default class Patch { let deletionRowCount = 0; for (const region of hunk.getRegions()) { - for (const {intersection, gap} of region.getRowRange().intersectRowsIn(rowSet, this.getBufferText(), true)) { + const intersections = region.getRowRange().intersectRowsIn(rowSet, this.getBuffer().getText(), true); + for (const {intersection, gap} of intersections) { region.when({ addition: () => { if (gap) { @@ -282,7 +286,7 @@ export default class Patch { status = wholeFile ? 'deleted' : 'modified'; } - return this.clone({hunks, status, bufferText: builder.getBufferText()}); + return this.clone({hunks, status, buffer: builder.getBuffer()}); } getFullUnstagedPatch() { @@ -332,7 +336,7 @@ export default class Patch { let hunkSelectionOffset = 0; changeLoop: for (const change of hunk.getChanges()) { - const intersections = change.getRowRange().intersectRowsIn(lastSelectedRows, this.getBufferText(), true); + const intersections = change.getRowRange().intersectRowsIn(lastSelectedRows, this.getBuffer().getText(), true); for (const {intersection, gap} of intersections) { // Only include a partial range if this intersection includes the last selected buffer row. includesMax = intersection.includesRow(lastMax); @@ -373,7 +377,7 @@ export default class Patch { toString() { return this.getHunks().reduce((str, hunk) => { - str += hunk.toStringIn(this.getBufferText()); + str += hunk.toStringIn(this.getBuffer().getText()); return str; }, ''); } @@ -392,8 +396,8 @@ export const nullPatch = { return []; }, - getBufferText() { - return ''; + getBuffer() { + return new TextBuffer(); }, getByteSize() { @@ -405,13 +409,13 @@ export const nullPatch = { }, clone(opts = {}) { - if (opts.status === undefined && opts.hunks === undefined && opts.bufferText === undefined) { + if (opts.status === undefined && opts.hunks === undefined && opts.buffer === undefined) { return this; } else { return new Patch({ status: opts.status !== undefined ? opts.status : this.getStatus(), hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), - bufferText: opts.bufferText !== undefined ? opts.bufferText : this.getBufferText(), + buffer: opts.buffer !== undefined ? opts.buffer : this.getBuffer(), }); } }, diff --git a/test/helpers.js b/test/helpers.js index ea359743a4..29e52db3d8 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -184,7 +184,7 @@ class PatchBufferAssertions { const spec = changes[i]; assert.strictEqual(change.constructor.name.toLowerCase(), spec.kind); - assert.strictEqual(change.toStringIn(this.patch.getBufferText()), spec.string); + assert.strictEqual(change.toStringIn(this.patch.getBuffer().getText()), spec.string); assert.deepEqual(change.getRowRange().bufferRange.serialize(), spec.range); } } diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 00f115e09c..06b94f3917 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -1,3 +1,5 @@ +import {TextBuffer} from 'atom'; + import Patch, {nullPatch} from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; @@ -5,15 +7,15 @@ import {assertInPatch, buildRange} from '../../helpers'; describe('Patch', function() { it('has some standard accessors', function() { - const p = new Patch({status: 'modified', hunks: [], bufferText: 'bufferText'}); + const p = new Patch({status: 'modified', hunks: [], buffer: new TextBuffer({text: 'bufferText'})}); assert.strictEqual(p.getStatus(), 'modified'); assert.deepEqual(p.getHunks(), []); - assert.strictEqual(p.getBufferText(), 'bufferText'); + assert.strictEqual(p.getBuffer().getText(), 'bufferText'); assert.isTrue(p.isPresent()); }); it('computes the byte size of the total patch data', function() { - const p = new Patch({status: 'modified', hunks: [], bufferText: '\u00bd + \u00bc = \u00be'}); + const p = new Patch({status: 'modified', hunks: [], buffer: new TextBuffer({text: '\u00bd + \u00bc = \u00be'})}); assert.strictEqual(p.getByteSize(), 12); }); @@ -39,7 +41,7 @@ describe('Patch', function() { ], }), ]; - const p = new Patch({status: 'modified', hunks, bufferText: 'bufferText'}); + const p = new Patch({status: 'modified', hunks, buffer: new TextBuffer({text: 'bufferText'})}); assert.strictEqual(p.getChangedLineCount(), 10); }); @@ -62,40 +64,40 @@ describe('Patch', function() { changes: [], }), ]; - const p0 = new Patch({status: 'modified', hunks, bufferText: 'bufferText'}); + const p0 = new Patch({status: 'modified', hunks, buffer: new TextBuffer({text: 'bufferText'})}); assert.strictEqual(p0.getMaxLineNumberWidth(), 3); - const p1 = new Patch({status: 'deleted', hunks: [], bufferText: ''}); + const p1 = new Patch({status: 'deleted', hunks: [], buffer: new TextBuffer({text: ''})}); assert.strictEqual(p1.getMaxLineNumberWidth(), 0); }); it('clones itself with optionally overridden properties', function() { - const original = new Patch({status: 'modified', hunks: [], bufferText: 'bufferText'}); + const original = new Patch({status: 'modified', hunks: [], buffer: new TextBuffer({text: 'bufferText'})}); const dup0 = original.clone(); assert.notStrictEqual(dup0, original); assert.strictEqual(dup0.getStatus(), 'modified'); assert.deepEqual(dup0.getHunks(), []); - assert.strictEqual(dup0.getBufferText(), 'bufferText'); + assert.strictEqual(dup0.getBuffer().getText(), 'bufferText'); const dup1 = original.clone({status: 'added'}); assert.notStrictEqual(dup1, original); assert.strictEqual(dup1.getStatus(), 'added'); assert.deepEqual(dup1.getHunks(), []); - assert.strictEqual(dup1.getBufferText(), 'bufferText'); + assert.strictEqual(dup1.getBuffer().getText(), 'bufferText'); const hunks = [new Hunk({changes: []})]; const dup2 = original.clone({hunks}); assert.notStrictEqual(dup2, original); assert.strictEqual(dup2.getStatus(), 'modified'); assert.deepEqual(dup2.getHunks(), hunks); - assert.strictEqual(dup2.getBufferText(), 'bufferText'); + assert.strictEqual(dup2.getBuffer().getText(), 'bufferText'); - const dup3 = original.clone({bufferText: 'changed'}); + const dup3 = original.clone({buffer: new TextBuffer({text: 'changed'})}); assert.notStrictEqual(dup3, original); assert.strictEqual(dup3.getStatus(), 'modified'); assert.deepEqual(dup3.getHunks(), []); - assert.strictEqual(dup3.getBufferText(), 'changed'); + assert.strictEqual(dup3.getBuffer().getText(), 'changed'); }); it('clones a nullPatch as a nullPatch', function() { @@ -107,20 +109,20 @@ describe('Patch', function() { assert.notStrictEqual(dup0, nullPatch); assert.strictEqual(dup0.getStatus(), 'added'); assert.deepEqual(dup0.getHunks(), []); - assert.strictEqual(dup0.getBufferText(), ''); + assert.strictEqual(dup0.getBuffer().getText(), ''); const hunks = [new Hunk({changes: []})]; const dup1 = nullPatch.clone({hunks}); assert.notStrictEqual(dup1, nullPatch); assert.isNull(dup1.getStatus()); assert.deepEqual(dup1.getHunks(), hunks); - assert.strictEqual(dup1.getBufferText(), ''); + assert.strictEqual(dup1.getBuffer().getText(), ''); - const dup2 = nullPatch.clone({bufferText: 'changed'}); + const dup2 = nullPatch.clone({buffer: new TextBuffer({text: 'changed'})}); assert.notStrictEqual(dup2, nullPatch); assert.isNull(dup2.getStatus()); assert.deepEqual(dup2.getHunks(), []); - assert.strictEqual(dup2.getBufferText(), 'changed'); + assert.strictEqual(dup2.getBuffer().getText(), 'changed'); }); describe('stage patch generation', function() { @@ -129,7 +131,7 @@ describe('Patch', function() { const stagePatch = patch.getStagePatchForLines(new Set([8, 13, 14, 16])); // buffer rows: 0 1 2 3 4 5 6 7 8 9 const expectedBufferText = '0007\n0008\n0010\n0011\n0012\n0013\n0014\n0015\n0016\n0018\n'; - assert.strictEqual(stagePatch.getBufferText(), expectedBufferText); + assert.strictEqual(stagePatch.getBuffer().getText(), expectedBufferText); assertInPatch(stagePatch).hunks( { startRow: 0, @@ -155,7 +157,7 @@ describe('Patch', function() { '0007\n0010\n0011\n0012\n0013\n0014\n0015\n0016\n0017\n0018\n' + // 15 16 17 '0024\n0025\n No newline at end of file\n'; - assert.strictEqual(stagePatch.getBufferText(), expectedBufferText); + assert.strictEqual(stagePatch.getBuffer().getText(), expectedBufferText); assertInPatch(stagePatch).hunks( { startRow: 0, @@ -188,8 +190,7 @@ describe('Patch', function() { }); it('returns a modification patch if original patch is a deletion', function() { - const bufferText = 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\n'; - + const buffer = new TextBuffer({text: 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\n'}); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 5, newStartRow: 1, newRowCount: 0, @@ -201,7 +202,7 @@ describe('Patch', function() { }), ]; - const patch = new Patch({status: 'deleted', hunks, bufferText}); + const patch = new Patch({status: 'deleted', hunks, buffer}); const stagedPatch = patch.getStagePatchForLines(new Set([1, 3, 4])); assert.strictEqual(stagedPatch.getStatus(), 'modified'); @@ -219,7 +220,7 @@ describe('Patch', function() { }); it('returns an deletion when staging an entire deletion patch', function() { - const bufferText = '0000\n0001\n0002\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 0, @@ -229,7 +230,7 @@ describe('Patch', function() { ], }), ]; - const patch = new Patch({status: 'deleted', hunks, bufferText}); + const patch = new Patch({status: 'deleted', hunks, buffer}); const unstagePatch0 = patch.getUnstagePatchForLines(new Set([0, 1, 2])); assert.strictEqual(unstagePatch0.getStatus(), 'deleted'); @@ -245,7 +246,7 @@ describe('Patch', function() { const patch = buildPatchFixture(); const unstagePatch = patch.getUnstagePatchForLines(new Set([8, 12, 13])); assert.strictEqual( - unstagePatch.getBufferText(), + unstagePatch.getBuffer().getText(), // 0 1 2 3 4 5 6 7 8 '0007\n0008\n0009\n0010\n0011\n0012\n0013\n0017\n0018\n', ); @@ -266,7 +267,7 @@ describe('Patch', function() { const patch = buildPatchFixture(); const unstagePatch = patch.getUnstagePatchForLines(new Set([1, 4, 5, 16, 17, 20, 25])); assert.strictEqual( - unstagePatch.getBufferText(), + unstagePatch.getBuffer().getText(), // 0 1 2 3 4 5 '0000\n0001\n0003\n0004\n0005\n0006\n' + // 6 7 8 9 10 11 12 13 @@ -319,7 +320,7 @@ describe('Patch', function() { const patch = buildPatchFixture(); const unstagedPatch = patch.getFullUnstagedPatch(); - assert.strictEqual(unstagedPatch.getBufferText(), patch.getBufferText()); + assert.strictEqual(unstagedPatch.getBuffer().getText(), patch.getBuffer().getText()); assertInPatch(unstagedPatch).hunks( { startRow: 0, @@ -362,7 +363,7 @@ describe('Patch', function() { }); it('returns a modification if original patch is an addition', function() { - const bufferText = '0000\n0001\n0002\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 3, @@ -372,10 +373,10 @@ describe('Patch', function() { ], }), ]; - const patch = new Patch({status: 'added', hunks, bufferText}); + const patch = new Patch({status: 'added', hunks, buffer}); const unstagePatch = patch.getUnstagePatchForLines(new Set([1, 2])); assert.strictEqual(unstagePatch.getStatus(), 'modified'); - assert.strictEqual(unstagePatch.getBufferText(), '0000\n0001\n0002\n'); + assert.strictEqual(unstagePatch.getBuffer().getText(), '0000\n0001\n0002\n'); assertInPatch(unstagePatch).hunks( { startRow: 0, @@ -389,7 +390,7 @@ describe('Patch', function() { }); it('returns a deletion when unstaging an entire addition patch', function() { - const bufferText = '0000\n0001\n0002\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); const hunks = [ new Hunk({ oldStartRow: 1, @@ -402,7 +403,7 @@ describe('Patch', function() { ], }), ]; - const patch = new Patch({status: 'added', hunks, bufferText}); + const patch = new Patch({status: 'added', hunks, buffer}); const unstagePatch0 = patch.getUnstagePatchForLines(new Set([0, 1, 2])); assert.strictEqual(unstagePatch0.getStatus(), 'deleted'); @@ -431,12 +432,12 @@ describe('Patch', function() { changes: [], }), ]; - const patch = new Patch({status: 'modified', hunks, bufferText: ''}); + const patch = new Patch({status: 'modified', hunks, buffer: new TextBuffer({text: ''})}); assert.deepEqual(patch.getFirstChangeRange(), [[0, 0], [0, 0]]); }); it('returns the origin if the patch is empty', function() { - const patch = new Patch({status: 'modified', hunks: [], bufferText: ''}); + const patch = new Patch({status: 'modified', hunks: [], buffer: new TextBuffer({text: ''})}); assert.deepEqual(patch.getFirstChangeRange(), [[0, 0], [0, 0]]); }); }); @@ -450,7 +451,7 @@ describe('Patch', function() { // nothing in hunks 2 or 3 const lastSelectedRows = new Set([1, 2, 4, 5, 13]); - const nBufferText = + const nBuffer = new TextBuffer({text: // 0 1 2 3 4 '0000\n0003\n0004\n0005\n0006\n' + // 5 6 7 8 9 10 11 12 13 14 15 @@ -458,7 +459,8 @@ describe('Patch', function() { // 16 17 18 19 20 '0019\n0020\n0021\n0022\n0023\n' + // 21 22 23 - '0024\n0025\n No newline at end of file\n'; + '0024\n0025\n No newline at end of file\n', + }); const nHunks = [ new Hunk({ oldStartRow: 3, oldRowCount: 3, newStartRow: 3, newRowCount: 5, // next row drift = +2 @@ -494,7 +496,7 @@ describe('Patch', function() { ], }), ]; - const nextPatch = new Patch({status: 'modified', hunks: nHunks, bufferText: nBufferText}); + const nextPatch = new Patch({status: 'modified', hunks: nHunks, buffer: nBuffer}); const nextRange = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); // Original buffer row 14 = the next changed row = new buffer row 11 @@ -522,7 +524,7 @@ describe('Patch', function() { ], }), ], - bufferText: '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n0010\n0011\n', + buffer: new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n0010\n0011\n'}), }); // Select: // * all changes from hunk 0 @@ -541,7 +543,7 @@ describe('Patch', function() { ], }), ], - bufferText: '0006\n0007\n0008\n0009\n0010\n0011\n', + buffer: new TextBuffer({text: '0006\n0007\n0008\n0009\n0010\n0011\n'}), }); const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); @@ -572,7 +574,7 @@ describe('Patch', function() { }); it('prints itself as an apply-ready string', function() { - const bufferText = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999\n'; + const buffer = new TextBuffer({text: '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999\n'}); // old: 0000.2222.3333.4444.5555.6666.7777.8888.9999. // new: 0000.1111.2222.3333.4444.5555.6666.9999. // patch buffer: 0000.1111.2222.3333.4444.5555.6666.7777.8888.9999. @@ -595,7 +597,7 @@ describe('Patch', function() { ], }); - const p = new Patch({status: 'modified', hunks: [hunk0, hunk1], bufferText}); + const p = new Patch({status: 'modified', hunks: [hunk0, hunk1], buffer}); assert.strictEqual(p.toString(), [ '@@ -0,2 +0,3 @@\n', @@ -613,7 +615,7 @@ describe('Patch', function() { it('has a stubbed nullPatch counterpart', function() { assert.isNull(nullPatch.getStatus()); assert.deepEqual(nullPatch.getHunks(), []); - assert.strictEqual(nullPatch.getBufferText(), ''); + assert.strictEqual(nullPatch.getBuffer().getText(), ''); assert.strictEqual(nullPatch.getByteSize(), 0); assert.isFalse(nullPatch.isPresent()); assert.strictEqual(nullPatch.toString(), ''); @@ -625,11 +627,13 @@ describe('Patch', function() { }); function buildPatchFixture() { - const bufferText = - '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n' + - '0010\n0011\n0012\n0013\n0014\n0015\n0016\n0017\n0018\n0019\n' + - '0020\n0021\n0022\n0023\n0024\n0025\n' + - ' No newline at end of file\n'; + const buffer = new TextBuffer({ + text: + '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n' + + '0010\n0011\n0012\n0013\n0014\n0015\n0016\n0017\n0018\n0019\n' + + '0020\n0021\n0022\n0023\n0024\n0025\n' + + ' No newline at end of file\n', + }); const hunks = [ new Hunk({ @@ -671,5 +675,5 @@ function buildPatchFixture() { }), ]; - return new Patch({status: 'modified', hunks, bufferText}); + return new Patch({status: 'modified', hunks, buffer}); } From 61d49ce3c366c054c230cedaab5018219ede8da6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 11 Sep 2018 14:49:43 -0400 Subject: [PATCH 0207/4053] Store Ranges directly within Regions --- lib/models/patch/region.js | 18 +++++++++--------- test/models/patch/region.test.js | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index 233a7136cf..144439be44 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -3,20 +3,20 @@ class Region { this.range = range; } - getRowRange() { + getRange() { return this.range; } getStartBufferRow() { - return this.range.getStartBufferRow(); + return this.range.start.row; } getEndBufferRow() { - return this.range.getEndBufferRow(); + return this.range.end.row; } includesBufferRow(row) { - return this.range.includesRow(row); + return this.range.intersectsRow(row); } isAddition() { @@ -36,11 +36,11 @@ class Region { } getBufferRows() { - return this.range.getBufferRows(); + return this.range.getRows(); } bufferRowCount() { - return this.range.bufferRowCount(); + return this.range.getRowCount(); } when(callbacks) { @@ -49,7 +49,7 @@ class Region { } toStringIn(buffer) { - return this.range.toStringIn(buffer, this.constructor.origin); + return buffer.getTextInRange(this.range).replace(/(^|\n)(?!$)/g, '$&' + this.constructor.origin); } invert() { @@ -69,7 +69,7 @@ export class Addition extends Region { } invert() { - return new Deletion(this.getRowRange()); + return new Deletion(this.getRange()); } } @@ -81,7 +81,7 @@ export class Deletion extends Region { } invert() { - return new Addition(this.getRowRange()); + return new Addition(this.getRange()); } } diff --git a/test/models/patch/region.test.js b/test/models/patch/region.test.js index bc1ab8757c..c8a0b06849 100644 --- a/test/models/patch/region.test.js +++ b/test/models/patch/region.test.js @@ -1,12 +1,12 @@ +import {TextBuffer, Range} from 'atom'; import {Addition, Deletion, NoNewline, Unchanged} from '../../../lib/models/patch/region'; -import IndexedRowRange from '../../../lib/models/indexed-row-range'; describe('Regions', function() { let buffer, range; beforeEach(function() { - buffer = '0000\n1111\n2222\n3333\n4444\n5555\n'; - range = new IndexedRowRange({bufferRange: [[1, 0], [3, Infinity]], startOffset: 5, endOffset: 20}); + buffer = new TextBuffer({text: '0000\n1111\n2222\n3333\n4444\n5555\n'}); + range = Range.fromObject([[1, 0], [3, Infinity]]); }); describe('Addition', function() { @@ -17,7 +17,7 @@ describe('Regions', function() { }); it('has range accessors', function() { - assert.strictEqual(addition.getRowRange(), range); + assert.strictEqual(addition.getRange(), range); assert.strictEqual(addition.getStartBufferRow(), 1); assert.strictEqual(addition.getEndBufferRow(), 3); }); @@ -68,13 +68,13 @@ describe('Regions', function() { }); it('uses "+" as a prefix for toStringIn()', function() { - assert.strictEqual(addition.toStringIn(buffer), '+1111\n+2222\n+3333\n'); + assert.strictEqual(addition.toStringIn(buffer), '+1111\n+2222\n+3333'); }); it('inverts to a deletion', function() { const inverted = addition.invert(); assert.isTrue(inverted.isDeletion()); - assert.strictEqual(inverted.getRowRange(), addition.getRowRange()); + assert.strictEqual(inverted.getRange(), addition.getRange()); }); }); @@ -125,13 +125,13 @@ describe('Regions', function() { }); it('uses "-" as a prefix for toStringIn()', function() { - assert.strictEqual(deletion.toStringIn(buffer), '-1111\n-2222\n-3333\n'); + assert.strictEqual(deletion.toStringIn(buffer), '-1111\n-2222\n-3333'); }); it('inverts to an addition', function() { const inverted = deletion.invert(); assert.isTrue(inverted.isAddition()); - assert.strictEqual(inverted.getRowRange(), deletion.getRowRange()); + assert.strictEqual(inverted.getRange(), deletion.getRange()); }); }); @@ -182,7 +182,7 @@ describe('Regions', function() { }); it('uses " " as a prefix for toStringIn()', function() { - assert.strictEqual(unchanged.toStringIn(buffer), ' 1111\n 2222\n 3333\n'); + assert.strictEqual(unchanged.toStringIn(buffer), ' 1111\n 2222\n 3333'); }); it('inverts as itself', function() { @@ -237,7 +237,7 @@ describe('Regions', function() { }); it('uses "\\" as a prefix for toStringIn()', function() { - assert.strictEqual(noNewline.toStringIn(buffer), '\\1111\n\\2222\n\\3333\n'); + assert.strictEqual(noNewline.toStringIn(buffer), '\\1111\n\\2222\n\\3333'); }); it('inverts as itself', function() { From b64568d4d404236d778448e53e361311c78eea80 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 11 Sep 2018 15:18:37 -0400 Subject: [PATCH 0208/4053] Move intersectRowsIn() to Region --- lib/models/patch/region.js | 45 ++++++++++++++++++++ test/models/patch/region.test.js | 73 ++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index 144439be44..442592b2d1 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -1,3 +1,5 @@ +import {Range} from 'atom'; + class Region { constructor(range) { this.range = range; @@ -19,6 +21,49 @@ class Region { return this.range.intersectsRow(row); } + intersectRows(rowSet, includeGaps) { + const intersections = []; + let withinIntersection = false; + + let currentRow = this.range.start.row; + let nextStartRow = currentRow; + + const finishRowRange = isGap => { + if (isGap && !includeGaps) { + nextStartRow = currentRow; + return; + } + + if (currentRow <= this.range.start.row) { + return; + } + + intersections.push({ + intersection: Range.fromObject([[nextStartRow, 0], [currentRow - 1, Infinity]]), + gap: isGap, + }); + + nextStartRow = currentRow; + }; + + while (currentRow <= this.range.end.row) { + if (rowSet.has(currentRow) && !withinIntersection) { + // One row past the end of a gap. Start of intersecting row range. + finishRowRange(true); + withinIntersection = true; + } else if (!rowSet.has(currentRow) && withinIntersection) { + // One row past the end of intersecting row range. Start of the next gap. + finishRowRange(false); + withinIntersection = false; + } + + currentRow++; + } + + finishRowRange(!withinIntersection); + return intersections; + } + isAddition() { return false; } diff --git a/test/models/patch/region.test.js b/test/models/patch/region.test.js index c8a0b06849..bc37cb08ca 100644 --- a/test/models/patch/region.test.js +++ b/test/models/patch/region.test.js @@ -244,4 +244,77 @@ describe('Regions', function() { assert.strictEqual(noNewline.invert(), noNewline); }); }); + + describe('intersectRows()', function() { + function assertIntersections(actual, expected) { + const serialized = actual.map(({intersection, gap}) => ({intersection: intersection.serialize(), gap})); + assert.deepEqual(serialized, expected); + } + + it('returns an array containing all gaps with no intersection rows', function() { + const region = new Addition(Range.fromObject([[1, 0], [3, Infinity]])); + + assertIntersections(region.intersectRows(new Set([0, 5, 6]), false), []); + assertIntersections(region.intersectRows(new Set([0, 5, 6]), true), [ + {intersection: [[1, 0], [3, Infinity]], gap: true}, + ]); + }); + + it('detects an intersection at the beginning of the range', function() { + const region = new Deletion(Range.fromObject([[2, 0], [6, Infinity]])); + const rowSet = new Set([0, 1, 2, 3]); + + assertIntersections(region.intersectRows(rowSet, false), [ + {intersection: [[2, 0], [3, Infinity]], gap: false}, + ]); + assertIntersections(region.intersectRows(rowSet, true), [ + {intersection: [[2, 0], [3, Infinity]], gap: false}, + {intersection: [[4, 0], [6, Infinity]], gap: true}, + ]); + }); + + it('detects an intersection in the middle of the range', function() { + const region = new Unchanged(Range.fromObject([[2, 0], [6, Infinity]])); + const rowSet = new Set([0, 3, 4, 8, 9]); + + assertIntersections(region.intersectRows(rowSet, false), [ + {intersection: [[3, 0], [4, Infinity]], gap: false}, + ]); + assertIntersections(region.intersectRows(rowSet, true), [ + {intersection: [[2, 0], [2, Infinity]], gap: true}, + {intersection: [[3, 0], [4, Infinity]], gap: false}, + {intersection: [[5, 0], [6, Infinity]], gap: true}, + ]); + }); + + it('detects an intersection at the end of the range', function() { + const region = new Addition(Range.fromObject([[2, 0], [6, Infinity]])); + const rowSet = new Set([4, 5, 6, 7, 10, 11]); + + assertIntersections(region.intersectRows(rowSet, false), [ + {intersection: [[4, 0], [6, Infinity]], gap: false}, + ]); + assertIntersections(region.intersectRows(rowSet, true), [ + {intersection: [[2, 0], [3, Infinity]], gap: true}, + {intersection: [[4, 0], [6, Infinity]], gap: false}, + ]); + }); + + it('detects multiple intersections', function() { + const region = new Deletion(Range.fromObject([[2, 0], [8, Infinity]])); + const rowSet = new Set([0, 3, 4, 6, 7, 10]); + + assertIntersections(region.intersectRows(rowSet, false), [ + {intersection: [[3, 0], [4, Infinity]], gap: false}, + {intersection: [[6, 0], [7, Infinity]], gap: false}, + ]); + assertIntersections(region.intersectRows(rowSet, true), [ + {intersection: [[2, 0], [2, Infinity]], gap: true}, + {intersection: [[3, 0], [4, Infinity]], gap: false}, + {intersection: [[5, 0], [5, Infinity]], gap: true}, + {intersection: [[6, 0], [7, Infinity]], gap: false}, + {intersection: [[8, 0], [8, Infinity]], gap: true}, + ]); + }); + }); }); From e204ef24cc43f7f2c2ba09a5ff81df5f42d01296 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 11 Sep 2018 15:45:18 -0400 Subject: [PATCH 0209/4053] Adjust Hunk to store Ranges instead of IndexedRowRanges --- lib/models/patch/hunk.js | 61 +++++------- test/models/patch/hunk.test.js | 169 +++++++++++++-------------------- 2 files changed, 93 insertions(+), 137 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 7ca5697d86..5788732ca8 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -1,4 +1,4 @@ -import IndexedRowRange from '../indexed-row-range'; +import {Range} from 'atom'; import {Unchanged} from './region'; export default class Hunk { @@ -17,7 +17,7 @@ export default class Hunk { this.newRowCount = newRowCount; this.sectionHeading = sectionHeading; - this.rowRange = rowRange; + this.range = rowRange; this.changes = changes; } @@ -51,83 +51,71 @@ export default class Hunk { getRegions() { const regions = []; - let currentRow = this.rowRange.bufferRange.start.row; - let currentPosition = this.rowRange.startOffset; + let currentRow = this.range.start.row; for (const change of this.changes) { - const startRow = change.getRowRange().bufferRange.start.row; - const startPosition = change.getRowRange().startOffset; + const startRow = change.getRange().start.row; if (currentRow !== startRow) { - regions.push(new Unchanged(new IndexedRowRange({ - bufferRange: [[currentRow, 0], [startRow - 1, Infinity]], - startOffset: currentPosition, - endOffset: startPosition, - }))); + regions.push(new Unchanged( + Range.fromObject([[currentRow, 0], [startRow - 1, Infinity]]), + )); } regions.push(change); - currentRow = change.getRowRange().bufferRange.end.row + 1; - currentPosition = change.getRowRange().endOffset; + currentRow = change.getRange().end.row + 1; } - const endRow = this.rowRange.bufferRange.end.row; - const endPosition = this.rowRange.endOffset; + const endRow = this.range.end.row; if (currentRow <= endRow) { - regions.push(new Unchanged(new IndexedRowRange({ - bufferRange: [[currentRow, 0], [endRow, this.rowRange.bufferRange.end.column]], - startOffset: currentPosition, - endOffset: endPosition, - }))); + regions.push(new Unchanged( + Range.fromObject([[currentRow, 0], [endRow, this.range.end.column]]), + )); } return regions; } getAdditionRanges() { - return this.changes.filter(change => change.isAddition()).map(change => change.getRowRange().bufferRange); + return this.changes.filter(change => change.isAddition()).map(change => change.getRange()); } getDeletionRanges() { - return this.changes.filter(change => change.isDeletion()).map(change => change.getRowRange().bufferRange); + return this.changes.filter(change => change.isDeletion()).map(change => change.getRange()); } getNoNewlineRange() { const lastChange = this.changes[this.changes.length - 1]; if (lastChange && lastChange.isNoNewline()) { - return lastChange.getRowRange().bufferRange; + return lastChange.getRange(); } else { return null; } } - getRowRange() { - return this.rowRange; - } - - getBufferRange() { - return this.rowRange.bufferRange; + getRange() { + return this.range; } getBufferRows() { - return this.rowRange.getBufferRows(); + return this.range.getRows(); } bufferRowCount() { - return this.rowRange.bufferRowCount(); + return this.range.getRowCount(); } includesBufferRow(row) { - return this.rowRange.includesRow(row); + return this.range.intersectsRow(row); } getOldRowAt(row) { let current = this.oldStartRow; for (const region of this.getRegions()) { - if (region.range.includesRow(row)) { + if (region.includesBufferRow(row)) { const offset = row - region.getStartBufferRow(); return region.when({ @@ -185,14 +173,15 @@ export default class Hunk { changedLineCount() { return this.changes.reduce((count, change) => change.when({ nonewline: () => count, - default: () => count + change.getRowRange().bufferRowCount(), + default: () => count + change.bufferRowCount(), }), 0); } - toStringIn(bufferText) { + toStringIn(buffer) { let str = this.getHeader() + '\n'; for (const region of this.getRegions()) { - str += region.toStringIn(bufferText); + str += region.toStringIn(buffer); + str += '\n'; } return str; } diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 5f4509c71a..3fa7577c39 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -1,5 +1,6 @@ +import {TextBuffer, Range} from 'atom'; + import Hunk from '../../../lib/models/patch/hunk'; -import IndexedRowRange from '../../../lib/models/indexed-row-range'; import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; describe('Hunk', function() { @@ -9,15 +10,11 @@ describe('Hunk', function() { oldRowCount: 0, newRowCount: 0, sectionHeading: 'sectionHeading', - rowRange: new IndexedRowRange({ - bufferRange: [[1, 0], [10, Infinity]], - startOffset: 5, - endOffset: 100, - }), + rowRange: Range.fromObject([[1, 0], [10, Infinity]]), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, Infinity]], startOffset: 6, endOffset: 7})), - new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [4, Infinity]], startOffset: 8, endOffset: 9})), - new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, Infinity]], startOffset: 10, endOffset: 11})), + new Addition(Range.fromObject([[1, 0], [2, Infinity]])), + new Deletion(Range.fromObject([[3, 0], [4, Infinity]])), + new Deletion(Range.fromObject([[5, 0], [6, Infinity]])), ], }; @@ -28,15 +25,11 @@ describe('Hunk', function() { oldRowCount: 2, newRowCount: 3, sectionHeading: 'sectionHeading', - rowRange: new IndexedRowRange({ - bufferRange: [[0, 0], [10, Infinity]], - startOffset: 0, - endOffset: 100, - }), + rowRange: Range.fromObject([[0, 0], [10, Infinity]]), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, Infinity]], startOffset: 6, endOffset: 7})), - new Deletion(new IndexedRowRange({bufferRange: [[3, 0], [4, Infinity]], startOffset: 8, endOffset: 9})), - new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, Infinity]], startOffset: 10, endOffset: 11})), + new Addition(Range.fromObject([[1, 0], [2, Infinity]])), + new Deletion(Range.fromObject([[3, 0], [4, Infinity]])), + new Deletion(Range.fromObject([[5, 0], [6, Infinity]])), ], }); @@ -45,11 +38,7 @@ describe('Hunk', function() { assert.strictEqual(h.getOldRowCount(), 2); assert.strictEqual(h.getNewRowCount(), 3); assert.strictEqual(h.getSectionHeading(), 'sectionHeading'); - assert.deepEqual(h.getRowRange().serialize(), { - bufferRange: [[0, 0], [10, Infinity]], - startOffset: 0, - endOffset: 100, - }); + assert.deepEqual(h.getRange().serialize(), [[0, 0], [10, Infinity]]); assert.strictEqual(h.bufferRowCount(), 11); assert.lengthOf(h.getChanges(), 3); assert.lengthOf(h.getAdditionRanges(), 1); @@ -61,9 +50,9 @@ describe('Hunk', function() { const h = new Hunk({ ...attrs, changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, Infinity]], startOffset: 6, endOffset: 7})), - new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [5, Infinity]], startOffset: 8, endOffset: 9})), - new NoNewline(new IndexedRowRange({bufferRange: [[10, 0], [10, Infinity]], startOffset: 100, endOffset: 120})), + new Addition(Range.fromObject([[1, 0], [2, Infinity]])), + new Deletion(Range.fromObject([[4, 0], [5, Infinity]])), + new NoNewline(Range.fromObject([[10, 0], [10, Infinity]])), ], }); @@ -75,14 +64,10 @@ describe('Hunk', function() { it('creates its row range for decoration placement', function() { const h = new Hunk({ ...attrs, - rowRange: new IndexedRowRange({ - bufferRange: [[3, 0], [6, Infinity]], - startOffset: 15, - endOffset: 35, - }), + rowRange: Range.fromObject([[3, 0], [6, Infinity]]), }); - assert.deepEqual(h.getBufferRange().serialize(), [[3, 0], [6, Infinity]]); + assert.deepEqual(h.getRange().serialize(), [[3, 0], [6, Infinity]]); }); it('generates a patch section header', function() { @@ -100,15 +85,11 @@ describe('Hunk', function() { it('returns a full set of covered regions, including unchanged', function() { const h = new Hunk({ ...attrs, - rowRange: new IndexedRowRange({ - bufferRange: [[0, 0], [11, Infinity]], - startOffset: 0, - endOffset: 120, - }), + rowRange: Range.fromObject([[0, 0], [11, Infinity]]), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [3, Infinity]], startOffset: 10, endOffset: 40})), - new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, Infinity]], startOffset: 50, endOffset: 70})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, Infinity]], startOffset: 70, endOffset: 100})), + new Addition(Range.fromObject([[1, 0], [3, Infinity]])), + new Deletion(Range.fromObject([[5, 0], [6, Infinity]])), + new Deletion(Range.fromObject([[7, 0], [9, Infinity]])), ], }); @@ -116,36 +97,32 @@ describe('Hunk', function() { assert.lengthOf(regions, 6); assert.isTrue(regions[0].isUnchanged()); - assert.deepEqual(regions[0].range.serialize(), {bufferRange: [[0, 0], [0, Infinity]], startOffset: 0, endOffset: 10}); + assert.deepEqual(regions[0].range.serialize(), [[0, 0], [0, Infinity]]); assert.isTrue(regions[1].isAddition()); - assert.deepEqual(regions[1].range.serialize(), {bufferRange: [[1, 0], [3, Infinity]], startOffset: 10, endOffset: 40}); + assert.deepEqual(regions[1].range.serialize(), [[1, 0], [3, Infinity]]); assert.isTrue(regions[2].isUnchanged()); - assert.deepEqual(regions[2].range.serialize(), {bufferRange: [[4, 0], [4, Infinity]], startOffset: 40, endOffset: 50}); + assert.deepEqual(regions[2].range.serialize(), [[4, 0], [4, Infinity]]); assert.isTrue(regions[3].isDeletion()); - assert.deepEqual(regions[3].range.serialize(), {bufferRange: [[5, 0], [6, Infinity]], startOffset: 50, endOffset: 70}); + assert.deepEqual(regions[3].range.serialize(), [[5, 0], [6, Infinity]]); assert.isTrue(regions[4].isDeletion()); - assert.deepEqual(regions[4].range.serialize(), {bufferRange: [[7, 0], [9, Infinity]], startOffset: 70, endOffset: 100}); + assert.deepEqual(regions[4].range.serialize(), [[7, 0], [9, Infinity]]); assert.isTrue(regions[5].isUnchanged()); - assert.deepEqual(regions[5].range.serialize(), {bufferRange: [[10, 0], [11, Infinity]], startOffset: 100, endOffset: 120}); + assert.deepEqual(regions[5].range.serialize(), [[10, 0], [11, Infinity]]); }); it('omits empty regions at the hunk beginning and end', function() { const h = new Hunk({ ...attrs, - rowRange: new IndexedRowRange({ - bufferRange: [[1, 0], [9, 20]], - startOffset: 10, - endOffset: 100, - }), + rowRange: Range.fromObject([[1, 0], [9, 20]]), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [3, Infinity]], startOffset: 10, endOffset: 40})), - new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [6, Infinity]], startOffset: 50, endOffset: 70})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, 20]], startOffset: 70, endOffset: 100})), + new Addition(Range.fromObject([[1, 0], [3, Infinity]])), + new Deletion(Range.fromObject([[5, 0], [6, Infinity]])), + new Deletion(Range.fromObject([[7, 0], [9, 20]])), ], }); @@ -153,26 +130,22 @@ describe('Hunk', function() { assert.lengthOf(regions, 4); assert.isTrue(regions[0].isAddition()); - assert.deepEqual(regions[0].range.serialize(), {bufferRange: [[1, 0], [3, Infinity]], startOffset: 10, endOffset: 40}); + assert.deepEqual(regions[0].range.serialize(), [[1, 0], [3, Infinity]]); assert.isTrue(regions[1].isUnchanged()); - assert.deepEqual(regions[1].range.serialize(), {bufferRange: [[4, 0], [4, Infinity]], startOffset: 40, endOffset: 50}); + assert.deepEqual(regions[1].range.serialize(), [[4, 0], [4, Infinity]]); assert.isTrue(regions[2].isDeletion()); - assert.deepEqual(regions[2].range.serialize(), {bufferRange: [[5, 0], [6, Infinity]], startOffset: 50, endOffset: 70}); + assert.deepEqual(regions[2].range.serialize(), [[5, 0], [6, Infinity]]); assert.isTrue(regions[3].isDeletion()); - assert.deepEqual(regions[3].range.serialize(), {bufferRange: [[7, 0], [9, 20]], startOffset: 70, endOffset: 100}); + assert.deepEqual(regions[3].range.serialize(), [[7, 0], [9, 20]]); }); it('returns a set of covered buffer rows', function() { const h = new Hunk({ ...attrs, - rowRange: new IndexedRowRange({ - bufferRange: [[6, 0], [10, 60]], - startOffset: 30, - endOffset: 55, - }), + rowRange: Range.fromObject([[6, 0], [10, 60]]), }); assert.sameMembers(Array.from(h.getBufferRows()), [6, 7, 8, 9, 10]); }); @@ -180,11 +153,7 @@ describe('Hunk', function() { it('determines if a buffer row is part of this hunk', function() { const h = new Hunk({ ...attrs, - rowRange: new IndexedRowRange({ - bufferRange: [[3, 0], [5, Infinity]], - startOffset: 30, - endOffset: 55, - }), + rowRange: Range.fromObject([[3, 0], [5, Infinity]]), }); assert.isFalse(h.includesBufferRow(2)); @@ -201,12 +170,12 @@ describe('Hunk', function() { oldRowCount: 6, newStartRow: 20, newRowCount: 7, - rowRange: new IndexedRowRange({bufferRange: [[2, 0], [12, 10]], startOffset: 0, endOffset: 0}), + rowRange: Range.fromObject([[2, 0], [12, 10]]), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[3, 0], [5, Infinity]], startOffset: 0, endOffset: 0})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, Infinity]], startOffset: 0, endOffset: 0})), - new Addition(new IndexedRowRange({bufferRange: [[11, 0], [11, Infinity]], startOffset: 0, endOffset: 0})), - new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, Infinity]], startOffset: 0, endOffset: 0})), + new Addition(Range.fromObject([[3, 0], [5, Infinity]])), + new Deletion(Range.fromObject([[7, 0], [9, Infinity]])), + new Addition(Range.fromObject([[11, 0], [11, Infinity]])), + new NoNewline(Range.fromObject([[12, 0], [12, Infinity]])), ], }); @@ -231,12 +200,12 @@ describe('Hunk', function() { oldRowCount: 6, newStartRow: 20, newRowCount: 7, - rowRange: new IndexedRowRange({bufferRange: [[2, 0], [12, Infinity]], startOffset: 0, endOffset: 0}), + rowRange: Range.fromObject([[2, 0], [12, Infinity]]), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[3, 0], [5, Infinity]], startOffset: 0, endOffset: 0})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [9, Infinity]], startOffset: 0, endOffset: 0})), - new Addition(new IndexedRowRange({bufferRange: [[11, 0], [11, Infinity]], startOffset: 0, endOffset: 0})), - new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, Infinity]], startOffset: 0, endOffset: 0})), + new Addition(Range.fromObject([[3, 0], [5, Infinity]])), + new Deletion(Range.fromObject([[7, 0], [9, Infinity]])), + new Addition(Range.fromObject([[11, 0], [11, Infinity]])), + new NoNewline(Range.fromObject([[12, 0], [12, Infinity]])), ], }); @@ -258,10 +227,10 @@ describe('Hunk', function() { const h0 = new Hunk({ ...attrs, changes: [ - new Addition(new IndexedRowRange({bufferRange: [[2, 0], [4, Infinity]], startOffset: 0, endOffset: 0})), - new Addition(new IndexedRowRange({bufferRange: [[6, 0], [6, Infinity]], startOffset: 0, endOffset: 0})), - new Deletion(new IndexedRowRange({bufferRange: [[7, 0], [10, Infinity]], startOffset: 0, endOffset: 0})), - new NoNewline(new IndexedRowRange({bufferRange: [[12, 0], [12, Infinity]], startOffset: 0, endOffset: 0})), + new Addition(Range.fromObject([[2, 0], [4, Infinity]])), + new Addition(Range.fromObject([[6, 0], [6, Infinity]])), + new Deletion(Range.fromObject([[7, 0], [10, Infinity]])), + new NoNewline(Range.fromObject([[12, 0], [12, Infinity]])), ], }); assert.strictEqual(h0.changedLineCount(), 8); @@ -304,14 +273,16 @@ describe('Hunk', function() { changes: [], }); - assert.strictEqual(h.toStringIn(''), '@@ -0,2 +1,3 @@\n'); + assert.strictEqual(h.toStringIn(new TextBuffer()), '@@ -0,2 +1,3 @@\n\n'); }); it('renders changed and unchanged lines with the appropriate origin characters', function() { - const buffer = - '0000\n0111\n0222\n0333\n0444\n0555\n0666\n0777\n0888\n0999\n' + - '1000\n1111\n1222\n' + - ' No newline at end of file\n'; + const buffer = new TextBuffer({ + text: + '0000\n0111\n0222\n0333\n0444\n0555\n0666\n0777\n0888\n0999\n' + + '1000\n1111\n1222\n' + + ' No newline at end of file\n', + }); // 0000.0111.0222.0333.0444.0555.0666.0777.0888.0999.1000.1111.1222. No newline at end of file. const h = new Hunk({ @@ -320,18 +291,14 @@ describe('Hunk', function() { newStartRow: 1, oldRowCount: 6, newRowCount: 6, - rowRange: new IndexedRowRange({ - bufferRange: [[1, 0], [13, Infinity]], - startOffset: 5, - endOffset: 91, - }), + rowRange: Range.fromObject([[1, 0], [13, Infinity]]), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[2, 0], [3, Infinity]], startOffset: 10, endOffset: 20})), - new Deletion(new IndexedRowRange({bufferRange: [[5, 0], [5, Infinity]], startOffset: 25, endOffset: 30})), - new Addition(new IndexedRowRange({bufferRange: [[7, 0], [7, Infinity]], startOffset: 35, endOffset: 40})), - new Deletion(new IndexedRowRange({bufferRange: [[8, 0], [9, Infinity]], startOffset: 40, endOffset: 50})), - new Addition(new IndexedRowRange({bufferRange: [[10, 0], [10, Infinity]], startOffset: 50, endOffset: 55})), - new NoNewline(new IndexedRowRange({bufferRange: [[13, 0], [13, Infinity]], startOffset: 65, endOffset: 92})), + new Addition(Range.fromObject([[2, 0], [3, Infinity]])), + new Deletion(Range.fromObject([[5, 0], [5, Infinity]])), + new Addition(Range.fromObject([[7, 0], [7, Infinity]])), + new Deletion(Range.fromObject([[8, 0], [9, Infinity]])), + new Addition(Range.fromObject([[10, 0], [10, Infinity]])), + new NoNewline(Range.fromObject([[13, 0], [13, Infinity]])), ], }); @@ -354,7 +321,7 @@ describe('Hunk', function() { }); it('renders a hunk without a nonewline', function() { - const buffer = '0000\n1111\n2222\n3333\n4444\n'; + const buffer = new TextBuffer({text: '0000\n1111\n2222\n3333\n4444\n'}); const h = new Hunk({ ...attrs, @@ -362,10 +329,10 @@ describe('Hunk', function() { newStartRow: 1, oldRowCount: 1, newRowCount: 1, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [3, Infinity]], startOffset: 0, endOffset: 20}), + rowRange: Range.fromObject([[0, 0], [3, Infinity]]), changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, Infinity]], startOffset: 5, endOffset: 10})), - new Deletion(new IndexedRowRange({bufferRange: [[2, 0], [2, Infinity]], startOffset: 10, endOffset: 15})), + new Addition(Range.fromObject([[1, 0], [1, Infinity]])), + new Deletion(Range.fromObject([[2, 0], [2, Infinity]])), ], }); From 7179d06842aff6ebdd05f354607b564433c6e286 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 12 Sep 2018 09:51:16 -0400 Subject: [PATCH 0210/4053] Range :point_right: Marker in Regions --- lib/models/patch/region.js | 32 ++++++++++++++++------------- test/models/patch/region.test.js | 35 ++++++++++++++++---------------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index 442592b2d1..14e71b9d5e 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -1,31 +1,35 @@ import {Range} from 'atom'; class Region { - constructor(range) { - this.range = range; + constructor(marker) { + this.marker = marker; + } + + getMarker() { + return this.marker; } getRange() { - return this.range; + return this.marker.getRange(); } getStartBufferRow() { - return this.range.start.row; + return this.getRange().start.row; } getEndBufferRow() { - return this.range.end.row; + return this.getRange().end.row; } includesBufferRow(row) { - return this.range.intersectsRow(row); + return this.getRange().intersectsRow(row); } intersectRows(rowSet, includeGaps) { const intersections = []; let withinIntersection = false; - let currentRow = this.range.start.row; + let currentRow = this.getRange().start.row; let nextStartRow = currentRow; const finishRowRange = isGap => { @@ -34,7 +38,7 @@ class Region { return; } - if (currentRow <= this.range.start.row) { + if (currentRow <= this.getRange().start.row) { return; } @@ -46,7 +50,7 @@ class Region { nextStartRow = currentRow; }; - while (currentRow <= this.range.end.row) { + while (currentRow <= this.getRange().end.row) { if (rowSet.has(currentRow) && !withinIntersection) { // One row past the end of a gap. Start of intersecting row range. finishRowRange(true); @@ -81,11 +85,11 @@ class Region { } getBufferRows() { - return this.range.getRows(); + return this.getRange().getRows(); } bufferRowCount() { - return this.range.getRowCount(); + return this.getRange().getRowCount(); } when(callbacks) { @@ -94,7 +98,7 @@ class Region { } toStringIn(buffer) { - return buffer.getTextInRange(this.range).replace(/(^|\n)(?!$)/g, '$&' + this.constructor.origin); + return buffer.getTextInRange(this.getRange()).replace(/(^|\n)(?!$)/g, '$&' + this.constructor.origin); } invert() { @@ -114,7 +118,7 @@ export class Addition extends Region { } invert() { - return new Deletion(this.getRange()); + return new Deletion(this.getMarker()); } } @@ -126,7 +130,7 @@ export class Deletion extends Region { } invert() { - return new Addition(this.getRange()); + return new Addition(this.getMarker()); } } diff --git a/test/models/patch/region.test.js b/test/models/patch/region.test.js index bc37cb08ca..3c575fc9e0 100644 --- a/test/models/patch/region.test.js +++ b/test/models/patch/region.test.js @@ -1,23 +1,24 @@ -import {TextBuffer, Range} from 'atom'; +import {TextBuffer} from 'atom'; import {Addition, Deletion, NoNewline, Unchanged} from '../../../lib/models/patch/region'; describe('Regions', function() { - let buffer, range; + let buffer, marker; beforeEach(function() { - buffer = new TextBuffer({text: '0000\n1111\n2222\n3333\n4444\n5555\n'}); - range = Range.fromObject([[1, 0], [3, Infinity]]); + buffer = new TextBuffer({text: '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999\n'}); + marker = buffer.markRange([[1, 0], [3, 4]]); }); describe('Addition', function() { let addition; beforeEach(function() { - addition = new Addition(range); + addition = new Addition(marker); }); - it('has range accessors', function() { - assert.strictEqual(addition.getRange(), range); + it('has marker and range accessors', function() { + assert.strictEqual(addition.getMarker(), marker); + assert.deepEqual(addition.getRange().serialize(), [[1, 0], [3, 4]]); assert.strictEqual(addition.getStartBufferRow(), 1); assert.strictEqual(addition.getEndBufferRow(), 3); }); @@ -74,7 +75,7 @@ describe('Regions', function() { it('inverts to a deletion', function() { const inverted = addition.invert(); assert.isTrue(inverted.isDeletion()); - assert.strictEqual(inverted.getRange(), addition.getRange()); + assert.strictEqual(inverted.getMarker(), addition.getMarker()); }); }); @@ -82,7 +83,7 @@ describe('Regions', function() { let deletion; beforeEach(function() { - deletion = new Deletion(range); + deletion = new Deletion(marker); }); it('can be recognized by the isDeletion predicate', function() { @@ -131,7 +132,7 @@ describe('Regions', function() { it('inverts to an addition', function() { const inverted = deletion.invert(); assert.isTrue(inverted.isAddition()); - assert.strictEqual(inverted.getRange(), deletion.getRange()); + assert.strictEqual(inverted.getMarker(), deletion.getMarker()); }); }); @@ -139,7 +140,7 @@ describe('Regions', function() { let unchanged; beforeEach(function() { - unchanged = new Unchanged(range); + unchanged = new Unchanged(marker); }); it('can be recognized by the isUnchanged predicate', function() { @@ -194,7 +195,7 @@ describe('Regions', function() { let noNewline; beforeEach(function() { - noNewline = new NoNewline(range); + noNewline = new NoNewline(marker); }); it('can be recognized by the isNoNewline predicate', function() { @@ -252,7 +253,7 @@ describe('Regions', function() { } it('returns an array containing all gaps with no intersection rows', function() { - const region = new Addition(Range.fromObject([[1, 0], [3, Infinity]])); + const region = new Addition(buffer.markRange([[1, 0], [3, Infinity]])); assertIntersections(region.intersectRows(new Set([0, 5, 6]), false), []); assertIntersections(region.intersectRows(new Set([0, 5, 6]), true), [ @@ -261,7 +262,7 @@ describe('Regions', function() { }); it('detects an intersection at the beginning of the range', function() { - const region = new Deletion(Range.fromObject([[2, 0], [6, Infinity]])); + const region = new Deletion(buffer.markRange([[2, 0], [6, Infinity]])); const rowSet = new Set([0, 1, 2, 3]); assertIntersections(region.intersectRows(rowSet, false), [ @@ -274,7 +275,7 @@ describe('Regions', function() { }); it('detects an intersection in the middle of the range', function() { - const region = new Unchanged(Range.fromObject([[2, 0], [6, Infinity]])); + const region = new Unchanged(buffer.markRange([[2, 0], [6, Infinity]])); const rowSet = new Set([0, 3, 4, 8, 9]); assertIntersections(region.intersectRows(rowSet, false), [ @@ -288,7 +289,7 @@ describe('Regions', function() { }); it('detects an intersection at the end of the range', function() { - const region = new Addition(Range.fromObject([[2, 0], [6, Infinity]])); + const region = new Addition(buffer.markRange([[2, 0], [6, Infinity]])); const rowSet = new Set([4, 5, 6, 7, 10, 11]); assertIntersections(region.intersectRows(rowSet, false), [ @@ -301,7 +302,7 @@ describe('Regions', function() { }); it('detects multiple intersections', function() { - const region = new Deletion(Range.fromObject([[2, 0], [8, Infinity]])); + const region = new Deletion(buffer.markRange([[2, 0], [8, Infinity]])); const rowSet = new Set([0, 3, 4, 6, 7, 10]); assertIntersections(region.intersectRows(rowSet, false), [ From 401da219ef388d0056b501d1ac7482af81f64891 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 13 Sep 2018 12:52:41 -0400 Subject: [PATCH 0211/4053] Store buffer markers in hunk regions --- lib/models/patch/hunk.js | 71 ++---- lib/models/patch/patch.js | 178 +++++++------- lib/models/patch/region.js | 18 +- test/helpers.js | 31 +-- test/models/patch/hunk.test.js | 204 +++++++--------- test/models/patch/patch.test.js | 400 +++++++++++++++++++------------ test/models/patch/region.test.js | 18 +- 7 files changed, 473 insertions(+), 447 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 5788732ca8..329257388d 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -1,4 +1,3 @@ -import {Range} from 'atom'; import {Unchanged} from './region'; export default class Hunk { @@ -8,8 +7,8 @@ export default class Hunk { oldRowCount, newRowCount, sectionHeading, - rowRange, - changes, + marker, + regions, }) { this.oldStartRow = oldStartRow; this.newStartRow = newStartRow; @@ -17,8 +16,8 @@ export default class Hunk { this.newRowCount = newRowCount; this.sectionHeading = sectionHeading; - this.range = rowRange; - this.changes = changes; + this.marker = marker; + this.regions = regions; } getOldStartRow() { @@ -45,70 +44,49 @@ export default class Hunk { return this.sectionHeading; } - getChanges() { - return this.changes; - } - getRegions() { - const regions = []; - let currentRow = this.range.start.row; - - for (const change of this.changes) { - const startRow = change.getRange().start.row; - - if (currentRow !== startRow) { - regions.push(new Unchanged( - Range.fromObject([[currentRow, 0], [startRow - 1, Infinity]]), - )); - } - - regions.push(change); - - currentRow = change.getRange().end.row + 1; - } - - const endRow = this.range.end.row; - - if (currentRow <= endRow) { - regions.push(new Unchanged( - Range.fromObject([[currentRow, 0], [endRow, this.range.end.column]]), - )); - } + return this.regions; + } - return regions; + getChanges() { + return this.regions.filter(change => change.isChange()); } getAdditionRanges() { - return this.changes.filter(change => change.isAddition()).map(change => change.getRange()); + return this.regions.filter(change => change.isAddition()).map(change => change.getRange()); } getDeletionRanges() { - return this.changes.filter(change => change.isDeletion()).map(change => change.getRange()); + return this.regions.filter(change => change.isDeletion()).map(change => change.getRange()); } getNoNewlineRange() { - const lastChange = this.changes[this.changes.length - 1]; - if (lastChange && lastChange.isNoNewline()) { - return lastChange.getRange(); + const lastRegion = this.regions[this.regions.length - 1]; + if (lastRegion && lastRegion.isNoNewline()) { + return lastRegion.getRange(); } else { return null; } } + getMarker() { + return this.marker; + } + getRange() { - return this.range; + return this.getMarker().getRange(); } getBufferRows() { - return this.range.getRows(); + return this.getRange().getRows(); } bufferRowCount() { - return this.range.getRowCount(); + return this.getRange().getRowCount(); } includesBufferRow(row) { - return this.range.intersectsRow(row); + return this.getRange().intersectsRow(row); } getOldRowAt(row) { @@ -171,10 +149,9 @@ export default class Hunk { } changedLineCount() { - return this.changes.reduce((count, change) => change.when({ - nonewline: () => count, - default: () => count + change.bufferRowCount(), - }), 0); + return this.regions + .filter(region => region.isChange()) + .reduce((count, change) => count + change.bufferRowCount(), 0); } toStringIn(buffer) { diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 5de54972cc..7365c48624 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -1,61 +1,82 @@ import {TextBuffer} from 'atom'; import Hunk from './hunk'; -import {Addition, Deletion, NoNewline} from './region'; +import {Unchanged, Addition, Deletion, NoNewline} from './region'; class BufferBuilder { constructor(original) { this.originalBuffer = original; this.buffer = new TextBuffer(); - this.positionOffset = 0; - this.rowOffset = 0; + this.offset = 0; this.hunkBufferText = ''; this.hunkRowCount = 0; - this.hunkStartPositionOffset = 0; - this.hunkStartRowOffset = 0; + this.hunkStartOffset = 0; + this.hunkRegions = []; + this.hunkRange = null; this.lastOffset = 0; } - append(rowRange) { - this.hunkBufferText += this.originalBuffer.getTextInRange(rowRange.bufferRange) + '\n'; - this.hunkRowCount += rowRange.bufferRowCount(); + append(range) { + this.hunkBufferText += this.originalBuffer.getTextInRange(range) + '\n'; + this.hunkRowCount += range.getRowCount(); } - remove(rowRange) { - this.rowOffset -= rowRange.bufferRowCount(); - this.positionOffset -= rowRange.endOffset - rowRange.startOffset; + remove(range) { + this.offset -= range.getRowCount(); + } + + markRegion(range, RegionKind) { + const finalRange = this.offset !== 0 + ? range.translate([this.offset, 0], [this.offset, 0]) + : range; + + // Collapse consecutive ranges of the same RegionKind into one continuous region. + const lastRegion = this.hunkRegions[this.hunkRegions.length - 1]; + if (lastRegion && lastRegion.RegionKind === RegionKind && finalRange.start.row - lastRegion.range.end.row === 1) { + lastRegion.range.end = finalRange.end; + } else { + this.hunkRegions.push({RegionKind, range: finalRange}); + } + } + + markHunkRange(range) { + let finalRange = range; + if (this.hunkStartRowOffset !== 0 || this.offset !== 0) { + finalRange = finalRange.translate([this.hunkStartOffset, 0], [this.offset, 0]); + } + this.hunkRange = finalRange; } latestHunkWasIncluded() { - this.buffer.append(this.hunkBufferText); + this.buffer.append(this.hunkBufferText, {normalizeLineEndings: false}); + + const regions = this.hunkRegions.map(({RegionKind, range}) => { + return new RegionKind(this.buffer.markRange(range, {invalidate: 'never', exclusive: false})); + }); + + const marker = this.buffer.markRange(this.hunkRange, {invalidate: 'never', exclusive: false}); this.hunkBufferText = ''; this.hunkRowCount = 0; - this.hunkStartPositionOffset = this.positionOffset; - this.hunkStartRowOffset = this.rowOffset; + this.hunkStartOffset = this.offset; + this.hunkRegions = []; + this.hunkRange = null; + + return {regions, marker}; } latestHunkWasDiscarded() { - this.rowOffset -= this.hunkRowCount; - this.positionOffset -= this.hunkBufferText.length; + this.offset -= this.hunkRowCount; this.hunkBufferText = ''; this.hunkRowCount = 0; - this.hunkStartPositionOffset = this.positionOffset; - this.hunkStartRowOffset = this.rowOffset; - } - - applyOffsetTo(rowRange) { - return rowRange.offsetBy(this.positionOffset, this.rowOffset); - } + this.hunkStartOffset = this.offset; + this.hunkRegions = []; + this.hunkRange = null; - applyHunkOffsetsTo(rowRange) { - return rowRange.offsetBy( - this.hunkStartPositionOffset, this.hunkStartRowOffset, - this.positionOffset, this.rowOffset, - ); + return {regions: [], marker: null}; } getBuffer() { @@ -112,14 +133,12 @@ export default class Patch { let newRowDelta = 0; for (const hunk of this.getHunks()) { - const changes = []; - let noNewlineChange = null; + let atLeastOneSelectedChange = false; let selectedDeletionRowCount = 0; let noNewlineRowCount = 0; for (const region of hunk.getRegions()) { - const intersections = region.getRowRange().intersectRowsIn(rowSet, this.getBuffer().getText(), true); - for (const {intersection, gap} of intersections) { + for (const {intersection, gap} of region.intersectRows(rowSet, true)) { region.when({ addition: () => { if (gap) { @@ -127,49 +146,45 @@ export default class Patch { builder.remove(intersection); } else { // Selected addition: include in new patch + atLeastOneSelectedChange = true; builder.append(intersection); - changes.push(new Addition( - builder.applyOffsetTo(intersection), - )); + builder.markRegion(intersection, Addition); } }, deletion: () => { if (gap) { // Unselected deletion: convert to context row builder.append(intersection); + builder.markRegion(intersection, Unchanged); } else { // Selected deletion: include in new patch + atLeastOneSelectedChange = true; builder.append(intersection); - changes.push(new Deletion( - builder.applyOffsetTo(intersection), - )); - selectedDeletionRowCount += intersection.bufferRowCount(); + builder.markRegion(intersection, Deletion); + selectedDeletionRowCount += intersection.getRowCount(); } }, unchanged: () => { // Untouched context line: include in new patch builder.append(intersection); + builder.markRegion(intersection, Unchanged); }, nonewline: () => { builder.append(intersection); - noNewlineChange = new NoNewline( - builder.applyOffsetTo(intersection), - ); - noNewlineRowCount += intersection.bufferRowCount(); + builder.markRegion(intersection, NoNewline); + noNewlineRowCount += intersection.getRowCount(); }, }); } } - if (changes.length > 0) { + if (atLeastOneSelectedChange) { // Hunk contains at least one selected line - if (noNewlineChange !== null) { - changes.push(noNewlineChange); - } - const rowRange = builder.applyHunkOffsetsTo(hunk.getRowRange()); + builder.markHunkRange(hunk.getRange()); + const {regions, marker} = builder.latestHunkWasIncluded(); const newStartRow = hunk.getNewStartRow() + newRowDelta; - const newRowCount = rowRange.bufferRowCount() - selectedDeletionRowCount - noNewlineRowCount; + const newRowCount = marker.getRange().getRowCount() - selectedDeletionRowCount - noNewlineRowCount; hunks.push(new Hunk({ oldStartRow: hunk.getOldStartRow(), @@ -177,13 +192,11 @@ export default class Patch { newStartRow, newRowCount, sectionHeading: hunk.getSectionHeading(), - rowRange, - changes, + marker, + regions, })); newRowDelta += newRowCount - hunk.getNewRowCount(); - - builder.latestHunkWasIncluded(); } else { newRowDelta += hunk.getOldRowCount() - hunk.getNewRowCount(); @@ -202,28 +215,26 @@ export default class Patch { let newRowDelta = 0; for (const hunk of this.getHunks()) { - const changes = []; - let noNewlineChange = null; + let atLeastOneSelectedChange = false; let contextRowCount = 0; let additionRowCount = 0; let deletionRowCount = 0; for (const region of hunk.getRegions()) { - const intersections = region.getRowRange().intersectRowsIn(rowSet, this.getBuffer().getText(), true); - for (const {intersection, gap} of intersections) { + for (const {intersection, gap} of region.intersectRows(rowSet, true)) { region.when({ addition: () => { if (gap) { // Unselected addition: become a context line. builder.append(intersection); - contextRowCount += intersection.bufferRowCount(); + builder.markRegion(intersection, Unchanged); + contextRowCount += intersection.getRowCount(); } else { // Selected addition: become a deletion. + atLeastOneSelectedChange = true; builder.append(intersection); - changes.push(new Deletion( - builder.applyOffsetTo(intersection), - )); - deletionRowCount += intersection.bufferRowCount(); + builder.markRegion(intersection, Deletion); + deletionRowCount += intersection.getRowCount(); } }, deletion: () => { @@ -232,46 +243,41 @@ export default class Patch { builder.remove(intersection); } else { // Selected deletion: becomes an addition + atLeastOneSelectedChange = true; builder.append(intersection); - changes.push(new Addition( - builder.applyOffsetTo(intersection), - )); - additionRowCount += intersection.bufferRowCount(); + builder.markRegion(intersection, Addition); + additionRowCount += intersection.getRowCount(); } }, unchanged: () => { // Untouched context line: include in new patch. builder.append(intersection); - contextRowCount += intersection.bufferRowCount(); + builder.markRegion(intersection, Unchanged); + contextRowCount += intersection.getRowCount(); }, nonewline: () => { // Nonewline marker: include in new patch. builder.append(intersection); - noNewlineChange = new NoNewline( - builder.applyOffsetTo(intersection), - ); + builder.markRegion(intersection, NoNewline); }, }); } } - if (changes.length > 0) { + if (atLeastOneSelectedChange) { // Hunk contains at least one selected line - if (noNewlineChange !== null) { - changes.push(noNewlineChange); - } + builder.markHunkRange(hunk.getRange()); + const {marker, regions} = builder.latestHunkWasIncluded(); hunks.push(new Hunk({ oldStartRow: hunk.getNewStartRow(), oldRowCount: contextRowCount + deletionRowCount, newStartRow: hunk.getNewStartRow() + newRowDelta, newRowCount: contextRowCount + additionRowCount, sectionHeading: hunk.getSectionHeading(), - rowRange: builder.applyHunkOffsetsTo(hunk.getRowRange()), - changes, + marker, + regions, })); - - builder.latestHunkWasIncluded(); } else { builder.latestHunkWasDiscarded(); } @@ -291,15 +297,16 @@ export default class Patch { getFullUnstagedPatch() { let newRowDelta = 0; + const buffer = new TextBuffer({text: this.buffer.getText()}); const hunks = this.getHunks().map(hunk => { - const changes = hunk.getChanges().map(change => change.invert()); + const regions = hunk.getRegions().map(region => region.invertIn(buffer)); const newHunk = new Hunk({ oldStartRow: hunk.getNewStartRow(), oldRowCount: hunk.getNewRowCount(), newStartRow: hunk.getNewStartRow() + newRowDelta, newRowCount: hunk.getOldRowCount(), - rowRange: hunk.getRowRange(), - changes, + marker: buffer.markRange(hunk.getRange()), + regions, }); newRowDelta += newHunk.getNewRowCount() - newHunk.getOldRowCount(); return newHunk; @@ -336,11 +343,10 @@ export default class Patch { let hunkSelectionOffset = 0; changeLoop: for (const change of hunk.getChanges()) { - const intersections = change.getRowRange().intersectRowsIn(lastSelectedRows, this.getBuffer().getText(), true); - for (const {intersection, gap} of intersections) { + for (const {intersection, gap} of change.intersectRows(lastSelectedRows, true)) { // Only include a partial range if this intersection includes the last selected buffer row. - includesMax = intersection.includesRow(lastMax); - const delta = includesMax ? lastMax - intersection.getStartBufferRow() + 1 : intersection.bufferRowCount(); + includesMax = intersection.intersectsRow(lastMax); + const delta = includesMax ? lastMax - intersection.start.row + 1 : intersection.getRowCount(); if (gap) { // Range of unselected changes. @@ -377,7 +383,7 @@ export default class Patch { toString() { return this.getHunks().reduce((str, hunk) => { - str += hunk.toStringIn(this.getBuffer().getText()); + str += hunk.toStringIn(this.getBuffer()); return str; }, ''); } diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index 14e71b9d5e..b2b7b8fbf0 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -101,7 +101,7 @@ class Region { return buffer.getTextInRange(this.getRange()).replace(/(^|\n)(?!$)/g, '$&' + this.constructor.origin); } - invert() { + invertIn() { return this; } @@ -117,8 +117,8 @@ export class Addition extends Region { return true; } - invert() { - return new Deletion(this.getMarker()); + invertIn(nextBuffer) { + return new Deletion(nextBuffer.markRange(this.getRange())); } } @@ -129,8 +129,8 @@ export class Deletion extends Region { return true; } - invert() { - return new Addition(this.getMarker()); + invertIn(nextBuffer) { + return new Addition(nextBuffer.markRange(this.getRange())); } } @@ -144,6 +144,10 @@ export class Unchanged extends Region { isChange() { return false; } + + invertIn(nextBuffer) { + return new Unchanged(nextBuffer.markRange(this.getRange())); + } } export class NoNewline extends Region { @@ -156,4 +160,8 @@ export class NoNewline extends Region { isChange() { return false; } + + invertIn(nextBuffer) { + return new NoNewline(nextBuffer.markRange(this.getRange())); + } } diff --git a/test/helpers.js b/test/helpers.js index 29e52db3d8..066d40bb8a 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -16,7 +16,6 @@ import WorkerManager from '../lib/worker-manager'; import ContextMenuInterceptor from '../lib/context-menu-interceptor'; import getRepoPipelineManager from '../lib/get-repo-pipeline-manager'; import {clearRelayExpectations} from '../lib/relay-network-layer-manager'; -import IndexedRowRange from '../lib/models/indexed-row-range'; assert.autocrlfEqual = (actual, expected, ...args) => { const newActual = actual.replace(/\r\n/g, '\n'); @@ -155,37 +154,27 @@ export function assertEqualSortedArraysByKey(arr1, arr2, key) { // Helpers for test/models/patch classes -// Quickly construct an IndexedRowRange into a buffer that has uniform line lengths (except for possibly the final -// line.) The default parameters are chosen to match buffers of the form "0000\n0001\n0002\n....". -export function buildRange(startRow, endRow = startRow, rowLength = 5, endRowLength = rowLength) { - return new IndexedRowRange({ - bufferRange: [[startRow, 0], [endRow, endRowLength - 1]], - startOffset: startRow * rowLength, - endOffset: endRow * rowLength + endRowLength, - }); -} - class PatchBufferAssertions { constructor(patch) { this.patch = patch; } - hunk(hunkIndex, {startRow, endRow, header, changes}) { + hunk(hunkIndex, {startRow, endRow, header, regions}) { const hunk = this.patch.getHunks()[hunkIndex]; assert.isDefined(hunk); - assert.strictEqual(hunk.getRowRange().getStartBufferRow(), startRow); - assert.strictEqual(hunk.getRowRange().getEndBufferRow(), endRow); + assert.strictEqual(hunk.getRange().start.row, startRow); + assert.strictEqual(hunk.getRange().end.row, endRow); assert.strictEqual(hunk.getHeader(), header); - assert.lengthOf(hunk.getChanges(), changes.length); + assert.lengthOf(hunk.getRegions(), regions.length); - for (let i = 0; i < changes.length; i++) { - const change = hunk.getChanges()[i]; - const spec = changes[i]; + for (let i = 0; i < regions.length; i++) { + const region = hunk.getRegions()[i]; + const spec = regions[i]; - assert.strictEqual(change.constructor.name.toLowerCase(), spec.kind); - assert.strictEqual(change.toStringIn(this.patch.getBuffer().getText()), spec.string); - assert.deepEqual(change.getRowRange().bufferRange.serialize(), spec.range); + assert.strictEqual(region.constructor.name.toLowerCase(), spec.kind); + assert.strictEqual(region.toStringIn(this.patch.getBuffer()), spec.string); + assert.deepEqual(region.getRange().serialize(), spec.range); } } diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 3fa7577c39..af05387442 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -1,20 +1,27 @@ -import {TextBuffer, Range} from 'atom'; +import {TextBuffer} from 'atom'; import Hunk from '../../../lib/models/patch/hunk'; -import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; +import {Unchanged, Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; describe('Hunk', function() { + const buffer = new TextBuffer({ + text: + '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n' + + '0010\n0011\n0012\n0013\n0014\n0015\n0016\n0017\n0018\n0019\n', + }); + const attrs = { oldStartRow: 0, newStartRow: 0, oldRowCount: 0, newRowCount: 0, sectionHeading: 'sectionHeading', - rowRange: Range.fromObject([[1, 0], [10, Infinity]]), - changes: [ - new Addition(Range.fromObject([[1, 0], [2, Infinity]])), - new Deletion(Range.fromObject([[3, 0], [4, Infinity]])), - new Deletion(Range.fromObject([[5, 0], [6, Infinity]])), + marker: buffer.markRange([[1, 0], [10, 4]]), + regions: [ + new Addition(buffer.markRange([[1, 0], [2, 4]])), + new Deletion(buffer.markRange([[3, 0], [4, 4]])), + new Deletion(buffer.markRange([[5, 0], [6, 4]])), + new Unchanged(buffer.markRange([[7, 0], [10, 4]])), ], }; @@ -25,11 +32,12 @@ describe('Hunk', function() { oldRowCount: 2, newRowCount: 3, sectionHeading: 'sectionHeading', - rowRange: Range.fromObject([[0, 0], [10, Infinity]]), - changes: [ - new Addition(Range.fromObject([[1, 0], [2, Infinity]])), - new Deletion(Range.fromObject([[3, 0], [4, Infinity]])), - new Deletion(Range.fromObject([[5, 0], [6, Infinity]])), + marker: buffer.markRange([[0, 0], [10, 4]]), + regions: [ + new Addition(buffer.markRange([[1, 0], [2, 4]])), + new Deletion(buffer.markRange([[3, 0], [4, 4]])), + new Deletion(buffer.markRange([[5, 0], [6, 4]])), + new Unchanged(buffer.markRange([[7, 0], [10, 4]])), ], }); @@ -38,9 +46,10 @@ describe('Hunk', function() { assert.strictEqual(h.getOldRowCount(), 2); assert.strictEqual(h.getNewRowCount(), 3); assert.strictEqual(h.getSectionHeading(), 'sectionHeading'); - assert.deepEqual(h.getRange().serialize(), [[0, 0], [10, Infinity]]); + assert.deepEqual(h.getRange().serialize(), [[0, 0], [10, 4]]); assert.strictEqual(h.bufferRowCount(), 11); assert.lengthOf(h.getChanges(), 3); + assert.lengthOf(h.getRegions(), 4); assert.lengthOf(h.getAdditionRanges(), 1); assert.lengthOf(h.getDeletionRanges(), 2); assert.isNull(h.getNoNewlineRange()); @@ -49,25 +58,17 @@ describe('Hunk', function() { it('returns the range of a no-newline region', function() { const h = new Hunk({ ...attrs, - changes: [ - new Addition(Range.fromObject([[1, 0], [2, Infinity]])), - new Deletion(Range.fromObject([[4, 0], [5, Infinity]])), - new NoNewline(Range.fromObject([[10, 0], [10, Infinity]])), + regions: [ + new Addition(buffer.markRange([[1, 0], [2, 4]])), + new Deletion(buffer.markRange([[4, 0], [5, 4]])), + new Unchanged(buffer.markRange([[6, 0], [9, 4]])), + new NoNewline(buffer.markRange([[10, 0], [10, 4]])), ], }); const nl = h.getNoNewlineRange(); assert.isNotNull(nl); - assert.deepEqual(nl.serialize(), [[10, 0], [10, Infinity]]); - }); - - it('creates its row range for decoration placement', function() { - const h = new Hunk({ - ...attrs, - rowRange: Range.fromObject([[3, 0], [6, Infinity]]), - }); - - assert.deepEqual(h.getRange().serialize(), [[3, 0], [6, Infinity]]); + assert.deepEqual(nl.serialize(), [[10, 0], [10, 4]]); }); it('generates a patch section header', function() { @@ -82,70 +83,10 @@ describe('Hunk', function() { assert.strictEqual(h.getHeader(), '@@ -0,2 +1,3 @@'); }); - it('returns a full set of covered regions, including unchanged', function() { - const h = new Hunk({ - ...attrs, - rowRange: Range.fromObject([[0, 0], [11, Infinity]]), - changes: [ - new Addition(Range.fromObject([[1, 0], [3, Infinity]])), - new Deletion(Range.fromObject([[5, 0], [6, Infinity]])), - new Deletion(Range.fromObject([[7, 0], [9, Infinity]])), - ], - }); - - const regions = h.getRegions(); - assert.lengthOf(regions, 6); - - assert.isTrue(regions[0].isUnchanged()); - assert.deepEqual(regions[0].range.serialize(), [[0, 0], [0, Infinity]]); - - assert.isTrue(regions[1].isAddition()); - assert.deepEqual(regions[1].range.serialize(), [[1, 0], [3, Infinity]]); - - assert.isTrue(regions[2].isUnchanged()); - assert.deepEqual(regions[2].range.serialize(), [[4, 0], [4, Infinity]]); - - assert.isTrue(regions[3].isDeletion()); - assert.deepEqual(regions[3].range.serialize(), [[5, 0], [6, Infinity]]); - - assert.isTrue(regions[4].isDeletion()); - assert.deepEqual(regions[4].range.serialize(), [[7, 0], [9, Infinity]]); - - assert.isTrue(regions[5].isUnchanged()); - assert.deepEqual(regions[5].range.serialize(), [[10, 0], [11, Infinity]]); - }); - - it('omits empty regions at the hunk beginning and end', function() { - const h = new Hunk({ - ...attrs, - rowRange: Range.fromObject([[1, 0], [9, 20]]), - changes: [ - new Addition(Range.fromObject([[1, 0], [3, Infinity]])), - new Deletion(Range.fromObject([[5, 0], [6, Infinity]])), - new Deletion(Range.fromObject([[7, 0], [9, 20]])), - ], - }); - - const regions = h.getRegions(); - assert.lengthOf(regions, 4); - - assert.isTrue(regions[0].isAddition()); - assert.deepEqual(regions[0].range.serialize(), [[1, 0], [3, Infinity]]); - - assert.isTrue(regions[1].isUnchanged()); - assert.deepEqual(regions[1].range.serialize(), [[4, 0], [4, Infinity]]); - - assert.isTrue(regions[2].isDeletion()); - assert.deepEqual(regions[2].range.serialize(), [[5, 0], [6, Infinity]]); - - assert.isTrue(regions[3].isDeletion()); - assert.deepEqual(regions[3].range.serialize(), [[7, 0], [9, 20]]); - }); - it('returns a set of covered buffer rows', function() { const h = new Hunk({ ...attrs, - rowRange: Range.fromObject([[6, 0], [10, 60]]), + marker: buffer.markRange([[6, 0], [10, 60]]), }); assert.sameMembers(Array.from(h.getBufferRows()), [6, 7, 8, 9, 10]); }); @@ -153,7 +94,7 @@ describe('Hunk', function() { it('determines if a buffer row is part of this hunk', function() { const h = new Hunk({ ...attrs, - rowRange: Range.fromObject([[3, 0], [5, Infinity]]), + marker: buffer.markRange([[3, 0], [5, 4]]), }); assert.isFalse(h.includesBufferRow(2)); @@ -170,12 +111,15 @@ describe('Hunk', function() { oldRowCount: 6, newStartRow: 20, newRowCount: 7, - rowRange: Range.fromObject([[2, 0], [12, 10]]), - changes: [ - new Addition(Range.fromObject([[3, 0], [5, Infinity]])), - new Deletion(Range.fromObject([[7, 0], [9, Infinity]])), - new Addition(Range.fromObject([[11, 0], [11, Infinity]])), - new NoNewline(Range.fromObject([[12, 0], [12, Infinity]])), + marker: buffer.markRange([[2, 0], [12, 4]]), + regions: [ + new Unchanged(buffer.markRange([[2, 0], [2, 4]])), + new Addition(buffer.markRange([[3, 0], [5, 4]])), + new Unchanged(buffer.markRange([[6, 0], [6, 4]])), + new Deletion(buffer.markRange([[7, 0], [9, 4]])), + new Unchanged(buffer.markRange([[10, 0], [10, 4]])), + new Addition(buffer.markRange([[11, 0], [11, 4]])), + new NoNewline(buffer.markRange([[12, 0], [12, 4]])), ], }); @@ -200,12 +144,15 @@ describe('Hunk', function() { oldRowCount: 6, newStartRow: 20, newRowCount: 7, - rowRange: Range.fromObject([[2, 0], [12, Infinity]]), - changes: [ - new Addition(Range.fromObject([[3, 0], [5, Infinity]])), - new Deletion(Range.fromObject([[7, 0], [9, Infinity]])), - new Addition(Range.fromObject([[11, 0], [11, Infinity]])), - new NoNewline(Range.fromObject([[12, 0], [12, Infinity]])), + marker: buffer.markRange([[2, 0], [12, 4]]), + regions: [ + new Unchanged(buffer.markRange([[2, 0], [2, 4]])), + new Addition(buffer.markRange([[3, 0], [5, 4]])), + new Unchanged(buffer.markRange([[6, 0], [6, 4]])), + new Deletion(buffer.markRange([[7, 0], [9, 4]])), + new Unchanged(buffer.markRange([[10, 0], [10, 4]])), + new Addition(buffer.markRange([[11, 0], [11, 4]])), + new NoNewline(buffer.markRange([[12, 0], [12, 4]])), ], }); @@ -226,18 +173,21 @@ describe('Hunk', function() { it('computes the total number of changed lines', function() { const h0 = new Hunk({ ...attrs, - changes: [ - new Addition(Range.fromObject([[2, 0], [4, Infinity]])), - new Addition(Range.fromObject([[6, 0], [6, Infinity]])), - new Deletion(Range.fromObject([[7, 0], [10, Infinity]])), - new NoNewline(Range.fromObject([[12, 0], [12, Infinity]])), + regions: [ + new Unchanged(buffer.markRange([[1, 0], [1, 4]])), + new Addition(buffer.markRange([[2, 0], [4, 4]])), + new Unchanged(buffer.markRange([[5, 0], [5, 4]])), + new Addition(buffer.markRange([[6, 0], [6, 4]])), + new Deletion(buffer.markRange([[7, 0], [10, 4]])), + new Unchanged(buffer.markRange([[11, 0], [11, 4]])), + new NoNewline(buffer.markRange([[12, 0], [12, 4]])), ], }); assert.strictEqual(h0.changedLineCount(), 8); const h1 = new Hunk({ ...attrs, - changes: [], + regions: [], }); assert.strictEqual(h1.changedLineCount(), 0); }); @@ -273,11 +223,11 @@ describe('Hunk', function() { changes: [], }); - assert.strictEqual(h.toStringIn(new TextBuffer()), '@@ -0,2 +1,3 @@\n\n'); + assert.match(h.toStringIn(new TextBuffer()), /^@@ -0,2 \+1,3 @@/); }); it('renders changed and unchanged lines with the appropriate origin characters', function() { - const buffer = new TextBuffer({ + const nBuffer = new TextBuffer({ text: '0000\n0111\n0222\n0333\n0444\n0555\n0666\n0777\n0888\n0999\n' + '1000\n1111\n1222\n' + @@ -291,18 +241,22 @@ describe('Hunk', function() { newStartRow: 1, oldRowCount: 6, newRowCount: 6, - rowRange: Range.fromObject([[1, 0], [13, Infinity]]), - changes: [ - new Addition(Range.fromObject([[2, 0], [3, Infinity]])), - new Deletion(Range.fromObject([[5, 0], [5, Infinity]])), - new Addition(Range.fromObject([[7, 0], [7, Infinity]])), - new Deletion(Range.fromObject([[8, 0], [9, Infinity]])), - new Addition(Range.fromObject([[10, 0], [10, Infinity]])), - new NoNewline(Range.fromObject([[13, 0], [13, Infinity]])), + marker: nBuffer.markRange([[1, 0], [13, 26]]), + regions: [ + new Unchanged(nBuffer.markRange([[1, 0], [1, 4]])), + new Addition(nBuffer.markRange([[2, 0], [3, 4]])), + new Unchanged(nBuffer.markRange([[4, 0], [4, 4]])), + new Deletion(nBuffer.markRange([[5, 0], [5, 4]])), + new Unchanged(nBuffer.markRange([[6, 0], [6, 4]])), + new Addition(nBuffer.markRange([[7, 0], [7, 4]])), + new Deletion(nBuffer.markRange([[8, 0], [9, 4]])), + new Addition(nBuffer.markRange([[10, 0], [10, 4]])), + new Unchanged(nBuffer.markRange([[11, 0], [12, 4]])), + new NoNewline(nBuffer.markRange([[13, 0], [13, 26]])), ], }); - assert.strictEqual(h.toStringIn(buffer), [ + assert.strictEqual(h.toStringIn(nBuffer), [ '@@ -1,6 +1,6 @@\n', ' 0111\n', '+0222\n', @@ -321,7 +275,7 @@ describe('Hunk', function() { }); it('renders a hunk without a nonewline', function() { - const buffer = new TextBuffer({text: '0000\n1111\n2222\n3333\n4444\n'}); + const nBuffer = new TextBuffer({text: '0000\n1111\n2222\n3333\n4444\n'}); const h = new Hunk({ ...attrs, @@ -329,14 +283,16 @@ describe('Hunk', function() { newStartRow: 1, oldRowCount: 1, newRowCount: 1, - rowRange: Range.fromObject([[0, 0], [3, Infinity]]), - changes: [ - new Addition(Range.fromObject([[1, 0], [1, Infinity]])), - new Deletion(Range.fromObject([[2, 0], [2, Infinity]])), + marker: nBuffer.markRange([[0, 0], [3, 4]]), + regions: [ + new Unchanged(nBuffer.markRange([[0, 0], [0, 4]])), + new Addition(nBuffer.markRange([[1, 0], [1, 4]])), + new Deletion(nBuffer.markRange([[2, 0], [2, 4]])), + new Unchanged(nBuffer.markRange([[3, 0], [3, 4]])), ], }); - assert.strictEqual(h.toStringIn(buffer), [ + assert.strictEqual(h.toStringIn(nBuffer), [ '@@ -1,1 +1,1 @@\n', ' 0000\n', '+1111\n', diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 06b94f3917..b76f86e7c1 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -2,8 +2,8 @@ import {TextBuffer} from 'atom'; import Patch, {nullPatch} from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; -import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; -import {assertInPatch, buildRange} from '../../helpers'; +import {Unchanged, Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; +import {assertInPatch} from '../../helpers'; describe('Patch', function() { it('has some standard accessors', function() { @@ -20,39 +20,47 @@ describe('Patch', function() { }); it('computes the total changed line count', function() { + const buffer = buildBuffer(15); const hunks = [ new Hunk({ oldStartRow: 0, newStartRow: 0, oldRowCount: 1, newRowCount: 1, sectionHeading: 'zero', - rowRange: buildRange(0, 5), - changes: [ - new Addition(buildRange(1)), - new Deletion(buildRange(3, 4)), + marker: markRange(buffer, 0, 5), + regions: [ + new Unchanged(markRange(buffer, 0)), + new Addition(markRange(buffer, 1)), + new Unchanged(markRange(buffer, 2)), + new Deletion(markRange(buffer, 3, 4)), + new Unchanged(markRange(buffer, 5)), ], }), new Hunk({ oldStartRow: 0, newStartRow: 0, oldRowCount: 1, newRowCount: 1, sectionHeading: 'one', - rowRange: buildRange(6, 15), - changes: [ - new Deletion(buildRange(7)), - new Deletion(buildRange(9, 11)), - new Addition(buildRange(12, 14)), + marker: markRange(buffer, 6, 15), + regions: [ + new Unchanged(markRange(buffer, 6)), + new Deletion(markRange(buffer, 7)), + new Unchanged(markRange(buffer, 8)), + new Deletion(markRange(buffer, 9, 11)), + new Addition(markRange(buffer, 12, 14)), + new Unchanged(markRange(buffer, 15)), ], }), ]; - const p = new Patch({status: 'modified', hunks, buffer: new TextBuffer({text: 'bufferText'})}); + const p = new Patch({status: 'modified', hunks, buffer}); assert.strictEqual(p.getChangedLineCount(), 10); }); it('computes the maximum number of digits needed to display a diff line number', function() { + const buffer = buildBuffer(15); const hunks = [ new Hunk({ oldStartRow: 0, oldRowCount: 1, newStartRow: 0, newRowCount: 1, sectionHeading: 'zero', - rowRange: buildRange(0, 5), - changes: [], + marker: markRange(buffer, 0, 5), + regions: [], }), new Hunk({ oldStartRow: 98, @@ -60,14 +68,14 @@ describe('Patch', function() { newStartRow: 95, newRowCount: 3, sectionHeading: 'one', - rowRange: buildRange(6, 15), - changes: [], + marker: markRange(buffer, 6, 15), + regions: [], }), ]; - const p0 = new Patch({status: 'modified', hunks, buffer: new TextBuffer({text: 'bufferText'})}); + const p0 = new Patch({status: 'modified', hunks, buffer}); assert.strictEqual(p0.getMaxLineNumberWidth(), 3); - const p1 = new Patch({status: 'deleted', hunks: [], buffer: new TextBuffer({text: ''})}); + const p1 = new Patch({status: 'deleted', hunks: [], buffer}); assert.strictEqual(p1.getMaxLineNumberWidth(), 0); }); @@ -86,7 +94,7 @@ describe('Patch', function() { assert.deepEqual(dup1.getHunks(), []); assert.strictEqual(dup1.getBuffer().getText(), 'bufferText'); - const hunks = [new Hunk({changes: []})]; + const hunks = [new Hunk({regions: []})]; const dup2 = original.clone({hunks}); assert.notStrictEqual(dup2, original); assert.strictEqual(dup2.getStatus(), 'modified'); @@ -111,7 +119,7 @@ describe('Patch', function() { assert.deepEqual(dup0.getHunks(), []); assert.strictEqual(dup0.getBuffer().getText(), ''); - const hunks = [new Hunk({changes: []})]; + const hunks = [new Hunk({regions: []})]; const dup1 = nullPatch.clone({hunks}); assert.notStrictEqual(dup1, nullPatch); assert.isNull(dup1.getStatus()); @@ -137,10 +145,14 @@ describe('Patch', function() { startRow: 0, endRow: 9, header: '@@ -12,9 +12,7 @@', - changes: [ - {kind: 'addition', string: '+0008\n', range: [[1, 0], [1, Infinity]]}, - {kind: 'deletion', string: '-0013\n-0014\n', range: [[5, 0], [6, Infinity]]}, - {kind: 'deletion', string: '-0016\n', range: [[8, 0], [8, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0007', range: [[0, 0], [0, 4]]}, + {kind: 'addition', string: '+0008', range: [[1, 0], [1, 4]]}, + {kind: 'unchanged', string: ' 0010\n 0011\n 0012', range: [[2, 0], [4, 4]]}, + {kind: 'deletion', string: '-0013\n-0014', range: [[5, 0], [6, 4]]}, + {kind: 'unchanged', string: ' 0015', range: [[7, 0], [7, 4]]}, + {kind: 'deletion', string: '-0016', range: [[8, 0], [8, 4]]}, + {kind: 'unchanged', string: ' 0018', range: [[9, 0], [9, 4]]}, ], }, ); @@ -163,27 +175,33 @@ describe('Patch', function() { startRow: 0, endRow: 4, header: '@@ -3,4 +3,4 @@', - changes: [ - {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, Infinity]]}, - {kind: 'addition', string: '+0005\n', range: [[3, 0], [3, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0000', range: [[0, 0], [0, 4]]}, + {kind: 'deletion', string: '-0001', range: [[1, 0], [1, 4]]}, + {kind: 'unchanged', string: ' 0002', range: [[2, 0], [2, 4]]}, + {kind: 'addition', string: '+0005', range: [[3, 0], [3, 4]]}, + {kind: 'unchanged', string: ' 0006', range: [[4, 0], [4, 4]]}, ], }, { startRow: 5, endRow: 14, header: '@@ -12,9 +12,8 @@', - changes: [ - {kind: 'deletion', string: '-0015\n-0016\n', range: [[11, 0], [12, Infinity]]}, - {kind: 'addition', string: '+0017\n', range: [[13, 0], [13, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0007\n 0010\n 0011\n 0012\n 0013\n 0014', range: [[5, 0], [10, 4]]}, + {kind: 'deletion', string: '-0015\n-0016', range: [[11, 0], [12, 4]]}, + {kind: 'addition', string: '+0017', range: [[13, 0], [13, 4]]}, + {kind: 'unchanged', string: ' 0018', range: [[14, 0], [14, 4]]}, ], }, { startRow: 15, endRow: 17, header: '@@ -32,1 +31,2 @@', - changes: [ - {kind: 'addition', string: '+0025\n', range: [[16, 0], [16, Infinity]]}, - {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[17, 0], [17, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0024', range: [[15, 0], [15, 4]]}, + {kind: 'addition', string: '+0025', range: [[16, 0], [16, 4]]}, + {kind: 'nonewline', string: '\\ No newline at end of file', range: [[17, 0], [17, 26]]}, ], }, ); @@ -195,9 +213,9 @@ describe('Patch', function() { new Hunk({ oldStartRow: 1, oldRowCount: 5, newStartRow: 1, newRowCount: 0, sectionHeading: 'zero', - rowRange: buildRange(0, 5, 6), - changes: [ - new Deletion(buildRange(0, 5, 6)), + marker: markRange(buffer, 0, 5), + regions: [ + new Deletion(markRange(buffer, 0, 5)), ], }), ]; @@ -211,9 +229,12 @@ describe('Patch', function() { startRow: 0, endRow: 5, header: '@@ -1,5 +1,3 @@', - changes: [ - {kind: 'deletion', string: '-line-1\n', range: [[1, 0], [1, Infinity]]}, - {kind: 'deletion', string: '-line-3\n-line-4\n', range: [[3, 0], [4, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' line-0', range: [[0, 0], [0, 6]]}, + {kind: 'deletion', string: '-line-1', range: [[1, 0], [1, 6]]}, + {kind: 'unchanged', string: ' line-2', range: [[2, 0], [2, 6]]}, + {kind: 'deletion', string: '-line-3\n-line-4', range: [[3, 0], [4, 6]]}, + {kind: 'unchanged', string: ' line-5', range: [[5, 0], [5, 6]]}, ], }, ); @@ -224,16 +245,16 @@ describe('Patch', function() { const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 0, - rowRange: buildRange(0, 2), - changes: [ - new Deletion(buildRange(0, 2)), + marker: markRange(buffer, 0, 2), + regions: [ + new Deletion(markRange(buffer, 0, 2)), ], }), ]; const patch = new Patch({status: 'deleted', hunks, buffer}); - const unstagePatch0 = patch.getUnstagePatchForLines(new Set([0, 1, 2])); - assert.strictEqual(unstagePatch0.getStatus(), 'deleted'); + const stagePatch0 = patch.getStagePatchForLines(new Set([0, 1, 2])); + assert.strictEqual(stagePatch0.getStatus(), 'deleted'); }); it('returns a nullPatch as a nullPatch', function() { @@ -255,9 +276,12 @@ describe('Patch', function() { startRow: 0, endRow: 8, header: '@@ -13,7 +13,8 @@', - changes: [ - {kind: 'deletion', string: '-0008\n', range: [[1, 0], [1, Infinity]]}, - {kind: 'addition', string: '+0012\n+0013\n', range: [[5, 0], [6, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0007', range: [[0, 0], [0, 4]]}, + {kind: 'deletion', string: '-0008', range: [[1, 0], [1, 4]]}, + {kind: 'unchanged', string: ' 0009\n 0010\n 0011', range: [[2, 0], [4, 4]]}, + {kind: 'addition', string: '+0012\n+0013', range: [[5, 0], [6, 4]]}, + {kind: 'unchanged', string: ' 0017\n 0018', range: [[7, 0], [8, 4]]}, ], }, ); @@ -282,35 +306,43 @@ describe('Patch', function() { startRow: 0, endRow: 5, header: '@@ -3,5 +3,4 @@', - changes: [ - {kind: 'addition', string: '+0001\n', range: [[1, 0], [1, Infinity]]}, - {kind: 'deletion', string: '-0004\n-0005\n', range: [[3, 0], [4, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0000', range: [[0, 0], [0, 4]]}, + {kind: 'addition', string: '+0001', range: [[1, 0], [1, 4]]}, + {kind: 'unchanged', string: ' 0003', range: [[2, 0], [2, 4]]}, + {kind: 'deletion', string: '-0004\n-0005', range: [[3, 0], [4, 4]]}, + {kind: 'unchanged', string: ' 0006', range: [[5, 0], [5, 4]]}, ], }, { startRow: 6, endRow: 13, header: '@@ -13,7 +12,7 @@', - changes: [ - {kind: 'addition', string: '+0016\n', range: [[11, 0], [11, Infinity]]}, - {kind: 'deletion', string: '-0017\n', range: [[12, 0], [12, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0007\n 0008\n 0009\n 0010\n 0011', range: [[6, 0], [10, 4]]}, + {kind: 'addition', string: '+0016', range: [[11, 0], [11, 4]]}, + {kind: 'deletion', string: '-0017', range: [[12, 0], [12, 4]]}, + {kind: 'unchanged', string: ' 0018', range: [[13, 0], [13, 4]]}, ], }, { startRow: 14, endRow: 16, header: '@@ -25,3 +24,2 @@', - changes: [ - {kind: 'deletion', string: '-0020\n', range: [[15, 0], [15, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0019', range: [[14, 0], [14, 4]]}, + {kind: 'deletion', string: '-0020', range: [[15, 0], [15, 4]]}, + {kind: 'unchanged', string: ' 0023', range: [[16, 0], [16, 4]]}, ], }, { startRow: 17, endRow: 19, header: '@@ -30,2 +28,1 @@', - changes: [ - {kind: 'deletion', string: '-0025\n', range: [[18, 0], [18, Infinity]]}, - {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[19, 0], [19, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0024', range: [[17, 0], [17, 4]]}, + {kind: 'deletion', string: '-0025', range: [[18, 0], [18, 4]]}, + {kind: 'nonewline', string: '\\ No newline at end of file', range: [[19, 0], [19, 26]]}, ], }, ); @@ -326,37 +358,45 @@ describe('Patch', function() { startRow: 0, endRow: 6, header: '@@ -3,5 +3,4 @@', - changes: [ - {kind: 'addition', string: '+0001\n+0002\n', range: [[1, 0], [2, 4]]}, - {kind: 'deletion', string: '-0003\n-0004\n-0005\n', range: [[3, 0], [5, 4]]}, + regions: [ + {kind: 'unchanged', string: ' 0000', range: [[0, 0], [0, 4]]}, + {kind: 'addition', string: '+0001\n+0002', range: [[1, 0], [2, 4]]}, + {kind: 'deletion', string: '-0003\n-0004\n-0005', range: [[3, 0], [5, 4]]}, + {kind: 'unchanged', string: ' 0006', range: [[6, 0], [6, 4]]}, ], }, { startRow: 7, endRow: 18, header: '@@ -13,7 +12,9 @@', - changes: [ - {kind: 'deletion', string: '-0008\n-0009\n', range: [[8, 0], [9, 4]]}, - {kind: 'addition', string: '+0012\n+0013\n+0014\n+0015\n+0016\n', range: [[12, 0], [16, 4]]}, - {kind: 'deletion', string: '-0017\n', range: [[17, 0], [17, 4]]}, + regions: [ + {kind: 'unchanged', string: ' 0007', range: [[7, 0], [7, 4]]}, + {kind: 'deletion', string: '-0008\n-0009', range: [[8, 0], [9, 4]]}, + {kind: 'unchanged', string: ' 0010\n 0011', range: [[10, 0], [11, 4]]}, + {kind: 'addition', string: '+0012\n+0013\n+0014\n+0015\n+0016', range: [[12, 0], [16, 4]]}, + {kind: 'deletion', string: '-0017', range: [[17, 0], [17, 4]]}, + {kind: 'unchanged', string: ' 0018', range: [[18, 0], [18, 4]]}, ], }, { startRow: 19, endRow: 23, header: '@@ -25,3 +26,4 @@', - changes: [ - {kind: 'deletion', string: '-0020\n', range: [[20, 0], [20, 4]]}, - {kind: 'addition', string: '+0021\n+0022\n', range: [[21, 0], [22, 4]]}, + regions: [ + {kind: 'unchanged', string: ' 0019', range: [[19, 0], [19, 4]]}, + {kind: 'deletion', string: '-0020', range: [[20, 0], [20, 4]]}, + {kind: 'addition', string: '+0021\n+0022', range: [[21, 0], [22, 4]]}, + {kind: 'unchanged', string: ' 0023', range: [[23, 0], [23, 4]]}, ], }, { startRow: 24, endRow: 26, header: '@@ -30,2 +32,1 @@', - changes: [ - {kind: 'deletion', string: '-0025\n', range: [[25, 0], [25, 4]]}, - {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[26, 0], [26, 26]]}, + regions: [ + {kind: 'unchanged', string: ' 0024', range: [[24, 0], [24, 4]]}, + {kind: 'deletion', string: '-0025', range: [[25, 0], [25, 4]]}, + {kind: 'nonewline', string: '\\ No newline at end of file', range: [[26, 0], [26, 26]]}, ], }, ); @@ -367,9 +407,9 @@ describe('Patch', function() { const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 3, - rowRange: buildRange(0, 2), - changes: [ - new Addition(buildRange(0, 2)), + marker: markRange(buffer, 0, 2), + regions: [ + new Addition(markRange(buffer, 0, 2)), ], }), ]; @@ -382,8 +422,9 @@ describe('Patch', function() { startRow: 0, endRow: 2, header: '@@ -1,3 +1,1 @@', - changes: [ - {kind: 'deletion', string: '-0001\n-0002\n', range: [[1, 0], [2, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0000', range: [[0, 0], [0, 4]]}, + {kind: 'deletion', string: '-0001\n-0002', range: [[1, 0], [2, 4]]}, ], }, ); @@ -397,9 +438,9 @@ describe('Patch', function() { oldRowCount: 0, newStartRow: 1, newRowCount: 3, - rowRange: buildRange(0, 2), - changes: [ - new Addition(buildRange(0, 2)), + marker: markRange(buffer, 0, 2), + regions: [ + new Addition(markRange(buffer, 0, 2)), ], }), ]; @@ -425,14 +466,15 @@ describe('Patch', function() { }); it('returns the origin if the first hunk is empty', function() { + const buffer = new TextBuffer({text: ''}); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 0, - rowRange: buildRange(0), - changes: [], + marker: markRange(buffer, 0), + regions: [], }), ]; - const patch = new Patch({status: 'modified', hunks, buffer: new TextBuffer({text: ''})}); + const patch = new Patch({status: 'modified', hunks, buffer}); assert.deepEqual(patch.getFirstChangeRange(), [[0, 0], [0, 0]]); }); @@ -464,35 +506,44 @@ describe('Patch', function() { const nHunks = [ new Hunk({ oldStartRow: 3, oldRowCount: 3, newStartRow: 3, newRowCount: 5, // next row drift = +2 - rowRange: buildRange(0, 4), // context: 0, 2, 4 - changes: [ - new Addition(buildRange(1)), // + 1 - new Addition(buildRange(3)), // + 3 + marker: markRange(nBuffer, 0, 4), + regions: [ + new Unchanged(markRange(nBuffer, 0)), // 0 + new Addition(markRange(nBuffer, 1)), // + 1 + new Unchanged(markRange(nBuffer, 2)), // 2 + new Addition(markRange(nBuffer, 3)), // + 3 + new Unchanged(markRange(nBuffer, 4)), // 4 ], }), new Hunk({ oldStartRow: 12, oldRowCount: 9, newStartRow: 14, newRowCount: 7, // next row drift = +2 -2 = 0 - rowRange: buildRange(5, 15), // context: 5, 7, 8, 9, 15 - changes: [ - new Addition(buildRange(6)), // +6 - new Deletion(buildRange(10, 13)), // -10 -11 -12 -13 - new Addition(buildRange(14)), // +14 + marker: markRange(nBuffer, 5, 15), + regions: [ + new Unchanged(markRange(nBuffer, 5)), // 5 + new Addition(markRange(nBuffer, 6)), // +6 + new Unchanged(markRange(nBuffer, 7, 9)), // 7 8 9 + new Deletion(markRange(nBuffer, 10, 13)), // -10 -11 -12 -13 + new Addition(markRange(nBuffer, 14)), // +14 + new Unchanged(markRange(nBuffer, 15)), // 15 ], }), new Hunk({ oldStartRow: 26, oldRowCount: 4, newStartRow: 26, newRowCount: 3, // next row drift = 0 -1 = -1 - rowRange: buildRange(16, 20), // context: 16, 20 - changes: [ - new Addition(buildRange(17)), // +17 - new Deletion(buildRange(18, 19)), // -18 -19 + marker: markRange(nBuffer, 16, 20), + regions: [ + new Unchanged(markRange(nBuffer, 16)), // 16 + new Addition(markRange(nBuffer, 17)), // +17 + new Deletion(markRange(nBuffer, 18, 19)), // -18 -19 + new Unchanged(markRange(nBuffer, 20)), // 20 ], }), new Hunk({ oldStartRow: 32, oldRowCount: 1, newStartRow: 31, newRowCount: 2, - rowRange: buildRange(22, 24), // context: 22 - changes: [ - new Addition(buildRange(23)), // +23 - new NoNewline(buildRange(24)), + marker: markRange(nBuffer, 22, 24), + regions: [ + new Unchanged(markRange(nBuffer, 22)), // 22 + new Addition(markRange(nBuffer, 23)), // +23 + new NoNewline(markRange(nBuffer, 24)), ], }), ]; @@ -504,46 +555,54 @@ describe('Patch', function() { }); it('offsets the chosen selection index by hunks that were completely selected', function() { + const buffer = buildBuffer(11); const lastPatch = new Patch({ status: 'modified', hunks: [ new Hunk({ oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 3, - rowRange: buildRange(0, 5), - changes: [ - new Addition(buildRange(1, 2)), - new Deletion(buildRange(3, 4)), + marker: markRange(buffer, 0, 5), + regions: [ + new Unchanged(markRange(buffer, 0)), + new Addition(markRange(buffer, 1, 2)), + new Deletion(markRange(buffer, 3, 4)), + new Unchanged(markRange(buffer, 5)), ], }), new Hunk({ oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, - rowRange: buildRange(6, 11), - changes: [ - new Addition(buildRange(7, 8)), - new Deletion(buildRange(9, 10)), + marker: markRange(buffer, 6, 11), + regions: [ + new Unchanged(markRange(buffer, 6)), + new Addition(markRange(buffer, 7, 8)), + new Deletion(markRange(buffer, 9, 10)), + new Unchanged(markRange(buffer, 11)), ], }), ], - buffer: new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n0010\n0011\n'}), + buffer, }); // Select: // * all changes from hunk 0 // * partial addition (8 of 7-8) from hunk 1 const lastSelectedRows = new Set([1, 2, 3, 4, 8]); + const nextBuffer = new TextBuffer({text: '0006\n0007\n0008\n0009\n0010\n0011\n'}); const nextPatch = new Patch({ status: 'modified', hunks: [ new Hunk({ oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, - rowRange: buildRange(0, 5), - changes: [ - new Addition(buildRange(1, 1)), - new Deletion(buildRange(3, 4)), + marker: markRange(nextBuffer, 0, 5), + regions: [ + new Unchanged(markRange(nextBuffer, 0)), + new Addition(markRange(nextBuffer, 1)), + new Deletion(markRange(nextBuffer, 3, 4)), + new Unchanged(markRange(nextBuffer, 5)), ], }), ], - buffer: new TextBuffer({text: '0006\n0007\n0008\n0009\n0010\n0011\n'}), + buffer: nextBuffer, }); const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); @@ -554,18 +613,20 @@ describe('Patch', function() { const lastPatch = buildPatchFixture(); const lastSelectedRows = new Set(); + const buffer = lastPatch.getBuffer(); const nextPatch = new Patch({ status: 'modified', hunks: [ new Hunk({ oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 4, - rowRange: buildRange(0, 4), - changes: [ - new Addition(buildRange(1, 2)), - new Deletion(buildRange(3, 3)), + marker: markRange(buffer, 0, 4), + regions: [ + new Addition(markRange(buffer, 1, 2)), + new Deletion(markRange(buffer, 3)), ], }), ], + buffer, }); const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); @@ -574,26 +635,27 @@ describe('Patch', function() { }); it('prints itself as an apply-ready string', function() { - const buffer = new TextBuffer({text: '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999\n'}); - // old: 0000.2222.3333.4444.5555.6666.7777.8888.9999. - // new: 0000.1111.2222.3333.4444.5555.6666.9999. - // patch buffer: 0000.1111.2222.3333.4444.5555.6666.7777.8888.9999. + const buffer = buildBuffer(10); const hunk0 = new Hunk({ oldStartRow: 0, newStartRow: 0, oldRowCount: 2, newRowCount: 3, sectionHeading: 'zero', - rowRange: buildRange(0, 2), - changes: [ - new Addition(buildRange(1)), + marker: markRange(buffer, 0, 2), + regions: [ + new Unchanged(markRange(buffer, 0)), + new Addition(markRange(buffer, 1)), + new Unchanged(markRange(buffer, 2)), ], }); const hunk1 = new Hunk({ oldStartRow: 5, newStartRow: 6, oldRowCount: 4, newRowCount: 2, sectionHeading: 'one', - rowRange: buildRange(6, 9), - changes: [ - new Deletion(buildRange(7, 8)), + marker: markRange(buffer, 6, 9), + regions: [ + new Unchanged(markRange(buffer, 6)), + new Deletion(markRange(buffer, 7, 8)), + new Unchanged(markRange(buffer, 9)), ], }); @@ -602,13 +664,13 @@ describe('Patch', function() { assert.strictEqual(p.toString(), [ '@@ -0,2 +0,3 @@\n', ' 0000\n', - '+1111\n', - ' 2222\n', + '+0001\n', + ' 0002\n', '@@ -5,4 +6,2 @@\n', - ' 6666\n', - '-7777\n', - '-8888\n', - ' 9999\n', + ' 0006\n', + '-0007\n', + '-0008\n', + ' 0009\n', ].join('')); }); @@ -626,51 +688,75 @@ describe('Patch', function() { }); }); +function buildBuffer(lines, noNewline = false) { + const buffer = new TextBuffer(); + for (let i = 0; i < lines; i++) { + const iStr = i.toString(10); + let padding = ''; + for (let p = iStr.length; p < 4; p++) { + padding += '0'; + } + buffer.append(padding); + buffer.append(iStr); + buffer.append('\n'); + } + if (noNewline) { + buffer.append(' No newline at end of file\n'); + } + return buffer; +} + +function markRange(buffer, start, end = start) { + return buffer.markRange([[start, 0], [end, Infinity]]); +} + function buildPatchFixture() { - const buffer = new TextBuffer({ - text: - '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n' + - '0010\n0011\n0012\n0013\n0014\n0015\n0016\n0017\n0018\n0019\n' + - '0020\n0021\n0022\n0023\n0024\n0025\n' + - ' No newline at end of file\n', - }); + const buffer = buildBuffer(26, true); const hunks = [ new Hunk({ oldStartRow: 3, oldRowCount: 4, newStartRow: 3, newRowCount: 5, sectionHeading: 'zero', - rowRange: buildRange(0, 6), - changes: [ - new Deletion(buildRange(1, 2)), - new Addition(buildRange(3, 5)), + marker: markRange(buffer, 0, 6), + regions: [ + new Unchanged(markRange(buffer, 0)), + new Deletion(markRange(buffer, 1, 2)), + new Addition(markRange(buffer, 3, 5)), + new Unchanged(markRange(buffer, 6)), ], }), new Hunk({ oldStartRow: 12, oldRowCount: 9, newStartRow: 13, newRowCount: 7, sectionHeading: 'one', - rowRange: buildRange(7, 18), - changes: [ - new Addition(buildRange(8, 9)), - new Deletion(buildRange(12, 16)), - new Addition(buildRange(17, 17)), + marker: markRange(buffer, 7, 18), + regions: [ + new Unchanged(markRange(buffer, 7)), + new Addition(markRange(buffer, 8, 9)), + new Unchanged(markRange(buffer, 10, 11)), + new Deletion(markRange(buffer, 12, 16)), + new Addition(markRange(buffer, 17, 17)), + new Unchanged(markRange(buffer, 18)), ], }), new Hunk({ oldStartRow: 26, oldRowCount: 4, newStartRow: 25, newRowCount: 3, sectionHeading: 'two', - rowRange: buildRange(19, 23), - changes: [ - new Addition(buildRange(20)), - new Deletion(buildRange(21, 22)), + marker: markRange(buffer, 19, 23), + regions: [ + new Unchanged(markRange(buffer, 19)), + new Addition(markRange(buffer, 20)), + new Deletion(markRange(buffer, 21, 22)), + new Unchanged(markRange(buffer, 23)), ], }), new Hunk({ oldStartRow: 32, oldRowCount: 1, newStartRow: 30, newRowCount: 2, sectionHeading: 'three', - rowRange: buildRange(24, 26), - changes: [ - new Addition(buildRange(25)), - new NoNewline(buildRange(26, 26, 5, 27)), + marker: markRange(buffer, 24, 26), + regions: [ + new Unchanged(markRange(buffer, 24)), + new Addition(markRange(buffer, 25)), + new NoNewline(markRange(buffer, 26)), ], }), ]; diff --git a/test/models/patch/region.test.js b/test/models/patch/region.test.js index 3c575fc9e0..41f9877f34 100644 --- a/test/models/patch/region.test.js +++ b/test/models/patch/region.test.js @@ -73,9 +73,9 @@ describe('Regions', function() { }); it('inverts to a deletion', function() { - const inverted = addition.invert(); + const inverted = addition.invertIn(buffer); assert.isTrue(inverted.isDeletion()); - assert.strictEqual(inverted.getMarker(), addition.getMarker()); + assert.deepEqual(inverted.getRange().serialize(), addition.getRange().serialize()); }); }); @@ -130,9 +130,9 @@ describe('Regions', function() { }); it('inverts to an addition', function() { - const inverted = deletion.invert(); + const inverted = deletion.invertIn(buffer); assert.isTrue(inverted.isAddition()); - assert.strictEqual(inverted.getMarker(), deletion.getMarker()); + assert.deepEqual(inverted.getRange().serialize(), deletion.getRange().serialize()); }); }); @@ -187,7 +187,9 @@ describe('Regions', function() { }); it('inverts as itself', function() { - assert.strictEqual(unchanged.invert(), unchanged); + const inverted = unchanged.invertIn(buffer); + assert.isTrue(inverted.isUnchanged()); + assert.deepEqual(inverted.getRange().serialize(), unchanged.getRange().serialize()); }); }); @@ -241,8 +243,10 @@ describe('Regions', function() { assert.strictEqual(noNewline.toStringIn(buffer), '\\1111\n\\2222\n\\3333'); }); - it('inverts as itself', function() { - assert.strictEqual(noNewline.invert(), noNewline); + it('inverts as another nonewline change', function() { + const inverted = noNewline.invertIn(buffer); + assert.isTrue(inverted.isNoNewline()); + assert.deepEqual(inverted.getRange().serialize(), noNewline.getRange().serialize()); }); }); From 64122f3317f2bc0e7d5ceda67e7584431fecb086 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 13 Sep 2018 14:49:21 -0400 Subject: [PATCH 0212/4053] Organize patch markers into layers --- lib/models/patch/patch.js | 106 +++++++++- test/models/patch/patch.test.js | 357 ++++++++++++++++++++++---------- 2 files changed, 346 insertions(+), 117 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 7365c48624..2689ace15b 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -7,6 +7,11 @@ class BufferBuilder { constructor(original) { this.originalBuffer = original; this.buffer = new TextBuffer(); + this.layers = new Map( + [Unchanged, Addition, Deletion, NoNewline, 'hunk'].map(key => { + return [key, this.buffer.addMarkerLayer()]; + }), + ); this.offset = 0; this.hunkBufferText = ''; @@ -53,10 +58,12 @@ class BufferBuilder { this.buffer.append(this.hunkBufferText, {normalizeLineEndings: false}); const regions = this.hunkRegions.map(({RegionKind, range}) => { - return new RegionKind(this.buffer.markRange(range, {invalidate: 'never', exclusive: false})); + return new RegionKind( + this.layers.get(RegionKind).markRange(range, {invalidate: 'never', exclusive: false}), + ); }); - const marker = this.buffer.markRange(this.hunkRange, {invalidate: 'never', exclusive: false}); + const marker = this.layers.get('hunk').markRange(this.hunkRange, {invalidate: 'never', exclusive: false}); this.hunkBufferText = ''; this.hunkRowCount = 0; @@ -82,14 +89,30 @@ class BufferBuilder { getBuffer() { return this.buffer; } + + getLayers() { + return { + hunk: this.layers.get('hunk'), + unchanged: this.layers.get(Unchanged), + addition: this.layers.get(Addition), + deletion: this.layers.get(Deletion), + noNewline: this.layers.get(NoNewline), + }; + } } export default class Patch { - constructor({status, hunks, buffer}) { + constructor({status, hunks, buffer, layers}) { this.status = status; this.hunks = hunks; this.buffer = buffer; + this.hunkLayer = layers.hunk; + this.unchangedLayer = layers.unchanged; + this.additionLayer = layers.addition; + this.deletionLayer = layers.deletion; + this.noNewlineLayer = layers.noNewline; + this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); } @@ -105,6 +128,26 @@ export default class Patch { return this.buffer; } + getHunkLayer() { + return this.hunkLayer; + } + + getUnchangedLayer() { + return this.unchangedLayer; + } + + getAdditionLayer() { + return this.additionLayer; + } + + getDeletionLayer() { + return this.deletionLayer; + } + + getNoNewlineLayer() { + return this.noNewlineLayer; + } + getByteSize() { return Buffer.byteLength(this.buffer.getText(), 'utf8'); } @@ -123,6 +166,13 @@ export default class Patch { status: opts.status !== undefined ? opts.status : this.getStatus(), hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), buffer: opts.buffer !== undefined ? opts.buffer : this.getBuffer(), + layers: opts.layers !== undefined ? opts.layers : { + hunk: this.getHunkLayer(), + unchanged: this.getUnchangedLayer(), + addition: this.getAdditionLayer(), + deletion: this.getDeletionLayer(), + noNewline: this.getNoNewlineLayer(), + }, }); } @@ -206,7 +256,7 @@ export default class Patch { const wholeFile = rowSet.size === this.changedLineCount; const status = this.getStatus() === 'deleted' && !wholeFile ? 'modified' : this.getStatus(); - return this.clone({hunks, status, buffer: builder.getBuffer()}); + return this.clone({hunks, status, buffer: builder.getBuffer(), layers: builder.getLayers()}); } getUnstagePatchForLines(rowSet) { @@ -292,7 +342,7 @@ export default class Patch { status = wholeFile ? 'deleted' : 'modified'; } - return this.clone({hunks, status, buffer: builder.getBuffer()}); + return this.clone({hunks, status, buffer: builder.getBuffer(), layers: builder.getLayers()}); } getFullUnstagedPatch() { @@ -393,6 +443,16 @@ export default class Patch { } } +const emptyTextBuffer = new TextBuffer(); + +const emptyTextBufferLayers = { + hunk: emptyTextBuffer.addMarkerLayer(), + unchanged: emptyTextBuffer.addMarkerLayer(), + addition: emptyTextBuffer.addMarkerLayer(), + deletion: emptyTextBuffer.addMarkerLayer(), + noNewline: emptyTextBuffer.addMarkerLayer(), +}; + export const nullPatch = { getStatus() { return null; @@ -403,7 +463,27 @@ export const nullPatch = { }, getBuffer() { - return new TextBuffer(); + return emptyTextBuffer; + }, + + getHunkLayer() { + return emptyTextBufferLayers.hunk; + }, + + getUnchangedLayer() { + return emptyTextBufferLayers.unchanged; + }, + + getAdditionLayer() { + return emptyTextBufferLayers.addition; + }, + + getDeletionLayer() { + return emptyTextBufferLayers.deletion; + }, + + getNoNewlineLayer() { + return emptyTextBufferLayers.noNewline; }, getByteSize() { @@ -415,13 +495,25 @@ export const nullPatch = { }, clone(opts = {}) { - if (opts.status === undefined && opts.hunks === undefined && opts.buffer === undefined) { + if ( + opts.status === undefined && + opts.hunks === undefined && + opts.buffer === undefined && + opts.layers === undefined + ) { return this; } else { return new Patch({ status: opts.status !== undefined ? opts.status : this.getStatus(), hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), buffer: opts.buffer !== undefined ? opts.buffer : this.getBuffer(), + layers: opts.layers !== undefined ? opts.layers : { + hunk: this.getHunkLayer(), + unchanged: this.getUnchangedLayer(), + addition: this.getAdditionLayer(), + deletion: this.getDeletionLayer(), + noNewline: this.getNoNewlineLayer(), + }, }); } }, diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index b76f86e7c1..b388e21df1 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -7,59 +7,70 @@ import {assertInPatch} from '../../helpers'; describe('Patch', function() { it('has some standard accessors', function() { - const p = new Patch({status: 'modified', hunks: [], buffer: new TextBuffer({text: 'bufferText'})}); + const buffer = new TextBuffer({text: 'bufferText'}); + const layers = buildLayers(buffer); + const p = new Patch({status: 'modified', hunks: [], buffer, layers}); assert.strictEqual(p.getStatus(), 'modified'); assert.deepEqual(p.getHunks(), []); assert.strictEqual(p.getBuffer().getText(), 'bufferText'); assert.isTrue(p.isPresent()); + + assert.strictEqual(p.getUnchangedLayer().getMarkerCount(), 0); + assert.strictEqual(p.getAdditionLayer().getMarkerCount(), 0); + assert.strictEqual(p.getDeletionLayer().getMarkerCount(), 0); + assert.strictEqual(p.getNoNewlineLayer().getMarkerCount(), 0); }); it('computes the byte size of the total patch data', function() { - const p = new Patch({status: 'modified', hunks: [], buffer: new TextBuffer({text: '\u00bd + \u00bc = \u00be'})}); + const buffer = new TextBuffer({text: '\u00bd + \u00bc = \u00be'}); + const layers = buildLayers(buffer); + const p = new Patch({status: 'modified', hunks: [], buffer, layers}); assert.strictEqual(p.getByteSize(), 12); }); it('computes the total changed line count', function() { const buffer = buildBuffer(15); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 0, newStartRow: 0, oldRowCount: 1, newRowCount: 1, sectionHeading: 'zero', - marker: markRange(buffer, 0, 5), + marker: markRange(layers.hunk, 0, 5), regions: [ - new Unchanged(markRange(buffer, 0)), - new Addition(markRange(buffer, 1)), - new Unchanged(markRange(buffer, 2)), - new Deletion(markRange(buffer, 3, 4)), - new Unchanged(markRange(buffer, 5)), + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1)), + new Unchanged(markRange(layers.unchanged, 2)), + new Deletion(markRange(layers.deletion, 3, 4)), + new Unchanged(markRange(layers.unchanged, 5)), ], }), new Hunk({ oldStartRow: 0, newStartRow: 0, oldRowCount: 1, newRowCount: 1, sectionHeading: 'one', - marker: markRange(buffer, 6, 15), + marker: markRange(layers.hunk, 6, 15), regions: [ - new Unchanged(markRange(buffer, 6)), - new Deletion(markRange(buffer, 7)), - new Unchanged(markRange(buffer, 8)), - new Deletion(markRange(buffer, 9, 11)), - new Addition(markRange(buffer, 12, 14)), - new Unchanged(markRange(buffer, 15)), + new Unchanged(markRange(layers.unchanged, 6)), + new Deletion(markRange(layers.deletion, 7)), + new Unchanged(markRange(layers.unchanged, 8)), + new Deletion(markRange(layers.deletion, 9, 11)), + new Addition(markRange(layers.addition, 12, 14)), + new Unchanged(markRange(layers.unchanged, 15)), ], }), ]; - const p = new Patch({status: 'modified', hunks, buffer}); + const p = new Patch({status: 'modified', hunks, buffer, layers}); assert.strictEqual(p.getChangedLineCount(), 10); }); it('computes the maximum number of digits needed to display a diff line number', function() { const buffer = buildBuffer(15); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 0, oldRowCount: 1, newStartRow: 0, newRowCount: 1, sectionHeading: 'zero', - marker: markRange(buffer, 0, 5), + marker: markRange(layers.hunk, 0, 5), regions: [], }), new Hunk({ @@ -68,19 +79,21 @@ describe('Patch', function() { newStartRow: 95, newRowCount: 3, sectionHeading: 'one', - marker: markRange(buffer, 6, 15), + marker: markRange(layers.hunk, 6, 15), regions: [], }), ]; - const p0 = new Patch({status: 'modified', hunks, buffer}); + const p0 = new Patch({status: 'modified', hunks, buffer, layers}); assert.strictEqual(p0.getMaxLineNumberWidth(), 3); - const p1 = new Patch({status: 'deleted', hunks: [], buffer}); + const p1 = new Patch({status: 'deleted', hunks: [], buffer, layers}); assert.strictEqual(p1.getMaxLineNumberWidth(), 0); }); it('clones itself with optionally overridden properties', function() { - const original = new Patch({status: 'modified', hunks: [], buffer: new TextBuffer({text: 'bufferText'})}); + const buffer = new TextBuffer({text: 'bufferText'}); + const layers = buildLayers(buffer); + const original = new Patch({status: 'modified', hunks: [], buffer, layers}); const dup0 = original.clone(); assert.notStrictEqual(dup0, original); @@ -101,7 +114,9 @@ describe('Patch', function() { assert.deepEqual(dup2.getHunks(), hunks); assert.strictEqual(dup2.getBuffer().getText(), 'bufferText'); - const dup3 = original.clone({buffer: new TextBuffer({text: 'changed'})}); + const nBuffer = new TextBuffer({text: 'changed'}); + const nLayers = buildLayers(nBuffer); + const dup3 = original.clone({buffer: nBuffer, layers: nLayers}); assert.notStrictEqual(dup3, original); assert.strictEqual(dup3.getStatus(), 'modified'); assert.deepEqual(dup3.getHunks(), []); @@ -126,7 +141,9 @@ describe('Patch', function() { assert.deepEqual(dup1.getHunks(), hunks); assert.strictEqual(dup1.getBuffer().getText(), ''); - const dup2 = nullPatch.clone({buffer: new TextBuffer({text: 'changed'})}); + const nBuffer = new TextBuffer({text: 'changed'}); + const nLayers = buildLayers(nBuffer); + const dup2 = nullPatch.clone({buffer: nBuffer, layers: nLayers}); assert.notStrictEqual(dup2, nullPatch); assert.isNull(dup2.getStatus()); assert.deepEqual(dup2.getHunks(), []); @@ -207,20 +224,65 @@ describe('Patch', function() { ); }); + it('marks ranges for each change region on the correct marker layer', function() { + const patch = buildPatchFixture(); + const stagePatch = patch.getStagePatchForLines(new Set([1, 5, 15, 16, 17, 25])); + + const layerRanges = [ + ['hunk', stagePatch.getHunkLayer()], + ['unchanged', stagePatch.getUnchangedLayer()], + ['addition', stagePatch.getAdditionLayer()], + ['deletion', stagePatch.getDeletionLayer()], + ['noNewline', stagePatch.getNoNewlineLayer()], + ].reduce((obj, [key, layer]) => { + obj[key] = layer.getMarkers().map(marker => marker.getRange().serialize()); + return obj; + }, {}); + + assert.deepEqual(layerRanges, { + hunk: [ + [[0, 0], [4, 4]], + [[5, 0], [14, 4]], + [[15, 0], [17, 26]], + ], + unchanged: [ + [[0, 0], [0, 4]], + [[2, 0], [2, 4]], + [[4, 0], [4, 4]], + [[5, 0], [10, 4]], + [[14, 0], [14, 4]], + [[15, 0], [15, 4]], + ], + addition: [ + [[3, 0], [3, 4]], + [[13, 0], [13, 4]], + [[16, 0], [16, 4]], + ], + deletion: [ + [[1, 0], [1, 4]], + [[11, 0], [12, 4]], + ], + noNewline: [ + [[17, 0], [17, 26]], + ], + }); + }); + it('returns a modification patch if original patch is a deletion', function() { const buffer = new TextBuffer({text: 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 5, newStartRow: 1, newRowCount: 0, sectionHeading: 'zero', - marker: markRange(buffer, 0, 5), + marker: markRange(layers.hunk, 0, 5), regions: [ - new Deletion(markRange(buffer, 0, 5)), + new Deletion(markRange(layers.deletion, 0, 5)), ], }), ]; - const patch = new Patch({status: 'deleted', hunks, buffer}); + const patch = new Patch({status: 'deleted', hunks, buffer, layers}); const stagedPatch = patch.getStagePatchForLines(new Set([1, 3, 4])); assert.strictEqual(stagedPatch.getStatus(), 'modified'); @@ -242,16 +304,17 @@ describe('Patch', function() { it('returns an deletion when staging an entire deletion patch', function() { const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 0, - marker: markRange(buffer, 0, 2), + marker: markRange(layers.hunk, 0, 2), regions: [ - new Deletion(markRange(buffer, 0, 2)), + new Deletion(markRange(layers.deletion, 0, 2)), ], }), ]; - const patch = new Patch({status: 'deleted', hunks, buffer}); + const patch = new Patch({status: 'deleted', hunks, buffer, layers}); const stagePatch0 = patch.getStagePatchForLines(new Set([0, 1, 2])); assert.strictEqual(stagePatch0.getStatus(), 'deleted'); @@ -348,6 +411,54 @@ describe('Patch', function() { ); }); + it('marks ranges for each change region on the correct marker layer', function() { + const patch = buildPatchFixture(); + const unstagePatch = patch.getUnstagePatchForLines(new Set([1, 4, 5, 16, 17, 20, 25])); + + const layerRanges = [ + ['hunk', unstagePatch.getHunkLayer()], + ['unchanged', unstagePatch.getUnchangedLayer()], + ['addition', unstagePatch.getAdditionLayer()], + ['deletion', unstagePatch.getDeletionLayer()], + ['noNewline', unstagePatch.getNoNewlineLayer()], + ].reduce((obj, [key, layer]) => { + obj[key] = layer.getMarkers().map(marker => marker.getRange().serialize()); + return obj; + }, {}); + + assert.deepEqual(layerRanges, { + hunk: [ + [[0, 0], [5, 4]], + [[6, 0], [13, 4]], + [[14, 0], [16, 4]], + [[17, 0], [19, 26]], + ], + unchanged: [ + [[0, 0], [0, 4]], + [[2, 0], [2, 4]], + [[5, 0], [5, 4]], + [[6, 0], [10, 4]], + [[13, 0], [13, 4]], + [[14, 0], [14, 4]], + [[16, 0], [16, 4]], + [[17, 0], [17, 4]], + ], + addition: [ + [[1, 0], [1, 4]], + [[11, 0], [11, 4]], + ], + deletion: [ + [[3, 0], [4, 4]], + [[12, 0], [12, 4]], + [[15, 0], [15, 4]], + [[18, 0], [18, 4]], + ], + noNewline: [ + [[19, 0], [19, 26]], + ], + }); + }); + it('unstages an entire patch at once', function() { const patch = buildPatchFixture(); const unstagedPatch = patch.getFullUnstagedPatch(); @@ -404,16 +515,17 @@ describe('Patch', function() { it('returns a modification if original patch is an addition', function() { const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 3, - marker: markRange(buffer, 0, 2), + marker: markRange(layers.hunk, 0, 2), regions: [ - new Addition(markRange(buffer, 0, 2)), + new Addition(markRange(layers.addition, 0, 2)), ], }), ]; - const patch = new Patch({status: 'added', hunks, buffer}); + const patch = new Patch({status: 'added', hunks, buffer, layers}); const unstagePatch = patch.getUnstagePatchForLines(new Set([1, 2])); assert.strictEqual(unstagePatch.getStatus(), 'modified'); assert.strictEqual(unstagePatch.getBuffer().getText(), '0000\n0001\n0002\n'); @@ -432,19 +544,20 @@ describe('Patch', function() { it('returns a deletion when unstaging an entire addition patch', function() { const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 3, - marker: markRange(buffer, 0, 2), + marker: markRange(layers.hunk, 0, 2), regions: [ - new Addition(markRange(buffer, 0, 2)), + new Addition(markRange(layers.addition, 0, 2)), ], }), ]; - const patch = new Patch({status: 'added', hunks, buffer}); + const patch = new Patch({status: 'added', hunks, buffer, layers}); const unstagePatch0 = patch.getUnstagePatchForLines(new Set([0, 1, 2])); assert.strictEqual(unstagePatch0.getStatus(), 'deleted'); @@ -467,19 +580,22 @@ describe('Patch', function() { it('returns the origin if the first hunk is empty', function() { const buffer = new TextBuffer({text: ''}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 0, - marker: markRange(buffer, 0), + marker: markRange(layers.hunk, 0), regions: [], }), ]; - const patch = new Patch({status: 'modified', hunks, buffer}); + const patch = new Patch({status: 'modified', hunks, buffer, layers}); assert.deepEqual(patch.getFirstChangeRange(), [[0, 0], [0, 0]]); }); it('returns the origin if the patch is empty', function() { - const patch = new Patch({status: 'modified', hunks: [], buffer: new TextBuffer({text: ''})}); + const buffer = new TextBuffer({text: ''}); + const layers = buildLayers(buffer); + const patch = new Patch({status: 'modified', hunks: [], buffer, layers}); assert.deepEqual(patch.getFirstChangeRange(), [[0, 0], [0, 0]]); }); }); @@ -503,51 +619,52 @@ describe('Patch', function() { // 21 22 23 '0024\n0025\n No newline at end of file\n', }); + const nLayers = buildLayers(nBuffer); const nHunks = [ new Hunk({ oldStartRow: 3, oldRowCount: 3, newStartRow: 3, newRowCount: 5, // next row drift = +2 - marker: markRange(nBuffer, 0, 4), + marker: markRange(nLayers.hunk, 0, 4), regions: [ - new Unchanged(markRange(nBuffer, 0)), // 0 - new Addition(markRange(nBuffer, 1)), // + 1 - new Unchanged(markRange(nBuffer, 2)), // 2 - new Addition(markRange(nBuffer, 3)), // + 3 - new Unchanged(markRange(nBuffer, 4)), // 4 + new Unchanged(markRange(nLayers.unchanged, 0)), // 0 + new Addition(markRange(nLayers.addition, 1)), // + 1 + new Unchanged(markRange(nLayers.unchanged, 2)), // 2 + new Addition(markRange(nLayers.addition, 3)), // + 3 + new Unchanged(markRange(nLayers.unchanged, 4)), // 4 ], }), new Hunk({ oldStartRow: 12, oldRowCount: 9, newStartRow: 14, newRowCount: 7, // next row drift = +2 -2 = 0 - marker: markRange(nBuffer, 5, 15), + marker: markRange(nLayers.hunk, 5, 15), regions: [ - new Unchanged(markRange(nBuffer, 5)), // 5 - new Addition(markRange(nBuffer, 6)), // +6 - new Unchanged(markRange(nBuffer, 7, 9)), // 7 8 9 - new Deletion(markRange(nBuffer, 10, 13)), // -10 -11 -12 -13 - new Addition(markRange(nBuffer, 14)), // +14 - new Unchanged(markRange(nBuffer, 15)), // 15 + new Unchanged(markRange(nLayers.unchanged, 5)), // 5 + new Addition(markRange(nLayers.addition, 6)), // +6 + new Unchanged(markRange(nLayers.unchanged, 7, 9)), // 7 8 9 + new Deletion(markRange(nLayers.deletion, 10, 13)), // -10 -11 -12 -13 + new Addition(markRange(nLayers.addition, 14)), // +14 + new Unchanged(markRange(nLayers.unchanged, 15)), // 15 ], }), new Hunk({ oldStartRow: 26, oldRowCount: 4, newStartRow: 26, newRowCount: 3, // next row drift = 0 -1 = -1 - marker: markRange(nBuffer, 16, 20), + marker: markRange(nLayers.hunk, 16, 20), regions: [ - new Unchanged(markRange(nBuffer, 16)), // 16 - new Addition(markRange(nBuffer, 17)), // +17 - new Deletion(markRange(nBuffer, 18, 19)), // -18 -19 - new Unchanged(markRange(nBuffer, 20)), // 20 + new Unchanged(markRange(nLayers.unchanged, 16)), // 16 + new Addition(markRange(nLayers.addition, 17)), // +17 + new Deletion(markRange(nLayers.deletion, 18, 19)), // -18 -19 + new Unchanged(markRange(nLayers.unchanged, 20)), // 20 ], }), new Hunk({ oldStartRow: 32, oldRowCount: 1, newStartRow: 31, newRowCount: 2, - marker: markRange(nBuffer, 22, 24), + marker: markRange(nLayers.hunk, 22, 24), regions: [ - new Unchanged(markRange(nBuffer, 22)), // 22 - new Addition(markRange(nBuffer, 23)), // +23 - new NoNewline(markRange(nBuffer, 24)), + new Unchanged(markRange(nLayers.unchanged, 22)), // 22 + new Addition(markRange(nLayers.addition, 23)), // +23 + new NoNewline(markRange(nLayers.noNewline, 24)), ], }), ]; - const nextPatch = new Patch({status: 'modified', hunks: nHunks, buffer: nBuffer}); + const nextPatch = new Patch({status: 'modified', hunks: nHunks, buffer: nBuffer, layers: nLayers}); const nextRange = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); // Original buffer row 14 = the next changed row = new buffer row 11 @@ -556,31 +673,33 @@ describe('Patch', function() { it('offsets the chosen selection index by hunks that were completely selected', function() { const buffer = buildBuffer(11); + const layers = buildLayers(buffer); const lastPatch = new Patch({ status: 'modified', hunks: [ new Hunk({ oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 3, - marker: markRange(buffer, 0, 5), + marker: markRange(layers.hunk, 0, 5), regions: [ - new Unchanged(markRange(buffer, 0)), - new Addition(markRange(buffer, 1, 2)), - new Deletion(markRange(buffer, 3, 4)), - new Unchanged(markRange(buffer, 5)), + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1, 2)), + new Deletion(markRange(layers.deletion, 3, 4)), + new Unchanged(markRange(layers.unchanged, 5)), ], }), new Hunk({ oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, - marker: markRange(buffer, 6, 11), + marker: markRange(layers.hunk, 6, 11), regions: [ - new Unchanged(markRange(buffer, 6)), - new Addition(markRange(buffer, 7, 8)), - new Deletion(markRange(buffer, 9, 10)), - new Unchanged(markRange(buffer, 11)), + new Unchanged(markRange(layers.unchanged, 6)), + new Addition(markRange(layers.addition, 7, 8)), + new Deletion(markRange(layers.deletion, 9, 10)), + new Unchanged(markRange(layers.unchanged, 11)), ], }), ], buffer, + layers, }); // Select: // * all changes from hunk 0 @@ -588,21 +707,23 @@ describe('Patch', function() { const lastSelectedRows = new Set([1, 2, 3, 4, 8]); const nextBuffer = new TextBuffer({text: '0006\n0007\n0008\n0009\n0010\n0011\n'}); + const nextLayers = buildLayers(nextBuffer); const nextPatch = new Patch({ status: 'modified', hunks: [ new Hunk({ oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, - marker: markRange(nextBuffer, 0, 5), + marker: markRange(nextLayers.hunk, 0, 5), regions: [ - new Unchanged(markRange(nextBuffer, 0)), - new Addition(markRange(nextBuffer, 1)), - new Deletion(markRange(nextBuffer, 3, 4)), - new Unchanged(markRange(nextBuffer, 5)), + new Unchanged(markRange(nextLayers.unchanged, 0)), + new Addition(markRange(nextLayers.addition, 1)), + new Deletion(markRange(nextLayers.deletion, 3, 4)), + new Unchanged(markRange(nextLayers.unchanged, 5)), ], }), ], buffer: nextBuffer, + layers: nextLayers, }); const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); @@ -614,19 +735,23 @@ describe('Patch', function() { const lastSelectedRows = new Set(); const buffer = lastPatch.getBuffer(); + const layers = buildLayers(buffer); const nextPatch = new Patch({ status: 'modified', hunks: [ new Hunk({ oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 4, - marker: markRange(buffer, 0, 4), + marker: markRange(layers.hunk, 0, 4), regions: [ - new Addition(markRange(buffer, 1, 2)), - new Deletion(markRange(buffer, 3)), + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1, 2)), + new Deletion(markRange(layers.deletion, 3)), + new Unchanged(markRange(layers.unchanged, 4)), ], }), ], buffer, + layers, }); const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); @@ -636,30 +761,31 @@ describe('Patch', function() { it('prints itself as an apply-ready string', function() { const buffer = buildBuffer(10); + const layers = buildLayers(buffer); const hunk0 = new Hunk({ oldStartRow: 0, newStartRow: 0, oldRowCount: 2, newRowCount: 3, sectionHeading: 'zero', - marker: markRange(buffer, 0, 2), + marker: markRange(layers.hunk, 0, 2), regions: [ - new Unchanged(markRange(buffer, 0)), - new Addition(markRange(buffer, 1)), - new Unchanged(markRange(buffer, 2)), + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1)), + new Unchanged(markRange(layers.unchanged, 2)), ], }); const hunk1 = new Hunk({ oldStartRow: 5, newStartRow: 6, oldRowCount: 4, newRowCount: 2, sectionHeading: 'one', - marker: markRange(buffer, 6, 9), + marker: markRange(layers.hunk, 6, 9), regions: [ - new Unchanged(markRange(buffer, 6)), - new Deletion(markRange(buffer, 7, 8)), - new Unchanged(markRange(buffer, 9)), + new Unchanged(markRange(layers.unchanged, 6)), + new Deletion(markRange(layers.deletion, 7, 8)), + new Unchanged(markRange(layers.unchanged, 9)), ], }); - const p = new Patch({status: 'modified', hunks: [hunk0, hunk1], buffer}); + const p = new Patch({status: 'modified', hunks: [hunk0, hunk1], buffer, layers}); assert.strictEqual(p.toString(), [ '@@ -0,2 +0,3 @@\n', @@ -706,60 +832,71 @@ function buildBuffer(lines, noNewline = false) { return buffer; } +function buildLayers(buffer) { + return { + hunk: buffer.addMarkerLayer(), + unchanged: buffer.addMarkerLayer(), + addition: buffer.addMarkerLayer(), + deletion: buffer.addMarkerLayer(), + noNewline: buffer.addMarkerLayer(), + }; +} + function markRange(buffer, start, end = start) { return buffer.markRange([[start, 0], [end, Infinity]]); } function buildPatchFixture() { const buffer = buildBuffer(26, true); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 3, oldRowCount: 4, newStartRow: 3, newRowCount: 5, sectionHeading: 'zero', - marker: markRange(buffer, 0, 6), + marker: markRange(layers.hunk, 0, 6), regions: [ - new Unchanged(markRange(buffer, 0)), - new Deletion(markRange(buffer, 1, 2)), - new Addition(markRange(buffer, 3, 5)), - new Unchanged(markRange(buffer, 6)), + new Unchanged(markRange(layers.unchanged, 0)), + new Deletion(markRange(layers.deletion, 1, 2)), + new Addition(markRange(layers.addition, 3, 5)), + new Unchanged(markRange(layers.unchanged, 6)), ], }), new Hunk({ oldStartRow: 12, oldRowCount: 9, newStartRow: 13, newRowCount: 7, sectionHeading: 'one', - marker: markRange(buffer, 7, 18), + marker: markRange(layers.hunk, 7, 18), regions: [ - new Unchanged(markRange(buffer, 7)), - new Addition(markRange(buffer, 8, 9)), - new Unchanged(markRange(buffer, 10, 11)), - new Deletion(markRange(buffer, 12, 16)), - new Addition(markRange(buffer, 17, 17)), - new Unchanged(markRange(buffer, 18)), + new Unchanged(markRange(layers.unchanged, 7)), + new Addition(markRange(layers.addition, 8, 9)), + new Unchanged(markRange(layers.unchanged, 10, 11)), + new Deletion(markRange(layers.deletion, 12, 16)), + new Addition(markRange(layers.addition, 17, 17)), + new Unchanged(markRange(layers.unchanged, 18)), ], }), new Hunk({ oldStartRow: 26, oldRowCount: 4, newStartRow: 25, newRowCount: 3, sectionHeading: 'two', - marker: markRange(buffer, 19, 23), + marker: markRange(layers.hunk, 19, 23), regions: [ - new Unchanged(markRange(buffer, 19)), - new Addition(markRange(buffer, 20)), - new Deletion(markRange(buffer, 21, 22)), - new Unchanged(markRange(buffer, 23)), + new Unchanged(markRange(layers.unchanged, 19)), + new Addition(markRange(layers.addition, 20)), + new Deletion(markRange(layers.deletion, 21, 22)), + new Unchanged(markRange(layers.unchanged, 23)), ], }), new Hunk({ oldStartRow: 32, oldRowCount: 1, newStartRow: 30, newRowCount: 2, sectionHeading: 'three', - marker: markRange(buffer, 24, 26), + marker: markRange(layers.hunk, 24, 26), regions: [ - new Unchanged(markRange(buffer, 24)), - new Addition(markRange(buffer, 25)), - new NoNewline(markRange(buffer, 26)), + new Unchanged(markRange(layers.unchanged, 24)), + new Addition(markRange(layers.addition, 25)), + new NoNewline(markRange(layers.noNewline, 26)), ], }), ]; - return new Patch({status: 'modified', hunks, buffer}); + return new Patch({status: 'modified', hunks, buffer, layers}); } From 000e1c279bb7f12daf27068e81e920e49f22fb21 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 13 Sep 2018 15:38:44 -0400 Subject: [PATCH 0213/4053] Update FilePatch tests, phew --- lib/models/patch/file-patch.js | 9 +- test/models/patch/file-patch.test.js | 346 ++++++++++++++++----------- 2 files changed, 214 insertions(+), 141 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 55d21178d8..cc0beb9798 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -1,5 +1,6 @@ import {nullFile} from './file'; import Patch, {nullPatch} from './patch'; +import Hunk from './hunk'; import {toGitPathSep} from '../../helpers'; export default class FilePatch { @@ -53,8 +54,8 @@ export default class FilePatch { return this.getPatch().getByteSize(); } - getBufferText() { - return this.getPatch().getBufferText(); + getBuffer() { + return this.getPatch().getBuffer(); } getMaxLineNumberWidth() { @@ -197,12 +198,12 @@ export default class FilePatch { if (this.hasTypechange()) { const left = this.clone({ newFile: nullFile, - patch: this.getOldSymlink() ? new Patch({status: 'deleted', hunks: []}) : this.getPatch(), + patch: this.getOldSymlink() ? this.getPatch().clone({status: 'deleted'}) : this.getPatch(), }); const right = this.clone({ oldFile: nullFile, - patch: this.getNewSymlink() ? new Patch({status: 'added', hunks: []}) : this.getPatch(), + patch: this.getNewSymlink() ? this.getPatch().clone({status: 'added'}) : this.getPatch(), }); return left.toString() + right.toString(); diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index b621425466..00dc241e52 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -1,23 +1,27 @@ +import {TextBuffer} from 'atom'; + import FilePatch, {nullFilePatch} from '../../../lib/models/patch/file-patch'; import File, {nullFile} from '../../../lib/models/patch/file'; import Patch from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; -import {Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; -import {assertInFilePatch, buildRange} from '../../helpers'; +import {Unchanged, Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; +import {assertInFilePatch} from '../../helpers'; describe('FilePatch', function() { it('delegates methods to its files and patch', function() { - const bufferText = '0000\n0001\n0002\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 2, oldRowCount: 1, newStartRow: 2, newRowCount: 3, - rowRange: buildRange(0, 2), - changes: [ - new Addition(buildRange(1, 2)), + marker: markRange(layers.hunk, 0, 2), + regions: [ + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1, 2)), ], }), ]; - const patch = new Patch({status: 'modified', hunks, bufferText}); + const patch = new Patch({status: 'modified', hunks, buffer, layers}); const oldFile = new File({path: 'a.txt', mode: '120000', symlink: 'dest.txt'}); const newFile = new File({path: 'b.txt', mode: '100755'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -33,22 +37,24 @@ describe('FilePatch', function() { assert.isUndefined(filePatch.getNewSymlink()); assert.strictEqual(filePatch.getByteSize(), 15); - assert.strictEqual(filePatch.getBufferText(), bufferText); + assert.strictEqual(filePatch.getBuffer().getText(), '0000\n0001\n0002\n'); assert.strictEqual(filePatch.getMaxLineNumberWidth(), 1); assert.deepEqual(filePatch.getFirstChangeRange(), [[1, 0], [1, Infinity]]); - const nBufferText = '0001\n0002\n'; + const nBuffer = new TextBuffer({text: '0001\n0002\n'}); + const nLayers = buildLayers(nBuffer); const nHunks = [ new Hunk({ oldStartRow: 3, oldRowCount: 1, newStartRow: 3, newRowCount: 2, - rowRange: buildRange(0, 1), - changes: [ - new Addition(buildRange(1)), + marker: markRange(nLayers.hunk, 0, 1), + regions: [ + new Unchanged(markRange(nLayers.unchanged, 0)), + new Addition(markRange(nLayers.addition, 1)), ], }), ]; - const nPatch = new Patch({status: 'modified', hunks: nHunks, bufferText: nBufferText}); + const nPatch = new Patch({status: 'modified', hunks: nHunks, buffer: nBuffer, layers: nLayers}); const nFilePatch = new FilePatch(oldFile, newFile, nPatch); const range = nFilePatch.getNextSelectionRange(filePatch, new Set([1])); @@ -58,7 +64,9 @@ describe('FilePatch', function() { it('accesses a file path from either side of the patch', function() { const oldFile = new File({path: 'old-file.txt', mode: '100644'}); const newFile = new File({path: 'new-file.txt', mode: '100644'}); - const patch = new Patch({status: 'modified', hunks: [], bufferText: ''}); + const buffer = new TextBuffer(); + const layers = buildLayers(buffer); + const patch = new Patch({status: 'modified', hunks: [], buffer, layers}); assert.strictEqual(new FilePatch(oldFile, newFile, patch).getPath(), 'old-file.txt'); assert.strictEqual(new FilePatch(oldFile, nullFile, patch).getPath(), 'old-file.txt'); @@ -67,22 +75,26 @@ describe('FilePatch', function() { }); it('iterates addition and deletion ranges from all hunks', function() { - const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n0008\n0009\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 0, - rowRange: buildRange(0, 9), - changes: [ - new Addition(buildRange(1)), - new Addition(buildRange(3)), - new Deletion(buildRange(4)), - new Addition(buildRange(5, 6)), - new Deletion(buildRange(7)), - new Addition(buildRange(8)), + marker: markRange(layers.hunk, 0, 9), + regions: [ + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1)), + new Unchanged(markRange(layers.unchanged, 2)), + new Addition(markRange(layers.addition, 3)), + new Deletion(markRange(layers.deletion, 4)), + new Addition(markRange(layers.addition, 5, 6)), + new Deletion(markRange(layers.deletion, 7)), + new Addition(markRange(layers.addition, 8)), + new Unchanged(markRange(layers.unchanged, 9)), ], }), ]; - const patch = new Patch({status: 'modified', hunks, bufferText}); + const patch = new Patch({status: 'modified', hunks, buffer, layers}); const oldFile = new File({path: 'a.txt', mode: '100644'}); const newFile = new File({path: 'a.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -106,7 +118,9 @@ describe('FilePatch', function() { }); it('returns an empty nonewline range if no hunks are present', function() { - const patch = new Patch({status: 'modified', hunks: [], bufferText: ''}); + const buffer = new TextBuffer(); + const layers = buildLayers(buffer); + const patch = new Patch({status: 'modified', hunks: [], buffer, layers}); const oldFile = new File({path: 'a.txt', mode: '100644'}); const newFile = new File({path: 'a.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -115,25 +129,26 @@ describe('FilePatch', function() { }); it('returns a nonewline range if one is present', function() { - const bufferText = '0000\n No newline at end of file\n'; + const buffer = new TextBuffer({text: '0000\n No newline at end of file\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 0, - rowRange: buildRange(0, 1), - changes: [ - new Addition(buildRange(0)), - new NoNewline(buildRange(1, 1, 5, 26)), + marker: markRange(layers.hunk, 0, 1), + regions: [ + new Addition(markRange(layers.addition, 0)), + new NoNewline(markRange(layers.noNewline, 1)), ], }), ]; - const patch = new Patch({status: 'modified', hunks, bufferText}); + const patch = new Patch({status: 'modified', hunks, buffer, layers}); const oldFile = new File({path: 'a.txt', mode: '100644'}); const newFile = new File({path: 'a.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); const noNewlineRanges = filePatch.getNoNewlineRanges(); assert.deepEqual(noNewlineRanges.map(range => range.serialize()), [ - [[1, 0], [1, 25]], + [[1, 0], [1, 26]], ]); }); @@ -141,7 +156,9 @@ describe('FilePatch', function() { let emptyPatch; beforeEach(function() { - emptyPatch = new Patch({status: 'modified', hunks: [], bufferText: ''}); + const buffer = new TextBuffer(); + const layers = buildLayers(buffer); + emptyPatch = new Patch({status: 'modified', hunks: [], buffer, layers}); }); it('detects changes in executable mode', function() { @@ -186,8 +203,12 @@ describe('FilePatch', function() { const file01 = new File({path: 'file-01.txt', mode: '100644'}); const file10 = new File({path: 'file-10.txt', mode: '100644'}); const file11 = new File({path: 'file-11.txt', mode: '100644'}); - const patch0 = new Patch({status: 'modified', hunks: [], bufferText: '0'}); - const patch1 = new Patch({status: 'modified', hunks: [], bufferText: '1'}); + const buffer0 = new TextBuffer({text: '0'}); + const layers0 = buildLayers(buffer0); + const patch0 = new Patch({status: 'modified', hunks: [], buffer: buffer0, layers: layers0}); + const buffer1 = new TextBuffer({text: '1'}); + const layers1 = buildLayers(buffer1); + const patch1 = new Patch({status: 'modified', hunks: [], buffer: buffer1, layers: layers1}); const original = new FilePatch(file00, file01, patch0); @@ -218,18 +239,21 @@ describe('FilePatch', function() { describe('getStagePatchForLines()', function() { it('returns a new FilePatch that applies only the selected lines', function() { - const bufferText = '0000\n0001\n0002\n0003\n0004\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 5, oldRowCount: 3, newStartRow: 5, newRowCount: 4, - rowRange: buildRange(0, 4), - changes: [ - new Addition(buildRange(1, 2)), - new Deletion(buildRange(3)), + marker: markRange(layers.hunk, 0, 4), + regions: [ + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1, 2)), + new Deletion(markRange(layers.deletion, 3)), + new Unchanged(markRange(layers.unchanged, 4)), ], }), ]; - const patch = new Patch({status: 'modified', hunks, bufferText}); + const patch = new Patch({status: 'modified', hunks, buffer, layers}); const oldFile = new File({path: 'file.txt', mode: '100644'}); const newFile = new File({path: 'file.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -240,15 +264,17 @@ describe('FilePatch', function() { assert.strictEqual(stagedPatch.getOldMode(), '100644'); assert.strictEqual(stagedPatch.getNewPath(), 'file.txt'); assert.strictEqual(stagedPatch.getNewMode(), '100644'); - assert.strictEqual(stagedPatch.getBufferText(), '0000\n0001\n0003\n0004\n'); + assert.strictEqual(stagedPatch.getBuffer().getText(), '0000\n0001\n0003\n0004\n'); assertInFilePatch(stagedPatch).hunks( { startRow: 0, endRow: 3, header: '@@ -5,3 +5,3 @@', - changes: [ - {kind: 'addition', string: '+0001\n', range: [[1, 0], [1, Infinity]]}, - {kind: 'deletion', string: '-0003\n', range: [[2, 0], [2, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0000', range: [[0, 0], [0, 4]]}, + {kind: 'addition', string: '+0001', range: [[1, 0], [1, 4]]}, + {kind: 'deletion', string: '-0003', range: [[2, 0], [2, 4]]}, + {kind: 'unchanged', string: ' 0004', range: [[3, 0], [3, 4]]}, ], }, ); @@ -258,17 +284,18 @@ describe('FilePatch', function() { let deletionPatch; beforeEach(function() { - const bufferText = '0000\n0001\n0002\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 0, - rowRange: buildRange(0, 2), - changes: [ - new Deletion(buildRange(0, 2)), + marker: markRange(layers.hunk, 0, 2), + regions: [ + new Deletion(markRange(layers.deletion, 0, 2)), ], }), ]; - const patch = new Patch({status: 'deleted', hunks, bufferText}); + const patch = new Patch({status: 'deleted', hunks, buffer, layers}); const oldFile = new File({path: 'file.txt', mode: '100644'}); deletionPatch = new FilePatch(oldFile, nullFile, patch); }); @@ -281,14 +308,15 @@ describe('FilePatch', function() { assert.strictEqual(stagedPatch.getOldMode(), '100644'); assert.strictEqual(stagedPatch.getNewPath(), 'file.txt'); assert.strictEqual(stagedPatch.getNewMode(), '100644'); - assert.strictEqual(stagedPatch.getBufferText(), '0000\n0001\n0002\n'); + assert.strictEqual(stagedPatch.getBuffer().getText(), '0000\n0001\n0002\n'); assertInFilePatch(stagedPatch).hunks( { startRow: 0, endRow: 2, header: '@@ -1,3 +1,1 @@', - changes: [ - {kind: 'deletion', string: '-0001\n-0002\n', range: [[1, 0], [2, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0000', range: [[0, 0], [0, 4]]}, + {kind: 'deletion', string: '-0001\n-0002', range: [[1, 0], [2, 4]]}, ], }, ); @@ -300,31 +328,32 @@ describe('FilePatch', function() { assert.strictEqual(stagedPatch.getOldPath(), 'file.txt'); assert.strictEqual(stagedPatch.getOldMode(), '100644'); assert.isFalse(stagedPatch.getNewFile().isPresent()); - assert.strictEqual(stagedPatch.getBufferText(), '0000\n0001\n0002\n'); + assert.strictEqual(stagedPatch.getBuffer().getText(), '0000\n0001\n0002\n'); assertInFilePatch(stagedPatch).hunks( { startRow: 0, endRow: 2, header: '@@ -1,3 +1,0 @@', - changes: [ - {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 4]]}, + regions: [ + {kind: 'deletion', string: '-0000\n-0001\n-0002', range: [[0, 0], [2, 4]]}, ], }, ); }); it('unsets the newFile when a symlink is created where a file was deleted', function() { - const bufferText = '0000\n0001\n0002\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 0, - rowRange: buildRange(0, 2), - changes: [ - new Deletion(buildRange(0, 2)), + marker: markRange(layers.hunk, 0, 2), + regions: [ + new Deletion(markRange(layers.deletion, 0, 2)), ], }), ]; - const patch = new Patch({status: 'deleted', hunks, bufferText}); + const patch = new Patch({status: 'deleted', hunks, buffer, layers}); const oldFile = new File({path: 'file.txt', mode: '100644'}); const newFile = new File({path: 'file.txt', mode: '120000'}); const replacePatch = new FilePatch(oldFile, newFile, patch); @@ -337,40 +366,44 @@ describe('FilePatch', function() { }); it('stages an entire hunk at once', function() { - const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n0005\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 10, oldRowCount: 2, newStartRow: 10, newRowCount: 3, - rowRange: buildRange(0, 2), - changes: [ - new Addition(buildRange(1)), + marker: markRange(layers.hunk, 0, 2), + regions: [ + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1)), + new Unchanged(markRange(layers.unchanged, 2)), ], }), new Hunk({ - oldStartRow: 20, - oldRowCount: 3, - newStartRow: 19, - newRowCount: 2, - rowRange: buildRange(3, 5), - changes: [ - new Deletion(buildRange(4)), + oldStartRow: 20, oldRowCount: 3, newStartRow: 19, newRowCount: 2, + marker: markRange(layers.hunk, 3, 5), + regions: [ + new Unchanged(markRange(layers.unchanged, 3)), + new Deletion(markRange(layers.deletion, 4)), + new Unchanged(markRange(layers.unchanged, 5)), ], }), ]; - const patch = new Patch({status: 'modified', hunks, bufferText}); + const patch = new Patch({status: 'modified', hunks, buffer, layers}); const oldFile = new File({path: 'file.txt', mode: '100644'}); const newFile = new File({path: 'file.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); const stagedPatch = filePatch.getStagePatchForHunk(hunks[1]); - assert.strictEqual(stagedPatch.getBufferText(), '0003\n0004\n0005\n'); + assert.strictEqual(stagedPatch.getBuffer().getText(), '0003\n0004\n0005\n'); assertInFilePatch(stagedPatch).hunks( { startRow: 0, endRow: 2, header: '@@ -20,3 +18,2 @@', - changes: [ - {kind: 'deletion', string: '-0004\n', range: [[1, 0], [1, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0003', range: [[0, 0], [0, 4]]}, + {kind: 'deletion', string: '-0004', range: [[1, 0], [1, 4]]}, + {kind: 'unchanged', string: ' 0005', range: [[2, 0], [2, 4]]}, ], }, ); @@ -378,18 +411,21 @@ describe('FilePatch', function() { describe('getUnstagePatchForLines()', function() { it('returns a new FilePatch that unstages only the specified lines', function() { - const bufferText = '0000\n0001\n0002\n0003\n0004\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 5, oldRowCount: 3, newStartRow: 5, newRowCount: 4, - rowRange: buildRange(0, 4), - changes: [ - new Addition(buildRange(1, 2)), - new Deletion(buildRange(3)), + marker: markRange(layers.hunk, 0, 4), + regions: [ + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1, 2)), + new Deletion(markRange(layers.deletion, 3)), + new Unchanged(markRange(layers.unchanged, 4)), ], }), ]; - const patch = new Patch({status: 'modified', hunks, bufferText}); + const patch = new Patch({status: 'modified', hunks, buffer, layers}); const oldFile = new File({path: 'file.txt', mode: '100644'}); const newFile = new File({path: 'file.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -400,15 +436,18 @@ describe('FilePatch', function() { assert.strictEqual(unstagedPatch.getOldMode(), '100644'); assert.strictEqual(unstagedPatch.getNewPath(), 'file.txt'); assert.strictEqual(unstagedPatch.getNewMode(), '100644'); - assert.strictEqual(unstagedPatch.getBufferText(), '0000\n0001\n0002\n0003\n0004\n'); + assert.strictEqual(unstagedPatch.getBuffer().getText(), '0000\n0001\n0002\n0003\n0004\n'); assertInFilePatch(unstagedPatch).hunks( { startRow: 0, endRow: 4, header: '@@ -5,4 +5,4 @@', - changes: [ - {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, Infinity]]}, - {kind: 'addition', string: '+0003\n', range: [[3, 0], [3, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0000', range: [[0, 0], [0, 4]]}, + {kind: 'deletion', string: '-0001', range: [[1, 0], [1, 4]]}, + {kind: 'unchanged', string: ' 0002', range: [[2, 0], [2, 4]]}, + {kind: 'addition', string: '+0003', range: [[3, 0], [3, 4]]}, + {kind: 'unchanged', string: ' 0004', range: [[4, 0], [4, 4]]}, ], }, ); @@ -418,18 +457,19 @@ describe('FilePatch', function() { let newFile, addedPatch, addedFilePatch; beforeEach(function() { - const bufferText = '0000\n0001\n0002\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 3, - rowRange: buildRange(0, 2), - changes: [ - new Addition(buildRange(0, 2)), + marker: markRange(layers.hunk, 0, 2), + regions: [ + new Addition(markRange(layers.addition, 0, 2)), ], }), ]; newFile = new File({path: 'file.txt', mode: '100644'}); - addedPatch = new Patch({status: 'added', hunks, bufferText}); + addedPatch = new Patch({status: 'added', hunks, buffer, layers}); addedFilePatch = new FilePatch(nullFile, newFile, addedPatch); }); @@ -441,8 +481,9 @@ describe('FilePatch', function() { startRow: 0, endRow: 2, header: '@@ -1,3 +1,2 @@', - changes: [ - {kind: 'deletion', string: '-0002\n', range: [[2, 0], [2, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0000\n 0001', range: [[0, 0], [1, 4]]}, + {kind: 'deletion', string: '-0002', range: [[2, 0], [2, 4]]}, ], }, ); @@ -456,8 +497,8 @@ describe('FilePatch', function() { startRow: 0, endRow: 2, header: '@@ -1,3 +1,0 @@', - changes: [ - {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 4]]}, + regions: [ + {kind: 'deletion', string: '-0000\n-0001\n-0002', range: [[0, 0], [2, 4]]}, ], }, ); @@ -473,8 +514,8 @@ describe('FilePatch', function() { startRow: 0, endRow: 2, header: '@@ -1,3 +1,0 @@', - changes: [ - {kind: 'deletion', string: '-0000\n-0001\n-0002\n', range: [[0, 0], [2, 4]]}, + regions: [ + {kind: 'deletion', string: '-0000\n-0001\n-0002', range: [[0, 0], [2, 4]]}, ], }, ); @@ -483,37 +524,44 @@ describe('FilePatch', function() { }); it('unstages an entire hunk at once', function() { - const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n0005\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 10, oldRowCount: 2, newStartRow: 10, newRowCount: 3, - rowRange: buildRange(0, 2), - changes: [ - new Addition(buildRange(1)), + marker: markRange(layers.hunk, 0, 2), + regions: [ + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1)), + new Unchanged(markRange(layers.unchanged, 2)), ], }), new Hunk({ oldStartRow: 20, oldRowCount: 3, newStartRow: 19, newRowCount: 2, - rowRange: buildRange(3, 5), - changes: [ - new Deletion(buildRange(4)), + marker: markRange(layers.hunk, 3, 5), + regions: [ + new Unchanged(markRange(layers.unchanged, 3)), + new Deletion(markRange(layers.deletion, 4)), + new Unchanged(markRange(layers.unchanged, 5)), ], }), ]; - const patch = new Patch({status: 'modified', hunks, bufferText}); + const patch = new Patch({status: 'modified', hunks, buffer, layers}); const oldFile = new File({path: 'file.txt', mode: '100644'}); const newFile = new File({path: 'file.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); const unstagedPatch = filePatch.getUnstagePatchForHunk(hunks[0]); - assert.strictEqual(unstagedPatch.getBufferText(), '0000\n0001\n0002\n'); + assert.strictEqual(unstagedPatch.getBuffer().getText(), '0000\n0001\n0002\n'); assertInFilePatch(unstagedPatch).hunks( { startRow: 0, endRow: 2, header: '@@ -10,3 +10,2 @@', - changes: [ - {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, Infinity]]}, + regions: [ + {kind: 'unchanged', string: ' 0000', range: [[0, 0], [0, 4]]}, + {kind: 'deletion', string: '-0001', range: [[1, 0], [1, 4]]}, + {kind: 'unchanged', string: ' 0002', range: [[2, 0], [2, 4]]}, ], }, ); @@ -521,25 +569,30 @@ describe('FilePatch', function() { describe('toString()', function() { it('converts the patch to the standard textual format', function() { - const bufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n'; + const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 10, oldRowCount: 4, newStartRow: 10, newRowCount: 3, - rowRange: buildRange(0, 4), - changes: [ - new Addition(buildRange(1)), - new Deletion(buildRange(2, 3)), + marker: markRange(layers.hunk, 0, 4), + regions: [ + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1)), + new Deletion(markRange(layers.deletion, 2, 3)), + new Unchanged(markRange(layers.unchanged, 4)), ], }), new Hunk({ oldStartRow: 20, oldRowCount: 2, newStartRow: 20, newRowCount: 3, - rowRange: buildRange(5, 7), - changes: [ - new Addition(buildRange(6)), + marker: markRange(layers.hunk, 5, 7), + regions: [ + new Unchanged(markRange(layers.unchanged, 5)), + new Addition(markRange(layers.addition, 6)), + new Unchanged(markRange(layers.unchanged, 7)), ], }), ]; - const patch = new Patch({status: 'modified', hunks, bufferText}); + const patch = new Patch({status: 'modified', hunks, buffer, layers}); const oldFile = new File({path: 'a.txt', mode: '100644'}); const newFile = new File({path: 'b.txt', mode: '100755'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -562,18 +615,20 @@ describe('FilePatch', function() { }); it('correctly formats a file with no newline at the end', function() { - const bufferText = '0000\n0001\n No newline at end of file\n'; + const buffer = new TextBuffer({text: '0000\n0001\n No newline at end of file\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 2, - rowRange: buildRange(0, 2), - changes: [ - new Addition(buildRange(1)), - new NoNewline(buildRange(2, 2, 5, 27)), + marker: markRange(layers.hunk, 0, 2), + regions: [ + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1)), + new NoNewline(markRange(layers.noNewline, 2)), ], }), ]; - const patch = new Patch({status: 'modified', hunks, bufferText}); + const patch = new Patch({status: 'modified', hunks, buffer, layers}); const oldFile = new File({path: 'a.txt', mode: '100644'}); const newFile = new File({path: 'b.txt', mode: '100755'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -591,17 +646,18 @@ describe('FilePatch', function() { describe('typechange file patches', function() { it('handles typechange patches for a symlink replaced with a file', function() { - const bufferText = '0000\n0001\n'; + const buffer = new TextBuffer({text: '0000\n0001\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 2, - rowRange: buildRange(0, 2), - changes: [ - new Addition(buildRange(0, 2)), + marker: markRange(layers.hunk, 0, 1), + regions: [ + new Addition(markRange(layers.addition, 0, 1)), ], }), ]; - const patch = new Patch({status: 'added', hunks, bufferText}); + const patch = new Patch({status: 'added', hunks, buffer, layers}); const oldFile = new File({path: 'a.txt', mode: '120000', symlink: 'dest.txt'}); const newFile = new File({path: 'a.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -625,17 +681,18 @@ describe('FilePatch', function() { }); it('handles typechange patches for a file replaced with a symlink', function() { - const bufferText = '0000\n0001\n'; + const buffer = new TextBuffer({text: '0000\n0001\n'}); + const layers = buildLayers(buffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 2, newStartRow: 1, newRowCount: 0, - rowRange: buildRange(0, 2), - changes: [ - new Deletion(buildRange(0, 2)), + markers: markRange(layers.hunk, 0, 1), + regions: [ + new Deletion(markRange(layers.deletion, 0, 1)), ], }), ]; - const patch = new Patch({status: 'deleted', hunks, bufferText}); + const patch = new Patch({status: 'deleted', hunks, buffer, layers}); const oldFile = new File({path: 'a.txt', mode: '100644'}); const newFile = new File({path: 'a.txt', mode: '120000', symlink: 'dest.txt'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -661,7 +718,8 @@ describe('FilePatch', function() { }); it('has a nullFilePatch that stubs all FilePatch methods', function() { - const rowRange = buildRange(0, 1); + const buffer = new TextBuffer({text: '0\n1\n2\n3\n'}); + const marker = markRange(buffer, 0, 1); assert.isFalse(nullFilePatch.isPresent()); assert.isFalse(nullFilePatch.getOldFile().isPresent()); @@ -674,7 +732,7 @@ describe('FilePatch', function() { assert.isNull(nullFilePatch.getOldSymlink()); assert.isNull(nullFilePatch.getNewSymlink()); assert.strictEqual(nullFilePatch.getByteSize(), 0); - assert.strictEqual(nullFilePatch.getBufferText(), ''); + assert.strictEqual(nullFilePatch.getBuffer().getText(), ''); assert.lengthOf(nullFilePatch.getAdditionRanges(), 0); assert.lengthOf(nullFilePatch.getDeletionRanges(), 0); assert.lengthOf(nullFilePatch.getNoNewlineRanges(), 0); @@ -685,9 +743,23 @@ describe('FilePatch', function() { assert.isNull(nullFilePatch.getStatus()); assert.lengthOf(nullFilePatch.getHunks(), 0); assert.isFalse(nullFilePatch.getStagePatchForLines(new Set([0])).isPresent()); - assert.isFalse(nullFilePatch.getStagePatchForHunk(new Hunk({changes: [], rowRange})).isPresent()); + assert.isFalse(nullFilePatch.getStagePatchForHunk(new Hunk({regions: [], marker})).isPresent()); assert.isFalse(nullFilePatch.getUnstagePatchForLines(new Set([0])).isPresent()); - assert.isFalse(nullFilePatch.getUnstagePatchForHunk(new Hunk({changes: [], rowRange})).isPresent()); + assert.isFalse(nullFilePatch.getUnstagePatchForHunk(new Hunk({regions: [], marker})).isPresent()); assert.strictEqual(nullFilePatch.toString(), ''); }); }); + +function buildLayers(buffer) { + return { + hunk: buffer.addMarkerLayer(), + unchanged: buffer.addMarkerLayer(), + addition: buffer.addMarkerLayer(), + deletion: buffer.addMarkerLayer(), + noNewline: buffer.addMarkerLayer(), + }; +} + +function markRange(buffer, start, end = start) { + return buffer.markRange([[start, 0], [end, Infinity]]); +} From 9d2481538dd29704d71cd12b6a85e0ef68f10056 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 13 Sep 2018 15:58:09 -0400 Subject: [PATCH 0214/4053] Create markers on correct layers in builder --- lib/models/patch/builder.js | 66 ++++++++++++++++--------------- test/models/patch/builder.test.js | 66 +++++++++++++++++-------------- 2 files changed, 70 insertions(+), 62 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index c918ed671c..89aa32165a 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -1,8 +1,9 @@ +import {TextBuffer} from 'atom'; + import Hunk from './hunk'; import File, {nullFile} from './file'; import Patch, {nullPatch} from './patch'; -import IndexedRowRange from '../indexed-row-range'; -import {Addition, Deletion, NoNewline} from './region'; +import {Unchanged, Addition, Deletion, NoNewline} from './region'; import FilePatch from './file-patch'; export default function buildFilePatch(diffs) { @@ -24,7 +25,7 @@ function emptyDiffFilePatch() { function singleDiffFilePatch(diff) { const wasSymlink = diff.oldMode === '120000'; const isSymlink = diff.newMode === '120000'; - const [hunks, bufferText] = buildHunks(diff); + const [hunks, buffer, layers] = buildHunks(diff); let oldSymlink = null; let newSymlink = null; @@ -43,7 +44,7 @@ function singleDiffFilePatch(diff) { const newFile = diff.newPath !== null || diff.newMode !== null ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink}) : nullFile; - const patch = new Patch({status: diff.status, hunks, bufferText}); + const patch = new Patch({status: diff.status, hunks, buffer, layers}); return new FilePatch(oldFile, newFile, patch); } @@ -58,7 +59,7 @@ function dualDiffFilePatch(diff1, diff2) { contentChangeDiff = diff1; } - const [hunks, bufferText] = buildHunks(contentChangeDiff); + const [hunks, buffer, layers] = buildHunks(contentChangeDiff); const filePath = contentChangeDiff.oldPath || contentChangeDiff.newPath; const symlink = modeChangeDiff.hunks[0].lines[0].slice(1); @@ -84,7 +85,7 @@ function dualDiffFilePatch(diff1, diff2) { const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink}); const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink}); - const patch = new Patch({status, hunks, bufferText}); + const patch = new Patch({status, hunks, buffer, layers}); return new FilePatch(oldFile, newFile, patch); } @@ -92,23 +93,30 @@ function dualDiffFilePatch(diff1, diff2) { const CHANGEKIND = { '+': Addition, '-': Deletion, - ' ': null, + ' ': Unchanged, '\\': NoNewline, }; function buildHunks(diff) { - let bufferText = ''; + const buffer = new TextBuffer(); + const layers = ['hunk', 'unchanged', 'addition', 'deletion', 'noNewline'].reduce((obj, key) => { + obj[key] = buffer.addMarkerLayer(); + return obj; + }, {}); + const layersByKind = new Map([ + [Unchanged, layers.unchanged], + [Addition, layers.addition], + [Deletion, layers.deletion], + [NoNewline, layers.noNewline], + ]); const hunks = []; let bufferRow = 0; - let bufferOffset = 0; - let startOffset = 0; for (const hunkData of diff.hunks) { const bufferStartRow = bufferRow; - const bufferStartOffset = bufferOffset; - const changes = []; + const regions = []; let LastChangeKind = null; let currentRangeStart = bufferRow; @@ -120,25 +128,21 @@ function buildHunks(diff) { return; } - if (LastChangeKind !== null) { - changes.push( - new LastChangeKind( - new IndexedRowRange({ - bufferRange: [[currentRangeStart, 0], [bufferRow - 1, lastLineLength - 1]], - startOffset, - endOffset: bufferOffset, - }), + regions.push( + new LastChangeKind( + layersByKind.get(LastChangeKind).markRange( + [[currentRangeStart, 0], [bufferRow - 1, lastLineLength]], + {invalidate: 'never', exclusive: false}, ), - ); - } - startOffset = bufferOffset; + ), + ); currentRangeStart = bufferRow; }; for (const lineText of hunkData.lines) { const bufferLine = lineText.slice(1) + '\n'; nextLineLength = lineText.length - 1; - bufferText += bufferLine; + buffer.append(bufferLine); const ChangeKind = CHANGEKIND[lineText[0]]; if (ChangeKind === undefined) { @@ -150,7 +154,6 @@ function buildHunks(diff) { } LastChangeKind = ChangeKind; - bufferOffset += bufferLine.length; bufferRow++; lastLineLength = nextLineLength; } @@ -162,14 +165,13 @@ function buildHunks(diff) { oldRowCount: hunkData.oldLineCount, newRowCount: hunkData.newLineCount, sectionHeading: hunkData.heading, - rowRange: new IndexedRowRange({ - bufferRange: [[bufferStartRow, 0], [bufferRow - 1, nextLineLength - 1]], - startOffset: bufferStartOffset, - endOffset: bufferOffset, - }), - changes, + marker: layers.hunk.markRange( + [[bufferStartRow, 0], [bufferRow - 1, nextLineLength]], + {invalidate: 'never', exclusive: false}, + ), + regions, })); } - return [hunks, bufferText]; + return [hunks, buffer, layers]; } diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 4794be5530..a31d1aaa34 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -73,34 +73,40 @@ describe('buildFilePatch', function() { const buffer = 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\nline-6\nline-7\nline-8\nline-9\nline-10\n' + 'line-11\nline-12\nline-13\nline-14\nline-15\nline-16\nline-17\nline-18\n'; - assert.strictEqual(p.getBufferText(), buffer); + assert.strictEqual(p.getBuffer().getText(), buffer); assertInPatch(p).hunks( { startRow: 0, endRow: 8, header: '@@ -0,7 +0,6 @@', - changes: [ - {kind: 'deletion', string: '-line-1\n-line-2\n-line-3\n', range: [[1, 0], [3, 5]]}, - {kind: 'addition', string: '+line-5\n+line-6\n', range: [[5, 0], [6, 5]]}, + regions: [ + {kind: 'unchanged', string: ' line-0', range: [[0, 0], [0, 6]]}, + {kind: 'deletion', string: '-line-1\n-line-2\n-line-3', range: [[1, 0], [3, 6]]}, + {kind: 'unchanged', string: ' line-4', range: [[4, 0], [4, 6]]}, + {kind: 'addition', string: '+line-5\n+line-6', range: [[5, 0], [6, 6]]}, + {kind: 'unchanged', string: ' line-7\n line-8', range: [[7, 0], [8, 6]]}, ], }, { startRow: 9, endRow: 12, header: '@@ -10,3 +11,3 @@', - changes: [ - {kind: 'deletion', string: '-line-9\n', range: [[9, 0], [9, 5]]}, - {kind: 'addition', string: '+line-12\n', range: [[12, 0], [12, 6]]}, + regions: [ + {kind: 'deletion', string: '-line-9', range: [[9, 0], [9, 6]]}, + {kind: 'unchanged', string: ' line-10\n line-11', range: [[10, 0], [11, 7]]}, + {kind: 'addition', string: '+line-12', range: [[12, 0], [12, 7]]}, ], }, { startRow: 13, endRow: 18, header: '@@ -20,4 +21,4 @@', - changes: [ - {kind: 'deletion', string: '-line-14\n-line-15\n', range: [[14, 0], [15, 6]]}, - {kind: 'addition', string: '+line-16\n+line-17\n', range: [[16, 0], [17, 6]]}, + regions: [ + {kind: 'unchanged', string: ' line-13', range: [[13, 0], [13, 7]]}, + {kind: 'deletion', string: '-line-14\n-line-15', range: [[14, 0], [15, 7]]}, + {kind: 'addition', string: '+line-16\n+line-17', range: [[16, 0], [17, 7]]}, + {kind: 'unchanged', string: ' line-18', range: [[18, 0], [18, 7]]}, ], }, ); @@ -206,15 +212,15 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getPatch().getStatus(), 'deleted'); const buffer = 'line-0\nline-1\nline-2\nline-3\n'; - assert.strictEqual(p.getBufferText(), buffer); + assert.strictEqual(p.getBuffer().getText(), buffer); assertInPatch(p).hunks( { startRow: 0, endRow: 3, header: '@@ -1,4 +0,0 @@', - changes: [ - {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n-line-3\n', range: [[0, 0], [3, 5]]}, + regions: [ + {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n-line-3', range: [[0, 0], [3, 6]]}, ], }, ); @@ -249,15 +255,15 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getPatch().getStatus(), 'added'); const buffer = 'line-0\nline-1\nline-2\n'; - assert.strictEqual(p.getBufferText(), buffer); + assert.strictEqual(p.getBuffer().getText(), buffer); assertInPatch(p).hunks( { startRow: 0, endRow: 2, header: '@@ -0,0 +1,3 @@', - changes: [ - {kind: 'addition', string: '+line-0\n+line-1\n+line-2\n', range: [[0, 0], [2, 5]]}, + regions: [ + {kind: 'addition', string: '+line-0\n+line-1\n+line-2', range: [[0, 0], [2, 6]]}, ], }, ); @@ -288,16 +294,16 @@ describe('buildFilePatch', function() { ]}], }]); - assert.strictEqual(p.getBufferText(), 'line-0\nline-1\n No newline at end of file\n'); + assert.strictEqual(p.getBuffer().getText(), 'line-0\nline-1\n No newline at end of file\n'); assertInPatch(p).hunks({ startRow: 0, endRow: 2, header: '@@ -0,1 +0,1 @@', - changes: [ - {kind: 'addition', string: '+line-0\n', range: [[0, 0], [0, 5]]}, - {kind: 'deletion', string: '-line-1\n', range: [[1, 0], [1, 5]]}, - {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[2, 0], [2, 25]]}, + regions: [ + {kind: 'addition', string: '+line-0', range: [[0, 0], [0, 6]]}, + {kind: 'deletion', string: '-line-1', range: [[1, 0], [1, 6]]}, + {kind: 'nonewline', string: '\\ No newline at end of file', range: [[2, 0], [2, 26]]}, ], }); }); @@ -348,13 +354,13 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getNewSymlink(), 'the-destination'); assert.strictEqual(p.getStatus(), 'deleted'); - assert.strictEqual(p.getBufferText(), 'line-0\nline-1\n'); + assert.strictEqual(p.getBuffer().getText(), 'line-0\nline-1\n'); assertInPatch(p).hunks({ startRow: 0, endRow: 1, header: '@@ -0,0 +0,2 @@', - changes: [ - {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 5]]}, + regions: [ + {kind: 'addition', string: '+line-0\n+line-1', range: [[0, 0], [1, 6]]}, ], }); }); @@ -403,13 +409,13 @@ describe('buildFilePatch', function() { assert.isNull(p.getNewSymlink()); assert.strictEqual(p.getStatus(), 'added'); - assert.strictEqual(p.getBufferText(), 'line-0\nline-1\n'); + assert.strictEqual(p.getBuffer().getText(), 'line-0\nline-1\n'); assertInPatch(p).hunks({ startRow: 0, endRow: 1, header: '@@ -0,2 +0,0 @@', - changes: [ - {kind: 'deletion', string: '-line-0\n-line-1\n', range: [[0, 0], [1, 5]]}, + regions: [ + {kind: 'deletion', string: '-line-0\n-line-1', range: [[0, 0], [1, 6]]}, ], }); }); @@ -457,13 +463,13 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getNewSymlink(), 'the-destination'); assert.strictEqual(p.getStatus(), 'deleted'); - assert.strictEqual(p.getBufferText(), 'line-0\nline-1\n'); + assert.strictEqual(p.getBuffer().getText(), 'line-0\nline-1\n'); assertInPatch(p).hunks({ startRow: 0, endRow: 1, header: '@@ -0,0 +0,2 @@', - changes: [ - {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 5]]}, + regions: [ + {kind: 'addition', string: '+line-0\n+line-1', range: [[0, 0], [1, 6]]}, ], }); }); From 31f57a6f1c25edcc7a1e05e2909e9f28cf924767 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 13 Sep 2018 16:00:12 -0400 Subject: [PATCH 0215/4053] Remove unused code --- lib/models/file-patch-selection.js | 408 ------------ lib/models/indexed-row-range.js | 156 ----- test/models/file-patch-selection.test.js | 778 ----------------------- test/models/indexed-row-range.test.js | 216 ------- 4 files changed, 1558 deletions(-) delete mode 100644 lib/models/file-patch-selection.js delete mode 100644 lib/models/indexed-row-range.js delete mode 100644 test/models/file-patch-selection.test.js delete mode 100644 test/models/indexed-row-range.test.js diff --git a/lib/models/file-patch-selection.js b/lib/models/file-patch-selection.js deleted file mode 100644 index d84110b860..0000000000 --- a/lib/models/file-patch-selection.js +++ /dev/null @@ -1,408 +0,0 @@ -import ListSelection from './list-selection'; - -const COPY = Symbol('Copy'); - -export default class FilePatchSelection { - constructor(hunks) { - if (hunks._copy !== COPY) { - // Initialize a new selection - this.mode = 'hunk'; - - this.hunksByLine = new Map(); - this.changedLines = new Set(); - - const lines = []; - for (const hunk of hunks) { - for (const region of hunk.getRegions()) { - for (const line of region.getBufferRows()) { - lines.push(line); - this.hunksByLine.set(line, hunk); - if (region.isChange()) { - this.changedLines.add(line); - } - } - } - } - - this.hunksSelection = new ListSelection({items: hunks}); - this.linesSelection = new ListSelection({items: lines, isItemSelectable: line => this.changedLines.has(line)}); - this.resolveNextUpdatePromise = () => {}; - } else { - // Copy from options. *Only* reachable from the copy() method because no other module has visibility to - // the COPY object without shenanigans. - const options = hunks; - - this.mode = options.mode; - this.hunksSelection = options.hunksSelection; - this.linesSelection = options.linesSelection; - this.resolveNextUpdatePromise = options.resolveNextUpdatePromise; - this.hunksByLine = options.hunksByLine; - this.changedLines = options.changedLines; - } - } - - copy(options = {}) { - const mode = options.mode || this.mode; - let hunksSelection = options.hunksSelection || this.hunksSelection; - let linesSelection = options.linesSelection || this.linesSelection; - - let hunksByLine = null; - let changedLines = null; - if (options.hunks) { - // Update hunks - const oldHunks = this.hunksSelection.getItems(); - const newHunks = options.hunks; - - let wasChanged = false; - if (newHunks.length !== oldHunks.length) { - wasChanged = true; - } else { - for (let i = 0; i < oldHunks.length; i++) { - if (oldHunks[i] !== newHunks[i]) { - wasChanged = true; - break; - } - } - } - - // Update hunks, preserving selection index - hunksSelection = hunksSelection.setItems(newHunks); - - const oldLines = this.linesSelection.getItems(); - const newLines = []; - - hunksByLine = new Map(); - changedLines = new Set(); - for (const hunk of newHunks) { - for (const region of hunk.getRegions()) { - for (const line of region.getBufferRows()) { - newLines.push(line); - hunksByLine.set(line, hunk); - if (region.isChange()) { - changedLines.add(line); - } - } - } - } - - // Update lines, preserving selection index in *changed* lines - let newSelectedLine; - if (oldLines.length > 0 && newLines.length > 0) { - const oldSelectionStartIndex = this.linesSelection.getMostRecentSelectionStartIndex(); - let changedLineCount = 0; - for (let i = 0; i < oldSelectionStartIndex; i++) { - if (this.changedLines.has(oldLines[i])) { - changedLineCount++; - } - } - - for (let i = 0; i < newLines.length; i++) { - const line = newLines[i]; - if (changedLines.has(line)) { - newSelectedLine = line; - if (changedLineCount === 0) { - break; - } - changedLineCount--; - } - } - } - - linesSelection = linesSelection.setItems(newLines); - if (newSelectedLine) { - linesSelection = linesSelection.selectItem(newSelectedLine); - } - if (wasChanged) { - this.resolveNextUpdatePromise(); - } - } else { - // Hunks are unchanged. Don't recompute hunksByLine or changedLines. - hunksByLine = this.hunksByLine; - changedLines = this.changedLines; - } - - return new FilePatchSelection({ - _copy: COPY, - mode, - hunksSelection, - linesSelection, - hunksByLine, - changedLines, - resolveNextUpdatePromise: options.resolveNextUpdatePromise || this.resolveNextUpdatePromise, - }); - } - - toggleMode() { - if (this.mode === 'hunk') { - const firstLineOfSelectedHunk = this.getHeadHunk().getBufferRange().start.row; - const selection = this.selectLine(firstLineOfSelectedHunk); - if (!this.changedLines.has(firstLineOfSelectedHunk)) { - return selection.selectNextLine(); - } else { - return selection; - } - } else { - const selectedLine = this.getHeadLine(); - const hunkContainingSelectedLine = this.hunksByLine.get(selectedLine); - return this.selectHunk(hunkContainingSelectedLine); - } - } - - getMode() { - return this.mode; - } - - selectNext(preserveTail = false) { - if (this.mode === 'hunk') { - return this.selectNextHunk(preserveTail); - } else { - return this.selectNextLine(preserveTail); - } - } - - selectPrevious(preserveTail = false) { - if (this.mode === 'hunk') { - return this.selectPreviousHunk(preserveTail); - } else { - return this.selectPreviousLine(preserveTail); - } - } - - selectAll() { - if (this.mode === 'hunk') { - return this.selectAllHunks(); - } else { - return this.selectAllLines(); - } - } - - selectFirst(preserveTail) { - if (this.mode === 'hunk') { - return this.selectFirstHunk(preserveTail); - } else { - return this.selectFirstLine(preserveTail); - } - } - - selectLast(preserveTail) { - if (this.mode === 'hunk') { - return this.selectLastHunk(preserveTail); - } else { - return this.selectLastLine(preserveTail); - } - } - - selectHunk(hunk, preserveTail = false) { - return this.copy({ - mode: 'hunk', - hunksSelection: this.hunksSelection.selectItem(hunk, preserveTail), - }); - } - - addOrSubtractHunkSelection(hunk) { - return this.copy({ - mode: 'hunk', - hunksSelection: this.hunksSelection.addOrSubtractSelection(hunk), - }); - } - - selectAllHunks() { - return this.copy({ - mode: 'hunk', - hunksSelection: this.hunksSelection.selectAllItems(), - }); - } - - selectFirstHunk(preserveTail) { - return this.copy({ - mode: 'hunk', - hunksSelection: this.hunksSelection.selectFirstItem(preserveTail), - }); - } - - selectLastHunk(preserveTail) { - return this.copy({ - mode: 'hunk', - hunksSelection: this.hunksSelection.selectLastItem(preserveTail), - }); - } - - jumpToNextHunk() { - const next = this.selectNextHunk(); - return next.getMode() !== this.mode ? next.toggleMode() : next; - } - - jumpToPreviousHunk() { - const next = this.selectPreviousHunk(); - return next.getMode() !== this.mode ? next.toggleMode() : next; - } - - selectNextHunk(preserveTail) { - return this.copy({ - mode: 'hunk', - hunksSelection: this.hunksSelection.selectNextItem(preserveTail), - }); - } - - selectPreviousHunk(preserveTail) { - return this.copy({ - mode: 'hunk', - hunksSelection: this.hunksSelection.selectPreviousItem(preserveTail), - }); - } - - getSelectedHunks() { - if (this.mode === 'line') { - const selectedHunks = new Set(); - for (const line of this.getSelectedLines()) { - selectedHunks.add(this.hunksByLine.get(line)); - } - return selectedHunks; - } else { - return this.hunksSelection.getSelectedItems(); - } - } - - isEmpty() { - return this.hunksSelection.getItems().length === 0; - } - - getHeadHunk() { - return this.mode === 'hunk' ? this.hunksSelection.getHeadItem() : null; - } - - selectLine(line, preserveTail = false) { - return this.copy({ - mode: 'line', - linesSelection: this.linesSelection.selectItem(line, preserveTail), - }); - } - - addOrSubtractLineSelection(line) { - return this.copy({ - mode: 'line', - linesSelection: this.linesSelection.addOrSubtractSelection(line), - }); - } - - selectAllLines(preserveTail) { - return this.copy({ - mode: 'line', - linesSelection: this.linesSelection.selectAllItems(preserveTail), - }); - } - - selectFirstLine(preserveTail) { - return this.copy({ - mode: 'line', - linesSelection: this.linesSelection.selectFirstItem(preserveTail), - }); - } - - selectLastLine(preserveTail) { - return this.copy({ - mode: 'line', - linesSelection: this.linesSelection.selectLastItem(preserveTail), - }); - } - - selectNextLine(preserveTail = false) { - return this.copy({ - mode: 'line', - linesSelection: this.linesSelection.selectNextItem(preserveTail), - }); - } - - selectPreviousLine(preserveTail = false) { - return this.copy({ - mode: 'line', - linesSelection: this.linesSelection.selectPreviousItem(preserveTail), - }); - } - - getSelectedLines() { - if (this.mode === 'hunk') { - const selectedLines = new Set(); - for (const hunk of this.getSelectedHunks()) { - for (const change of hunk.getChanges()) { - for (const line of change.getBufferRows()) { - selectedLines.add(line); - } - } - } - return selectedLines; - } else { - return this.linesSelection.getSelectedItems(); - } - } - - getHeadLine() { - return this.mode === 'line' ? this.linesSelection.getHeadItem() : null; - } - - updateHunks(newHunks) { - return this.copy({hunks: newHunks}); - } - - coalesce() { - return this.copy({ - hunksSelection: this.hunksSelection.coalesce(), - linesSelection: this.linesSelection.coalesce(), - }); - } - - getNextUpdatePromise() { - return new Promise((resolve, reject) => { - this.resolveNextUpdatePromise = resolve; - }); - } - - getLineSelectionTailIndex() { - return this.linesSelection.getTailIndex(); - } - - goToDiffLine(lineNumber) { - // console.log(`<<< finding closest line to ${lineNumber}`); - const lines = this.linesSelection.getItems(); - - let closestLine; - let closestLineDistance = Infinity; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - // console.log(`considering line = ${line}`); - if (!this.linesSelection.isItemSelectable(line)) { - // console.log('... not selectable'); - continue; - } - - const hunk = this.hunksByLine.get(line); - const newLineNumber = hunk.getNewRowAt(line); - if (newLineNumber === null) { - // console.log('... deleted line'); - continue; - } - - // console.log(` new line number = ${newLineNumber}`); - - if (newLineNumber === lineNumber) { - // console.log('>>> exact match'); - return this.selectLine(line); - } else { - const newDistance = Math.abs(newLineNumber - lineNumber); - // console.log(` distance = ${newDistance} vs. closest = ${closestLineDistance}`); - if (newDistance < closestLineDistance) { - closestLineDistance = newDistance; - closestLine = line; - // console.log(` new closest line = ${closestLine}`); - } else { - // console.log(`>>> increasing distance. choosing previous closest line = ${closestLine}`); - return this.selectLine(closestLine); - } - } - } - - // console.log(`>>> choosing closest line = ${closestLine}`); - return this.selectLine(closestLine); - } -} diff --git a/lib/models/indexed-row-range.js b/lib/models/indexed-row-range.js deleted file mode 100644 index 38357f703f..0000000000 --- a/lib/models/indexed-row-range.js +++ /dev/null @@ -1,156 +0,0 @@ -import {Range} from 'atom'; - -// A {Range} of rows within a buffer accompanied by its corresponding start and end offsets. -// -// Note that the range's columns are disregarded for purposes of offset consistency. -export default class IndexedRowRange { - constructor({bufferRange, startOffset, endOffset}) { - this.bufferRange = Range.fromObject(bufferRange); - this.startOffset = startOffset; - this.endOffset = endOffset; - } - - getStartBufferRow() { - return this.bufferRange.start.row; - } - - getEndBufferRow() { - return this.bufferRange.end.row; - } - - getBufferRows() { - return this.bufferRange.getRows(); - } - - bufferRowCount() { - return this.bufferRange.getRowCount(); - } - - includesRow(bufferRow) { - return this.bufferRange.intersectsRow(bufferRow); - } - - toStringIn(buffer, prefix) { - return buffer.slice(this.startOffset, this.endOffset).replace(/(^|\n)(?!$)/g, '$&' + prefix); - } - - // Identify {IndexedRowRanges} within our bufferRange that intersect the rows in rowSet. If {includeGaps} is true, - // also return an {IndexedRowRange} for each gap between intersecting ranges. - intersectRowsIn(rowSet, buffer, includeGaps) { - const intersections = []; - let withinIntersection = false; - - let currentRow = this.bufferRange.start.row; - let currentOffset = this.startOffset; - let nextStartRow = currentRow; - let nextStartOffset = currentOffset; - - const finishRowRange = isGap => { - if (isGap && !includeGaps) { - nextStartRow = currentRow; - nextStartOffset = currentOffset; - return; - } - - if (nextStartOffset === currentOffset) { - return; - } - - intersections.push({ - intersection: new IndexedRowRange({ - bufferRange: Range.fromObject([[nextStartRow, 0], [currentRow - 1, Infinity]]), - startOffset: nextStartOffset, - endOffset: currentOffset, - }), - gap: isGap, - }); - - nextStartRow = currentRow; - nextStartOffset = currentOffset; - }; - - while (currentRow <= this.bufferRange.end.row) { - if (rowSet.has(currentRow) && !withinIntersection) { - // One row past the end of a gap. Start of intersecting row range. - finishRowRange(true); - withinIntersection = true; - } else if (!rowSet.has(currentRow) && withinIntersection) { - // One row past the end of intersecting row range. Start of the next gap. - finishRowRange(false); - withinIntersection = false; - } - - currentOffset = buffer.indexOf('\n', currentOffset) + 1; - currentRow++; - } - - finishRowRange(!withinIntersection); - return intersections; - } - - offsetBy(startBufferOffset, startRowOffset, endBufferOffset = startBufferOffset, endRowOffset = startRowOffset) { - if (startBufferOffset === 0 && startRowOffset === 0 && endBufferOffset === 0 && endRowOffset === 0) { - return this; - } - - return new this.constructor({ - bufferRange: this.bufferRange.translate([startRowOffset, 0], [endRowOffset, 0]), - startOffset: this.startOffset + startBufferOffset, - endOffset: this.endOffset + endBufferOffset, - }); - } - - serialize() { - return { - bufferRange: this.bufferRange.serialize(), - startOffset: this.startOffset, - endOffset: this.endOffset, - }; - } - - isPresent() { - return true; - } -} - -export const nullIndexedRowRange = { - startOffset: Infinity, - - endOffset: Infinity, - - getStartBufferRow() { - return null; - }, - - bufferRowCount() { - return 0; - }, - - getBufferRows() { - return []; - }, - - includesRow() { - return false; - }, - - toStringIn() { - return ''; - }, - - intersectRowsIn() { - return []; - }, - - offsetBy() { - return this; - }, - - serialize() { - return null; - }, - - isPresent() { - return false; - }, -}; diff --git a/test/models/file-patch-selection.test.js b/test/models/file-patch-selection.test.js deleted file mode 100644 index cb0c6679d2..0000000000 --- a/test/models/file-patch-selection.test.js +++ /dev/null @@ -1,778 +0,0 @@ -import FilePatchSelection from '../../lib/models/file-patch-selection'; -import buildFilePatch from '../../lib/models/patch/builder'; -import IndexedRowRange from '../../lib/models/indexed-row-range'; -import Hunk from '../../lib/models/patch/hunk'; -import {Addition, Deletion} from '../../lib/models/patch/region'; - -describe('FilePatchSelection', function() { - describe('line selection', function() { - it('starts a new line selection with selectLine and updates an existing selection when preserveTail is true', function() { - const patch = buildPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()); - - const selection1 = selection0.selectLine(2); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [2]); - - const selection2 = selection1.selectLine(7, true); - assert.sameMembers(Array.from(selection2.getSelectedLines()), [2, 3, 6, 7]); - - const selection3 = selection2.selectLine(6, true); - assert.sameMembers(Array.from(selection3.getSelectedLines()), [2, 3, 6]); - - const selection4 = selection3.selectLine(1, true); - assert.sameMembers(Array.from(selection4.getSelectedLines()), [1, 2]); - - const selection5 = selection4.selectLine(7); - assert.sameMembers(Array.from(selection5.getSelectedLines()), [7]); - }); - - it('adds a new line selection when calling addOrSubtractLineSelection with an unselected line and always updates the head of the most recent line selection', function() { - const patch = buildPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()) - .selectLine(2) - .selectLine(3, true) - .addOrSubtractLineSelection(7) - .selectLine(8, true); - - assert.sameMembers(Array.from(selection0.getSelectedLines()), [2, 3, 7, 8]); - - const selection1 = selection0.selectLine(1, true); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [1, 2, 3, 6, 7]); - }); - - it('subtracts from existing selections when calling addOrSubtractLineSelection with a selected line', function() { - const patch = buildPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()) - .selectLine(2) - .selectLine(7, true); - - assert.sameMembers(Array.from(selection0.getSelectedLines()), [2, 3, 6, 7]); - - const selection1 = selection0.addOrSubtractLineSelection(6); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [2, 3, 7]); - - const selection2 = selection1.selectLine(8, true); - assert.sameMembers(Array.from(selection2.getSelectedLines()), [2, 3]); - - const selection3 = selection2.selectLine(2, true); - assert.sameMembers(Array.from(selection3.getSelectedLines()), [7]); - }); - - it('allows the next or previous line to be selected', function() { - const patch = buildPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()) - .selectLine(1) - .selectNextLine(); - assert.sameMembers(Array.from(selection0.getSelectedLines()), [2]); - - const selection1 = selection0.selectNextLine(); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [3]); - - const selection2 = selection1.selectNextLine(); - assert.sameMembers(Array.from(selection2.getSelectedLines()), [6]); - - const selection3 = selection2.selectPreviousLine(); - assert.sameMembers(Array.from(selection3.getSelectedLines()), [3]); - - const selection4 = selection3.selectPreviousLine(); - assert.sameMembers(Array.from(selection4.getSelectedLines()), [2]); - - const selection5 = selection4.selectPreviousLine(); - assert.sameMembers(Array.from(selection5.getSelectedLines()), [1]); - - const selection6 = selection5.selectNextLine(true); - assert.sameMembers(Array.from(selection6.getSelectedLines()), [1, 2]); - - const selection7 = selection6.selectNextLine().selectNextLine().selectPreviousLine(true); - assert.sameMembers(Array.from(selection7.getSelectedLines()), [3, 6]); - }); - - it('allows the first/last changed line to be selected', function() { - const patch = buildPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()).selectLastLine(); - assert.sameMembers(Array.from(selection0.getSelectedLines()), [8]); - - const selection1 = selection0.selectFirstLine(); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [1]); - - const selection2 = selection1.selectLastLine(true); - assert.sameMembers(Array.from(selection2.getSelectedLines()), [1, 2, 3, 6, 7, 8]); - - const selection3 = selection2.selectLastLine(); - assert.sameMembers(Array.from(selection3.getSelectedLines()), [8]); - - const selection4 = selection3.selectFirstLine(true); - assert.sameMembers(Array.from(selection4.getSelectedLines()), [1, 2, 3, 6, 7, 8]); - - const selection5 = selection4.selectFirstLine(); - assert.sameMembers(Array.from(selection5.getSelectedLines()), [1]); - }); - - it('allows all lines to be selected', function() { - const patch = buildPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()).selectAllLines(); - assert.sameMembers(Array.from(selection0.getSelectedLines()), [1, 2, 3, 6, 7, 8]); - }); - - it('defaults to the first/last changed line when selecting next / previous with no current selection', function() { - const patch = buildPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()) - .selectLine(1) - .addOrSubtractLineSelection(1) - .coalesce(); - assert.sameMembers(Array.from(selection0.getSelectedLines()), []); - - const selection1 = selection0.selectNextLine(); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [1]); - - const selection2 = selection1.addOrSubtractLineSelection(1).coalesce(); - assert.sameMembers(Array.from(selection2.getSelectedLines()), []); - - const selection3 = selection2.selectPreviousLine(); - assert.sameMembers(Array.from(selection3.getSelectedLines()), [8]); - }); - - it('collapses multiple selections down to one line when selecting next or previous', function() { - const patch = buildPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()) - .selectLine(1) - .addOrSubtractLineSelection(2) - .selectNextLine(true); - assert.sameMembers(Array.from(selection0.getSelectedLines()), [1, 2, 3]); - - const selection1 = selection0.selectNextLine(); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [6]); - - const selection2 = selection1.selectLine(1) - .addOrSubtractLineSelection(2) - .selectPreviousLine(true); - assert.sameMembers(Array.from(selection2.getSelectedLines()), [1, 2]); - - const selection3 = selection2.selectPreviousLine(); - assert.sameMembers(Array.from(selection3.getSelectedLines()), [1]); - }); - - describe('coalescing', function() { - it('merges overlapping selections', function() { - const patch = buildAllAddedPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()) - .selectLine(3) - .selectLine(5, true) - .addOrSubtractLineSelection(0) - .selectLine(4, true) - .coalesce() - .selectPreviousLine(true); - assert.sameMembers(Array.from(selection0.getSelectedLines()), [0, 1, 2, 3, 4]); - - const selection1 = selection0.addOrSubtractLineSelection(7) - .selectLine(3, true) - .coalesce() - .selectNextLine(true); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [1, 2, 3, 4, 5, 6, 7]); - }); - - it('merges adjacent selections', function() { - const patch = buildAllAddedPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()) - .selectLine(3) - .selectLine(5, true) - .addOrSubtractLineSelection(1) - .selectLine(2, true) - .coalesce() - .selectPreviousLine(true); - assert.sameMembers(Array.from(selection0.getSelectedLines()), [1, 2, 3, 4]); - - const selection1 = selection0.addOrSubtractLineSelection(6) - .selectLine(5, true) - .coalesce() - .selectNextLine(true); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [2, 3, 4, 5, 6]); - }); - - it('expands selections to contain all adjacent context lines', function() { - const patch = buildPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()) - .selectLine(7) - .selectLine(6, true) - .addOrSubtractLineSelection(2) - .selectLine(1, true) - .coalesce() - .selectNext(true); - - assert.sameMembers(Array.from(selection0.getSelectedLines()), [2, 6, 7]); - }); - - it('truncates or splits selections where they overlap a negative selection', function() { - const patch = buildAllAddedPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()) - .selectLine(0) - .selectLine(7, true) - .addOrSubtractLineSelection(3) - .selectLine(4, true) - .coalesce() - .selectPrevious(true); - assert.sameMembers(Array.from(selection0.getSelectedLines()), [0, 1, 2, 5, 6]); - }); - - it('does not blow up when coalescing with no selections', function() { - const patch = buildAllAddedPatchFixture(); - - const selection0 = new FilePatchSelection(patch.getHunks()) - .selectLine(0) - .addOrSubtractLineSelection(0); - assert.lengthOf(Array.from(selection0.getSelectedLines()), 0); - - const selection1 = selection0.coalesce(); - assert.lengthOf(Array.from(selection1.getSelectedLines()), 0); - }); - }); - }); - - describe('hunk selection', function() { - it('selects the first hunk by default', function() { - const hunks = buildPatchFixture().getHunks(); - const selection0 = new FilePatchSelection(hunks); - assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[0]]); - }); - - it('starts a new hunk selection with selectHunk and updates an existing selection when preserveTail is true', function() { - const hunks = buildFourHunksPatchFixture().getHunks(); - const selection0 = new FilePatchSelection(hunks) - .selectHunk(hunks[1]); - assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[1]]); - - const selection1 = selection0.selectHunk(hunks[3], true); - assert.sameMembers(Array.from(selection1.getSelectedHunks()), [hunks[1], hunks[2], hunks[3]]); - - const selection2 = selection1.selectHunk(hunks[0], true); - assert.sameMembers(Array.from(selection2.getSelectedHunks()), [hunks[0], hunks[1]]); - }); - - it('adds a new hunk selection with addOrSubtractHunkSelection and always updates the head of the most recent hunk selection', function() { - const hunks = buildFourHunksPatchFixture().getHunks(); - const selection0 = new FilePatchSelection(hunks) - .addOrSubtractHunkSelection(hunks[2]); - assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[0], hunks[2]]); - - const selection1 = selection0.selectHunk(hunks[3], true); - assert.sameMembers(Array.from(selection1.getSelectedHunks()), [hunks[0], hunks[2], hunks[3]]); - - const selection2 = selection1.selectHunk(hunks[1], true); - assert.sameMembers(Array.from(selection2.getSelectedHunks()), [hunks[0], hunks[1], hunks[2]]); - }); - - it('allows the next or previous hunk to be selected', function() { - const hunks = buildFourHunksPatchFixture().getHunks(); - const selection0 = new FilePatchSelection(hunks) - .selectNextHunk(); - assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[1]]); - - const selection1 = selection0.selectNextHunk(); - assert.sameMembers(Array.from(selection1.getSelectedHunks()), [hunks[2]]); - - const selection2 = selection1.selectNextHunk() - .selectNextHunk(); - assert.sameMembers(Array.from(selection2.getSelectedHunks()), [hunks[3]]); - - const selection3 = selection2.selectPreviousHunk(); - assert.sameMembers(Array.from(selection3.getSelectedHunks()), [hunks[2]]); - - const selection4 = selection3.selectPreviousHunk(); - assert.sameMembers(Array.from(selection4.getSelectedHunks()), [hunks[1]]); - - const selection5 = selection4.selectPreviousHunk() - .selectPreviousHunk(); - assert.sameMembers(Array.from(selection5.getSelectedHunks()), [hunks[0]]); - - const selection6 = selection5.selectNextHunk() - .selectNextHunk(true); - assert.sameMembers(Array.from(selection6.getSelectedHunks()), [hunks[1], hunks[2]]); - - const selection7 = selection6.selectPreviousHunk(true); - assert.sameMembers(Array.from(selection7.getSelectedHunks()), [hunks[1]]); - - const selection8 = selection7.selectPreviousHunk(true); - assert.sameMembers(Array.from(selection8.getSelectedHunks()), [hunks[0], hunks[1]]); - }); - - it('allows all hunks to be selected', function() { - const hunks = buildFourHunksPatchFixture().getHunks(); - - const selection0 = new FilePatchSelection(hunks) - .selectAllHunks(); - assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[0], hunks[1], hunks[2], hunks[3]]); - }); - }); - - describe('selection modes', function() { - it('allows the selection mode to be toggled between hunks and lines', function() { - const hunks = buildPatchFixture().getHunks(); - const selection0 = new FilePatchSelection(hunks); - - assert.strictEqual(selection0.getMode(), 'hunk'); - assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[0]]); - assert.sameMembers(Array.from(selection0.getSelectedLines()), [1, 2, 3]); - - const selection1 = selection0.selectNext(); - assert.strictEqual(selection1.getMode(), 'hunk'); - assert.sameMembers(Array.from(selection1.getSelectedHunks()), [hunks[1]]); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [6, 7, 8]); - - const selection2 = selection1.toggleMode(); - assert.strictEqual(selection2.getMode(), 'line'); - assert.sameMembers(Array.from(selection2.getSelectedHunks()), [hunks[1]]); - assert.sameMembers(Array.from(selection2.getSelectedLines()), [6]); - - const selection3 = selection2.selectNext(); - assert.sameMembers(Array.from(selection3.getSelectedHunks()), [hunks[1]]); - assert.sameMembers(Array.from(selection3.getSelectedLines()), [7]); - - const selection4 = selection3.toggleMode(); - assert.strictEqual(selection4.getMode(), 'hunk'); - assert.sameMembers(Array.from(selection4.getSelectedHunks()), [hunks[1]]); - assert.sameMembers(Array.from(selection4.getSelectedLines()), [6, 7, 8]); - - const selection5 = selection4.selectLine(1); - assert.strictEqual(selection5.getMode(), 'line'); - assert.sameMembers(Array.from(selection5.getSelectedHunks()), [hunks[0]]); - assert.sameMembers(Array.from(selection5.getSelectedLines()), [1]); - - const selection6 = selection5.selectHunk(hunks[1]); - assert.strictEqual(selection6.getMode(), 'hunk'); - assert.sameMembers(Array.from(selection6.getSelectedHunks()), [hunks[1]]); - assert.sameMembers(Array.from(selection6.getSelectedLines()), [6, 7, 8]); - }); - }); - - describe('updateHunks(hunks)', function() { - it('collapses the line selection to a single line following the previous selected range with the highest start index', function() { - const oldHunks = [ - new Hunk({ - oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 3, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 0})), - ], - }), - new Hunk({ - oldStartRow: 5, oldRowCount: 7, newStartRow: 5, newRowCount: 4, - rowRange: new IndexedRowRange({bufferRange: [[3, 0], [10, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [5, 0]], startOffset: 0, endOffset: 0})), - new Addition(new IndexedRowRange({bufferRange: [[6, 0], [8, 0]], startOffset: 0, endOffset: 0})), - new Deletion(new IndexedRowRange({bufferRange: [[9, 0], [10, 0]], startOffset: 0, endOffset: 0})), - ], - }), - ]; - - const selection0 = new FilePatchSelection(oldHunks) - .selectLine(5) - .selectLine(7, true); - - const newHunks = [ - new Hunk({ - oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 3, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 0})), - ], - }), - new Hunk({ - oldStartRow: 5, oldRowCount: 7, newStartRow: 3, newRowCount: 2, - rowRange: new IndexedRowRange({bufferRange: [[3, 0], [5, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Deletion(new IndexedRowRange({bufferRange: [[4, 0], [4, 0]], startOffset: 0, endOffset: 0})), - ], - }), - new Hunk({ - oldStartRow: 9, oldRowCount: 10, newStartRow: 3, newRowCount: 2, - rowRange: new IndexedRowRange({bufferRange: [[6, 0], [9, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[7, 0], [7, 0]], startOffset: 0, endOffset: 0})), - new Deletion(new IndexedRowRange({bufferRange: [[8, 0], [9, 0]], startOffset: 0, endOffset: 0})), - ], - }), - ]; - const selection1 = selection0.updateHunks(newHunks); - - assert.sameMembers(Array.from(selection1.getSelectedLines()), [7]); - }); - - it('collapses the line selection to the line preceding the previous selected line if it was the *last* line', function() { - const oldHunks = [ - new Hunk({ - oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 4, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [3, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [2, 0]], startOffset: 0, endOffset: 0})), - ], - }), - ]; - - const selection0 = new FilePatchSelection(oldHunks) - .selectLine(2); - - const newHunks = [ - new Hunk({ - oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 4, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [3, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 0})), - ], - }), - ]; - const selection1 = selection0.updateHunks(newHunks); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [1]); - }); - - it('updates the hunk selection if it exceeds the new length of the hunks list', function() { - const oldHunks = [ - new Hunk({ - oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 1, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0})), - ], - }), - new Hunk({ - oldStartRow: 5, oldRowCount: 0, newStartRow: 6, newRowCount: 1, - rowRange: new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 0, endOffset: 0})), - ], - }), - ]; - const selection0 = new FilePatchSelection(oldHunks) - .selectHunk(oldHunks[1]); - - const newHunks = [ - new Hunk({ - oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 1, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0})), - ], - }), - ]; - const selection1 = selection0.updateHunks(newHunks); - assert.sameMembers(Array.from(selection1.getSelectedHunks()), [newHunks[0]]); - }); - - it('deselects if updating with an empty hunk array', function() { - const oldHunks = [ - new Hunk({ - oldStartRow: 1, oldRowCount: 1, newStartRow: 1, newRowCount: 3, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [1, 0]], startOffset: 0, endOffset: 0})), - ], - }), - ]; - - const selection0 = new FilePatchSelection(oldHunks) - .selectLine(1) - .updateHunks([]); - assert.lengthOf(Array.from(selection0.getSelectedLines()), []); - }); - - it('resolves the getNextUpdatePromise the next time hunks are changed', async function() { - const hunk0 = new Hunk({ - oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 1, - rowRange: new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[0, 0], [0, 0]], startOffset: 0, endOffset: 0})), - ], - }); - const hunk1 = new Hunk({ - oldStartRow: 5, oldRowCount: 0, newStartRow: 6, newRowCount: 1, - rowRange: new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [1, 0]], startOffset: 0, endOffset: 0})), - ], - }); - const existingHunks = [hunk0, hunk1]; - const selection0 = new FilePatchSelection(existingHunks); - - let wasResolved = false; - const promise = selection0.getNextUpdatePromise().then(() => { wasResolved = true; }); - - const unchangedHunks = [hunk0, hunk1]; - const selection1 = selection0.updateHunks(unchangedHunks); - - assert.isFalse(wasResolved); - - const hunk2 = new Hunk({ - oldStartRow: 6, oldRowCount: 1, newStartRow: 4, newRowCount: 3, - rowRange: new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 0, endOffset: 0}), - changes: [ - new Addition(new IndexedRowRange({bufferRange: [[1, 0], [2, 0]], startOffset: 0, endOffset: 0})), - ], - }); - const changedHunks = [hunk0, hunk2]; - selection1.updateHunks(changedHunks); - - await promise; - assert.isTrue(wasResolved); - }); - }); - - describe('jumpToNextHunk() and jumpToPreviousHunk()', function() { - it('selects the next/previous hunk', function() { - const hunks = buildThreeHunkPatchFixture().getHunks(); - const selection0 = new FilePatchSelection(hunks); - - // in hunk mode, selects the entire next/previous hunk - assert.strictEqual(selection0.getMode(), 'hunk'); - assert.sameMembers(Array.from(selection0.getSelectedHunks()), [hunks[0]]); - - const selection1 = selection0.jumpToNextHunk(); - assert.sameMembers(Array.from(selection1.getSelectedHunks()), [hunks[1]]); - - const selection2 = selection1.jumpToNextHunk(); - assert.sameMembers(Array.from(selection2.getSelectedHunks()), [hunks[2]]); - - const selection3 = selection2.jumpToNextHunk(); - assert.sameMembers(Array.from(selection3.getSelectedHunks()), [hunks[2]]); - - const selection4 = selection3.jumpToPreviousHunk(); - assert.sameMembers(Array.from(selection4.getSelectedHunks()), [hunks[1]]); - - const selection5 = selection4.jumpToPreviousHunk(); - assert.sameMembers(Array.from(selection5.getSelectedHunks()), [hunks[0]]); - - const selection6 = selection5.jumpToPreviousHunk(); - assert.sameMembers(Array.from(selection6.getSelectedHunks()), [hunks[0]]); - - // in line selection mode, the first changed line of the next/previous hunk is selected - const selection7 = selection6.toggleMode(); - assert.strictEqual(selection7.getMode(), 'line'); - assert.sameMembers(Array.from(selection7.getSelectedLines()), [1]); - - const selection8 = selection7.jumpToNextHunk(); - assert.sameMembers(Array.from(selection8.getSelectedLines()), [6]); - - const selection9 = selection8.jumpToNextHunk(); - assert.sameMembers(Array.from(selection9.getSelectedLines()), [12]); - - const selection10 = selection9.jumpToNextHunk(); - assert.sameMembers(Array.from(selection10.getSelectedLines()), [12]); - - const selection11 = selection10.jumpToPreviousHunk(); - assert.sameMembers(Array.from(selection11.getSelectedLines()), [6]); - - const selection12 = selection11.jumpToPreviousHunk(); - assert.sameMembers(Array.from(selection12.getSelectedLines()), [1]); - - const selection13 = selection12.jumpToPreviousHunk(); - assert.sameMembers(Array.from(selection13.getSelectedLines()), [1]); - }); - }); - - describe('goToDiffLine(lineNumber)', function() { - it('selects the closest selectable hunk line', function() { - const hunks = buildPatchFixture().getHunks(); - - const selection0 = new FilePatchSelection(hunks); - const selection1 = selection0.goToDiffLine(11); - assert.sameMembers(Array.from(selection1.getSelectedLines()), [1]); - - const selection2 = selection1.goToDiffLine(26); - assert.sameMembers(Array.from(selection2.getSelectedLines()), [7]); - - // selects closest added hunk line - const selection3 = selection2.goToDiffLine(27); - assert.sameMembers(Array.from(selection3.getSelectedLines()), [7]); - - const selection4 = selection3.goToDiffLine(18); - assert.sameMembers(Array.from(selection4.getSelectedLines()), [1]); - - const selection5 = selection4.goToDiffLine(19); - assert.sameMembers(Array.from(selection5.getSelectedLines()), [6]); - }); - }); -}); - -function buildPatchFixture() { - return buildFilePatch([{ - oldPath: 'a.txt', - oldMode: '100644', - newPath: 'a.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 10, - oldLineCount: 4, - newStartLine: 10, - newLineCount: 3, - lines: [ - ' 0000', - '+0001', - '-0002', - '-0003', - ' 0004', - ], - }, - { - oldStartLine: 25, - oldLineCount: 3, - newStartLine: 24, - newLineCount: 4, - lines: [ - ' 0005', - '+0006', - '+0007', - '-0008', - ' 0009', - ], - }, - ], - }]); -} - -function buildThreeHunkPatchFixture() { - return buildFilePatch([{ - oldPath: 'a.txt', - oldMode: '100644', - newPath: 'a.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 10, - oldLineCount: 4, - newStartLine: 10, - newLineCount: 3, - lines: [ - ' 0000', - '+0001', - '-0002', - '-0003', - ' 0004', - ], - }, - { - oldStartLine: 25, - oldLineCount: 3, - newStartLine: 24, - newLineCount: 4, - lines: [ - ' 0005', - '+0006', - '+0007', - '-0008', - ' 0009', - ], - }, - { - oldStartLine: 40, - oldLineCount: 5, - newStartLine: 44, - newLineCount: 3, - lines: [ - ' 0010', - ' 0011', - '-0012', - '-0013', - ' 0014', - ], - }, - ], - }]); -} - -function buildAllAddedPatchFixture() { - return buildFilePatch([{ - oldPath: 'a.txt', - oldMode: '100644', - newPath: 'a.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 1, - oldLineCount: 0, - newStartLine: 1, - newLineCount: 4, - lines: [ - '+0000', - '+0001', - '+0002', - '+0003', - ], - }, - { - oldStartLine: 1, - oldLineCount: 0, - newStartLine: 5, - newLineCount: 4, - lines: [ - '+0004', - '+0005', - '+0006', - '+0007', - ], - }, - ], - }]); -} - -function buildFourHunksPatchFixture() { - return buildFilePatch([{ - oldPath: 'a.txt', - oldMode: '100644', - newPath: 'a.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 1, - oldLineCount: 0, - newStartLine: 1, - newLineCount: 1, - lines: [ - '+0000', - ], - }, - { - oldStartLine: 2, - oldLineCount: 0, - newStartLine: 2, - newLineCount: 1, - lines: [ - '+0001', - ], - }, - { - oldStartLine: 3, - oldLineCount: 0, - newStartLine: 3, - newLineCount: 1, - lines: [ - '+0002', - ], - }, - { - oldStartLine: 4, - oldLineCount: 0, - newStartLine: 4, - newLineCount: 1, - lines: [ - '+0004', - ], - }, - ], - }]); -} diff --git a/test/models/indexed-row-range.test.js b/test/models/indexed-row-range.test.js deleted file mode 100644 index a87ee5c1fc..0000000000 --- a/test/models/indexed-row-range.test.js +++ /dev/null @@ -1,216 +0,0 @@ -import IndexedRowRange, {nullIndexedRowRange} from '../../lib/models/indexed-row-range'; - -describe('IndexedRowRange', function() { - it('computes its row count', function() { - const range = new IndexedRowRange({ - bufferRange: [[0, 0], [1, Infinity]], - startOffset: 0, - endOffset: 10, - }); - assert.isTrue(range.isPresent()); - assert.deepEqual(range.bufferRowCount(), 2); - }); - - it('returns its starting buffer row', function() { - const range = new IndexedRowRange({ - bufferRange: [[2, 0], [8, Infinity]], - startOffset: 0, - endOffset: 10, - }); - assert.strictEqual(range.getStartBufferRow(), 2); - }); - - it('returns its ending buffer row', function() { - const range = new IndexedRowRange({ - bufferRange: [[2, 0], [8, Infinity]], - startOffset: 0, - endOffset: 10, - }); - assert.strictEqual(range.getEndBufferRow(), 8); - }); - - it('returns an array of the covered rows', function() { - const range = new IndexedRowRange({ - bufferRange: [[2, 0], [8, Infinity]], - startOffset: 0, - endOffset: 10, - }); - assert.sameMembers(range.getBufferRows(), [2, 3, 4, 5, 6, 7, 8]); - }); - - it('has a buffer row inclusion predicate', function() { - const range = new IndexedRowRange({ - bufferRange: [[2, 0], [4, Infinity]], - startOffset: 0, - endOffset: 10, - }); - - assert.isFalse(range.includesRow(1)); - assert.isTrue(range.includesRow(2)); - assert.isTrue(range.includesRow(3)); - assert.isTrue(range.includesRow(4)); - assert.isFalse(range.includesRow(5)); - }); - - it('extracts its offset range from buffer text with toStringIn()', function() { - const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n'; - const range = new IndexedRowRange({ - bufferRange: [[1, 0], [2, Infinity]], - startOffset: 5, - endOffset: 25, - }); - - assert.strictEqual(range.toStringIn(buffer, '+'), '+1111\n+2222\n+3333\n+4444\n'); - assert.strictEqual(range.toStringIn(buffer, '-'), '-1111\n-2222\n-3333\n-4444\n'); - }); - - describe('intersectRowsIn()', function() { - const buffer = '0000\n1111\n2222\n3333\n4444\n5555\n6666\n7777\n8888\n9999\n'; - // 0000.1111.2222.3333.4444.5555.6666.7777.8888.9999. - - function assertIntersections(actual, expected) { - const serialized = actual.map(({intersection, gap}) => ({intersection: intersection.serialize(), gap})); - assert.deepEqual(serialized, expected); - } - - it('returns an array containing all gaps with no intersection rows', function() { - const range = new IndexedRowRange({ - bufferRange: [[1, 0], [3, Infinity]], - startOffset: 5, - endOffset: 20, - }); - - assertIntersections(range.intersectRowsIn(new Set([0, 5, 6]), buffer, false), []); - assertIntersections(range.intersectRowsIn(new Set([0, 5, 6]), buffer, true), [ - {intersection: {bufferRange: [[1, 0], [3, Infinity]], startOffset: 5, endOffset: 20}, gap: true}, - ]); - }); - - it('detects an intersection at the beginning of the range', function() { - const range = new IndexedRowRange({ - bufferRange: [[2, 0], [6, Infinity]], - startOffset: 10, - endOffset: 35, - }); - const rowSet = new Set([0, 1, 2, 3]); - - assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ - {intersection: {bufferRange: [[2, 0], [3, Infinity]], startOffset: 10, endOffset: 20}, gap: false}, - ]); - assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ - {intersection: {bufferRange: [[2, 0], [3, Infinity]], startOffset: 10, endOffset: 20}, gap: false}, - {intersection: {bufferRange: [[4, 0], [6, Infinity]], startOffset: 20, endOffset: 35}, gap: true}, - ]); - }); - - it('detects an intersection in the middle of the range', function() { - const range = new IndexedRowRange({ - bufferRange: [[2, 0], [6, Infinity]], - startOffset: 10, - endOffset: 35, - }); - const rowSet = new Set([0, 3, 4, 8, 9]); - - assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ - {intersection: {bufferRange: [[3, 0], [4, Infinity]], startOffset: 15, endOffset: 25}, gap: false}, - ]); - assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ - {intersection: {bufferRange: [[2, 0], [2, Infinity]], startOffset: 10, endOffset: 15}, gap: true}, - {intersection: {bufferRange: [[3, 0], [4, Infinity]], startOffset: 15, endOffset: 25}, gap: false}, - {intersection: {bufferRange: [[5, 0], [6, Infinity]], startOffset: 25, endOffset: 35}, gap: true}, - ]); - }); - - it('detects an intersection at the end of the range', function() { - const range = new IndexedRowRange({ - bufferRange: [[2, 0], [6, Infinity]], - startOffset: 10, - endOffset: 35, - }); - const rowSet = new Set([4, 5, 6, 7, 10, 11]); - - assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ - {intersection: {bufferRange: [[4, 0], [6, Infinity]], startOffset: 20, endOffset: 35}, gap: false}, - ]); - assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ - {intersection: {bufferRange: [[2, 0], [3, Infinity]], startOffset: 10, endOffset: 20}, gap: true}, - {intersection: {bufferRange: [[4, 0], [6, Infinity]], startOffset: 20, endOffset: 35}, gap: false}, - ]); - }); - - it('detects multiple intersections', function() { - const range = new IndexedRowRange({ - bufferRange: [[2, 0], [8, Infinity]], - startOffset: 10, - endOffset: 45, - }); - const rowSet = new Set([0, 3, 4, 6, 7, 10]); - - assertIntersections(range.intersectRowsIn(rowSet, buffer, false), [ - {intersection: {bufferRange: [[3, 0], [4, Infinity]], startOffset: 15, endOffset: 25}, gap: false}, - {intersection: {bufferRange: [[6, 0], [7, Infinity]], startOffset: 30, endOffset: 40}, gap: false}, - ]); - assertIntersections(range.intersectRowsIn(rowSet, buffer, true), [ - {intersection: {bufferRange: [[2, 0], [2, Infinity]], startOffset: 10, endOffset: 15}, gap: true}, - {intersection: {bufferRange: [[3, 0], [4, Infinity]], startOffset: 15, endOffset: 25}, gap: false}, - {intersection: {bufferRange: [[5, 0], [5, Infinity]], startOffset: 25, endOffset: 30}, gap: true}, - {intersection: {bufferRange: [[6, 0], [7, Infinity]], startOffset: 30, endOffset: 40}, gap: false}, - {intersection: {bufferRange: [[8, 0], [8, Infinity]], startOffset: 40, endOffset: 45}, gap: true}, - ]); - }); - - it('returns an empty array for the null range', function() { - assertIntersections(nullIndexedRowRange.intersectRowsIn(new Set([1, 2, 3]), buffer, true), []); - assertIntersections(nullIndexedRowRange.intersectRowsIn(new Set([1, 2, 3]), buffer, false), []); - }); - }); - - describe('offsetBy()', function() { - let original; - - beforeEach(function() { - original = new IndexedRowRange({ - bufferRange: [[3, 0], [5, Infinity]], - startOffset: 15, - endOffset: 25, - }); - }); - - it('returns the receiver as-is when there is no change', function() { - assert.strictEqual(original.offsetBy(0, 0), original); - }); - - it('modifies the buffer range and the buffer offset', function() { - const changed = original.offsetBy(10, 3); - assert.deepEqual(changed.serialize(), { - bufferRange: [[6, 0], [8, Infinity]], - startOffset: 25, - endOffset: 35, - }); - }); - - it('may specify separate start and end offsets', function() { - const changed = original.offsetBy(10, 2, 30, 4); - assert.deepEqual(changed.serialize(), { - bufferRange: [[5, 0], [9, Infinity]], - startOffset: 25, - endOffset: 55, - }); - }); - - it('is a no-op on a nullIndexedRowRange', function() { - assert.strictEqual(nullIndexedRowRange.offsetBy(100, 200), nullIndexedRowRange); - }); - }); - - it('returns appropriate values from nullIndexedRowRange methods', function() { - assert.isNull(nullIndexedRowRange.getStartBufferRow()); - assert.lengthOf(nullIndexedRowRange.getBufferRows(), 0); - assert.strictEqual(nullIndexedRowRange.bufferRowCount(), 0); - assert.isFalse(nullIndexedRowRange.includesRow(4)); - assert.strictEqual(nullIndexedRowRange.toStringIn('', '+'), ''); - assert.deepEqual(nullIndexedRowRange.intersectRowsIn(new Set([0, 1, 2]), ''), []); - assert.isNull(nullIndexedRowRange.serialize()); - assert.isFalse(nullIndexedRowRange.isPresent()); - }); -}); From bb8c038c3d806eeacf08d975743a8176087baeff Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 14 Sep 2018 08:50:59 -0400 Subject: [PATCH 0216/4053] Adopt a Buffer from a previous Patch --- lib/models/patch/hunk.js | 6 ++- lib/models/patch/patch.js | 32 +++++++++++++ lib/models/patch/region.js | 4 ++ test/models/patch/hunk.test.js | 15 ++++++ test/models/patch/patch.test.js | 81 ++++++++++++++++++++++++++++++++ test/models/patch/region.test.js | 11 +++++ 6 files changed, 147 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 329257388d..c88c8e7c3d 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -1,5 +1,3 @@ -import {Unchanged} from './region'; - export default class Hunk { constructor({ oldStartRow, @@ -154,6 +152,10 @@ export default class Hunk { .reduce((count, change) => count + change.bufferRowCount(), 0); } + reMarkOn(markable) { + this.marker = markable.markRange(this.getRange(), {invalidate: 'never', exclusive: false}); + } + toStringIn(buffer) { let str = this.getHeader() + '\n'; for (const region of this.getRegions()) { diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 2689ace15b..7707719bf4 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -431,6 +431,38 @@ export default class Patch { return [[newSelectionRow, 0], [newSelectionRow, Infinity]]; } + adoptBufferFrom(lastPatch) { + lastPatch.getHunkLayer().clear(); + lastPatch.getUnchangedLayer().clear(); + lastPatch.getAdditionLayer().clear(); + lastPatch.getDeletionLayer().clear(); + lastPatch.getNoNewlineLayer().clear(); + + const nextBuffer = lastPatch.getBuffer(); + nextBuffer.setText(this.getBuffer().getText()); + + for (const hunk of this.getHunks()) { + hunk.reMarkOn(lastPatch.getHunkLayer()); + for (const region of hunk.getRegions()) { + const target = region.when({ + unchanged: () => lastPatch.getUnchangedLayer(), + addition: () => lastPatch.getAdditionLayer(), + deletion: () => lastPatch.getDeletionLayer(), + nonewline: () => lastPatch.getNoNewlineLayer(), + }); + region.reMarkOn(target); + } + } + + this.hunkLayer = lastPatch.getHunkLayer(); + this.unchangedLayer = lastPatch.getUnchangedLayer(); + this.additionLayer = lastPatch.getAdditionLayer(); + this.deletionLayer = lastPatch.getDeletionLayer(); + this.noNewlineLayer = lastPatch.getNoNewlineLayer(); + + this.buffer = nextBuffer; + } + toString() { return this.getHunks().reduce((str, hunk) => { str += hunk.toStringIn(this.getBuffer()); diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index b2b7b8fbf0..759f0e7200 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -97,6 +97,10 @@ class Region { return callback(); } + reMarkOn(markable) { + this.marker = markable.markRange(this.getRange(), {invalidate: 'never', exclusive: false}); + } + toStringIn(buffer) { return buffer.getTextInRange(this.getRange()).replace(/(^|\n)(?!$)/g, '$&' + this.constructor.origin); } diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index af05387442..9af6b19bf9 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -212,6 +212,21 @@ describe('Hunk', function() { assert.strictEqual(h1.getMaxLineNumberWidth(), 5); }); + it('creates a new marker on a different markable target', function() { + const h = new Hunk({ + ...attrs, + marker: buffer.markRange([[1, 0], [4, 4]]), + }); + + assert.strictEqual(h.getMarker().layer, buffer.getDefaultMarkerLayer()); + + const nextBuffer = new TextBuffer({text: buffer.getText()}); + h.reMarkOn(nextBuffer); + + assert.deepEqual(h.getRange().serialize(), [[1, 0], [4, 4]]); + assert.strictEqual(h.getMarker().layer, nextBuffer.getDefaultMarkerLayer()); + }); + describe('toStringIn()', function() { it('prints its header', function() { const h = new Hunk({ diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index b388e21df1..82f7ae118e 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -812,6 +812,87 @@ describe('Patch', function() { assert.deepEqual(nullPatch.getFirstChangeRange(), [[0, 0], [0, 0]]); assert.deepEqual(nullPatch.getNextSelectionRange(), [[0, 0], [0, 0]]); }); + + it('adopts a buffer from a previous patch', function() { + const patch0 = buildPatchFixture(); + const buffer0 = patch0.getBuffer(); + const hunkLayer0 = patch0.getHunkLayer(); + const unchangedLayer0 = patch0.getUnchangedLayer(); + const additionLayer0 = patch0.getAdditionLayer(); + const deletionLayer0 = patch0.getDeletionLayer(); + const noNewlineLayer0 = patch0.getNoNewlineLayer(); + + const buffer1 = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n No newline at end of file'}); + const layers1 = buildLayers(buffer1); + const hunks1 = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 2, newStartRow: 1, newRowCount: 3, + sectionHeading: '0', + marker: markRange(layers1.hunk, 0, 2), + regions: [ + new Unchanged(markRange(layers1.unchanged, 0)), + new Addition(markRange(layers1.addition, 1)), + new Unchanged(markRange(layers1.unchanged, 2)), + ], + }), + new Hunk({ + oldStartRow: 5, oldRowCount: 2, newStartRow: 1, newRowCount: 3, + sectionHeading: '0', + marker: markRange(layers1.hunk, 3, 5), + regions: [ + new Unchanged(markRange(layers1.unchanged, 3)), + new Deletion(markRange(layers1.deletion, 4)), + new NoNewline(markRange(layers1.noNewline, 5)), + ], + }), + ]; + + const patch1 = new Patch({status: 'modified', hunks: hunks1, buffer: buffer1, layers: layers1}); + + assert.notStrictEqual(patch1.getBuffer(), patch0.getBuffer()); + assert.notStrictEqual(patch1.getHunkLayer(), hunkLayer0); + assert.notStrictEqual(patch1.getUnchangedLayer(), unchangedLayer0); + assert.notStrictEqual(patch1.getAdditionLayer(), additionLayer0); + assert.notStrictEqual(patch1.getDeletionLayer(), deletionLayer0); + assert.notStrictEqual(patch1.getNoNewlineLayer(), noNewlineLayer0); + + patch1.adoptBufferFrom(patch0); + + assert.strictEqual(patch1.getBuffer(), buffer0); + + const markerRanges = [ + ['hunk', patch1.getHunkLayer(), hunkLayer0], + ['unchanged', patch1.getUnchangedLayer(), unchangedLayer0], + ['addition', patch1.getAdditionLayer(), additionLayer0], + ['deletion', patch1.getDeletionLayer(), deletionLayer0], + ['noNewline', patch1.getNoNewlineLayer(), noNewlineLayer0], + ].reduce((obj, [key, layer1, layer0]) => { + assert.strictEqual(layer1, layer0, `Layer ${key} not inherited`); + obj[key] = layer1.getMarkers().map(marker => marker.getRange().serialize()); + return obj; + }, {}); + + assert.deepEqual(markerRanges, { + hunk: [ + [[0, 0], [2, 4]], + [[3, 0], [5, 26]], + ], + unchanged: [ + [[0, 0], [0, 4]], + [[2, 0], [2, 4]], + [[3, 0], [3, 4]], + ], + addition: [ + [[1, 0], [1, 4]], + ], + deletion: [ + [[4, 0], [4, 4]], + ], + noNewline: [ + [[5, 0], [5, 26]], + ], + }); + }); }); function buildBuffer(lines, noNewline = false) { diff --git a/test/models/patch/region.test.js b/test/models/patch/region.test.js index 41f9877f34..2857158dea 100644 --- a/test/models/patch/region.test.js +++ b/test/models/patch/region.test.js @@ -29,6 +29,17 @@ describe('Regions', function() { assert.isTrue(addition.includesBufferRow(2)); }); + it('can be re-marked on a new markable target', function() { + assert.strictEqual(addition.getMarker().layer, buffer.getDefaultMarkerLayer()); + + const nextBuffer = new TextBuffer({text: buffer.getText()}); + const nextLayer = nextBuffer.addMarkerLayer(); + addition.reMarkOn(nextLayer); + + assert.strictEqual(addition.getMarker().layer, nextLayer); + assert.deepEqual(addition.getRange().serialize(), [[1, 0], [3, 4]]); + }); + it('can be recognized by the isAddition predicate', function() { assert.isTrue(addition.isAddition()); assert.isFalse(addition.isDeletion()); From 708ae89f8a43486b746478786d67c48c6b175f13 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 14 Sep 2018 08:51:14 -0400 Subject: [PATCH 0217/4053] Typo fix and test coverage for Patch --- lib/models/patch/patch.js | 2 +- test/models/patch/patch.test.js | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 7707719bf4..67cde2060d 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -48,7 +48,7 @@ class BufferBuilder { markHunkRange(range) { let finalRange = range; - if (this.hunkStartRowOffset !== 0 || this.offset !== 0) { + if (this.hunkStartOffset !== 0 || this.offset !== 0) { finalRange = finalRange.translate([this.hunkStartOffset, 0], [this.offset, 0]); } this.hunkRange = finalRange; diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 82f7ae118e..490bb5506f 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -151,7 +151,28 @@ describe('Patch', function() { }); describe('stage patch generation', function() { - it('creates a patch that applies selected lines from only a single hunk', function() { + it('creates a patch that applies selected lines from only the first hunk', function() { + const patch = buildPatchFixture(); + const stagePatch = patch.getStagePatchForLines(new Set([2, 3, 4, 5])); + // buffer rows: 0 1 2 3 4 5 6 + const expectedBufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n'; + assert.strictEqual(stagePatch.getBuffer().getText(), expectedBufferText); + assertInPatch(stagePatch).hunks( + { + startRow: 0, + endRow: 6, + header: '@@ -3,4 +3,6 @@', + regions: [ + {kind: 'unchanged', string: ' 0000\n 0001', range: [[0, 0], [1, 4]]}, + {kind: 'deletion', string: '-0002', range: [[2, 0], [2, 4]]}, + {kind: 'addition', string: '+0003\n+0004\n+0005', range: [[3, 0], [5, 4]]}, + {kind: 'unchanged', string: ' 0006', range: [[6, 0], [6, 4]]}, + ], + }, + ); + }); + + it('creates a patch that applies selected lines from a single non-first hunk', function() { const patch = buildPatchFixture(); const stagePatch = patch.getStagePatchForLines(new Set([8, 13, 14, 16])); // buffer rows: 0 1 2 3 4 5 6 7 8 9 From 426a177fb20dc8e1db17a4c057578e875a94490e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 14 Sep 2018 08:52:54 -0400 Subject: [PATCH 0218/4053] :shirt: Remove unused imports --- lib/models/patch/file-patch.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index cc0beb9798..20022d2ffd 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -1,6 +1,5 @@ import {nullFile} from './file'; -import Patch, {nullPatch} from './patch'; -import Hunk from './hunk'; +import {nullPatch} from './patch'; import {toGitPathSep} from '../../helpers'; export default class FilePatch { From efd24cbffb83da6461ab0b2a8de16376fcd1ca97 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 14 Sep 2018 09:11:50 -0400 Subject: [PATCH 0219/4053] Buffer adoption at the FilePatch layer --- lib/models/patch/file-patch.js | 27 ++++++++++ test/models/patch/file-patch.test.js | 80 ++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 20022d2ffd..e61b5d72d8 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -61,6 +61,27 @@ export default class FilePatch { return this.getPatch().getMaxLineNumberWidth(); } + getHunkLayer() { + return this.getPatch().getHunkLayer(); + } + + getUnchangedLayer() { + return this.getPatch().getUnchangedLayer(); + } + + getAdditionLayer() { + return this.getPatch().getAdditionLayer(); + } + + getDeletionLayer() { + return this.getPatch().getDeletionLayer(); + } + + getNoNewlineLayer() { + return this.getPatch().getNoNewlineLayer(); + } + + // TODO delete if unused getAdditionRanges() { return this.getHunks().reduce((acc, hunk) => { acc.push(...hunk.getAdditionRanges()); @@ -68,6 +89,7 @@ export default class FilePatch { }, []); } + // TODO delete if unused getDeletionRanges() { return this.getHunks().reduce((acc, hunk) => { acc.push(...hunk.getDeletionRanges()); @@ -75,6 +97,7 @@ export default class FilePatch { }, []); } + // TODO delete if unused getNoNewlineRanges() { const hunks = this.getHunks(); const lastHunk = hunks[hunks.length - 1]; @@ -90,6 +113,10 @@ export default class FilePatch { return [range]; } + adoptBufferFrom(prevFilePatch) { + this.getPatch().adoptBufferFrom(prevFilePatch.getPatch()); + } + didChangeExecutableMode() { if (!this.oldFile.isPresent() || !this.newFile.isPresent()) { return false; diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 00dc241e52..781ea478e7 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -152,6 +152,81 @@ describe('FilePatch', function() { ]); }); + it('adopts a buffer and layers from a prior FilePatch', function() { + const oldFile = new File({path: 'a.txt', mode: '100755'}); + const newFile = new File({path: 'b.txt', mode: '100755'}); + + const prevBuffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + const prevLayers = buildLayers(prevBuffer); + const prevHunks = [ + new Hunk({ + oldStartRow: 2, oldRowCount: 2, newStartRow: 2, newRowCount: 3, + marker: markRange(prevLayers.hunk, 0, 2), + regions: [ + new Unchanged(markRange(prevLayers.unchanged, 0)), + new Addition(markRange(prevLayers.addition, 1)), + new Unchanged(markRange(prevLayers.unchanged, 2)), + ], + }), + ]; + const prevPatch = new Patch({status: 'modified', hunks: prevHunks, buffer: prevBuffer, layers: prevLayers}); + const prevFilePatch = new FilePatch(oldFile, newFile, prevPatch); + + const nextBuffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n No newline at end of file'}); + const nextLayers = buildLayers(nextBuffer); + const nextHunks = [ + new Hunk({ + oldStartRow: 2, oldRowCount: 2, newStartRow: 2, newRowCount: 3, + marker: markRange(nextLayers.hunk, 0, 2), + regions: [ + new Unchanged(markRange(nextLayers.unchanged, 0)), + new Addition(markRange(nextLayers.addition, 1)), + new Unchanged(markRange(nextLayers.unchanged, 2)), + ], + }), + new Hunk({ + oldStartRow: 10, oldRowCount: 2, newStartRow: 11, newRowCount: 1, + marker: markRange(nextLayers.hunk, 3, 5), + regions: [ + new Unchanged(markRange(nextLayers.unchanged, 3)), + new Deletion(markRange(nextLayers.deletion, 4)), + new NoNewline(markRange(nextLayers.noNewline, 5)), + ], + }), + ]; + const nextPatch = new Patch({status: 'modified', hunks: nextHunks, buffer: nextBuffer, layers: nextLayers}); + const nextFilePatch = new FilePatch(oldFile, newFile, nextPatch); + + nextFilePatch.adoptBufferFrom(prevFilePatch); + + assert.strictEqual(nextFilePatch.getBuffer(), prevBuffer); + assert.strictEqual(nextFilePatch.getHunkLayer(), prevLayers.hunk); + assert.strictEqual(nextFilePatch.getUnchangedLayer(), prevLayers.unchanged); + assert.strictEqual(nextFilePatch.getAdditionLayer(), prevLayers.addition); + assert.strictEqual(nextFilePatch.getDeletionLayer(), prevLayers.deletion); + assert.strictEqual(nextFilePatch.getNoNewlineLayer(), prevLayers.noNewline); + + const rangesFrom = layer => layer.getMarkers().map(marker => marker.getRange().serialize()); + assert.deepEqual(rangesFrom(nextFilePatch.getHunkLayer()), [ + [[0, 0], [2, 4]], + [[3, 0], [5, 26]], + ]); + assert.deepEqual(rangesFrom(nextFilePatch.getUnchangedLayer()), [ + [[0, 0], [0, 4]], + [[2, 0], [2, 4]], + [[3, 0], [3, 4]], + ]); + assert.deepEqual(rangesFrom(nextFilePatch.getAdditionLayer()), [ + [[1, 0], [1, 4]], + ]); + assert.deepEqual(rangesFrom(nextFilePatch.getDeletionLayer()), [ + [[4, 0], [4, 4]], + ]); + assert.deepEqual(rangesFrom(nextFilePatch.getNoNewlineLayer()), [ + [[5, 0], [5, 26]], + ]); + }); + describe('file-level change detection', function() { let emptyPatch; @@ -736,6 +811,11 @@ describe('FilePatch', function() { assert.lengthOf(nullFilePatch.getAdditionRanges(), 0); assert.lengthOf(nullFilePatch.getDeletionRanges(), 0); assert.lengthOf(nullFilePatch.getNoNewlineRanges(), 0); + assert.lengthOf(nullFilePatch.getHunkLayer().getMarkers(), 0); + assert.lengthOf(nullFilePatch.getUnchangedLayer().getMarkers(), 0); + assert.lengthOf(nullFilePatch.getAdditionLayer().getMarkers(), 0); + assert.lengthOf(nullFilePatch.getDeletionLayer().getMarkers(), 0); + assert.lengthOf(nullFilePatch.getNoNewlineLayer().getMarkers(), 0); assert.isFalse(nullFilePatch.didChangeExecutableMode()); assert.isFalse(nullFilePatch.hasSymlink()); assert.isFalse(nullFilePatch.hasTypechange()); From 997036a471d9d36bad1a7802b4239d4e24828a98 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 14 Sep 2018 09:24:22 -0400 Subject: [PATCH 0220/4053] Implement getHunkAt() on Patch and FilePatch --- lib/models/patch/file-patch.js | 4 +++ lib/models/patch/patch.js | 7 ++++++ test/models/patch/file-patch.test.js | 2 ++ test/models/patch/patch.test.js | 37 ++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index e61b5d72d8..5c6847b4d2 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -53,6 +53,10 @@ export default class FilePatch { return this.getPatch().getByteSize(); } + getHunkAt(bufferRow) { + return this.getPatch().getHunkAt(bufferRow); + } + getBuffer() { return this.getPatch().getBuffer(); } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 67cde2060d..c0ea0d402b 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -113,6 +113,7 @@ export default class Patch { this.deletionLayer = layers.deletion; this.noNewlineLayer = layers.noNewline; + this.hunksByMarker = new Map(this.getHunks().map(hunk => [hunk.getMarker(), hunk])); this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); } @@ -161,6 +162,11 @@ export default class Patch { return lastHunk ? lastHunk.getMaxLineNumberWidth() : 0; } + getHunkAt(bufferRow) { + const [marker] = this.hunkLayer.findMarkers({intersectsRow: bufferRow}); + return this.hunksByMarker.get(marker); + } + clone(opts = {}) { return new this.constructor({ status: opts.status !== undefined ? opts.status : this.getStatus(), @@ -461,6 +467,7 @@ export default class Patch { this.noNewlineLayer = lastPatch.getNoNewlineLayer(); this.buffer = nextBuffer; + this.hunksByMarker = new Map(this.getHunks().map(hunk => [hunk.getMarker(), hunk])); } toString() { diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 781ea478e7..bfe3333c15 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -40,6 +40,8 @@ describe('FilePatch', function() { assert.strictEqual(filePatch.getBuffer().getText(), '0000\n0001\n0002\n'); assert.strictEqual(filePatch.getMaxLineNumberWidth(), 1); + assert.strictEqual(filePatch.getHunkAt(1), hunks[0]); + assert.deepEqual(filePatch.getFirstChangeRange(), [[1, 0], [1, Infinity]]); const nBuffer = new TextBuffer({text: '0001\n0002\n'}); diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 490bb5506f..4c625313a0 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -90,6 +90,43 @@ describe('Patch', function() { assert.strictEqual(p1.getMaxLineNumberWidth(), 0); }); + it('accesses the Hunk at a buffer row', function() { + const buffer = buildBuffer(8); + const layers = buildLayers(buffer); + const hunk0 = new Hunk({ + oldStartRow: 1, oldRowCount: 4, newStartRow: 1, newRowCount: 4, + marker: markRange(layers.hunk, 0, 3), + regions: [ + new Unchanged(markRange(layers.unchanged, 0)), + new Addition(markRange(layers.addition, 1)), + new Deletion(markRange(layers.deletion, 2)), + new Unchanged(markRange(layers.unchanged, 3)), + ], + }); + const hunk1 = new Hunk({ + oldStartRow: 10, oldRowCount: 4, newStartRow: 10, newRowCount: 4, + marker: markRange(layers.hunk, 4, 7), + regions: [ + new Unchanged(markRange(layers.unchanged, 4)), + new Deletion(markRange(layers.deletion, 5)), + new Addition(markRange(layers.addition, 6)), + new Unchanged(markRange(layers.unchanged, 7)), + ], + }); + const hunks = [hunk0, hunk1]; + const patch = new Patch({status: 'modified', hunks, buffer, layers}); + + assert.strictEqual(patch.getHunkAt(0), hunk0); + assert.strictEqual(patch.getHunkAt(1), hunk0); + assert.strictEqual(patch.getHunkAt(2), hunk0); + assert.strictEqual(patch.getHunkAt(3), hunk0); + assert.strictEqual(patch.getHunkAt(4), hunk1); + assert.strictEqual(patch.getHunkAt(5), hunk1); + assert.strictEqual(patch.getHunkAt(6), hunk1); + assert.strictEqual(patch.getHunkAt(7), hunk1); + assert.isUndefined(patch.getHunkAt(10)); + }); + it('clones itself with optionally overridden properties', function() { const buffer = new TextBuffer({text: 'bufferText'}); const layers = buildLayers(buffer); From 35238719e6bc8159f72579d8681d76cfb052a999 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 14 Sep 2018 10:26:26 -0400 Subject: [PATCH 0221/4053] Externally managed Markers and MarkerLayers --- lib/atom/marker-layer.js | 58 ++++++++++++++++++++++------------ lib/atom/marker.js | 34 ++++++++++++++------ test/atom/marker-layer.test.js | 36 ++++++++++++++++++--- test/atom/marker.test.js | 46 +++++++++++++++++++++++---- 4 files changed, 132 insertions(+), 42 deletions(-) diff --git a/lib/atom/marker-layer.js b/lib/atom/marker-layer.js index 8cfc381daa..714462aa35 100644 --- a/lib/atom/marker-layer.js +++ b/lib/atom/marker-layer.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Disposable} from 'event-kit'; +import {CompositeDisposable, Disposable} from 'event-kit'; import {autobind, extractProps} from '../helpers'; import RefHolder from '../models/ref-holder'; @@ -18,6 +18,7 @@ class BareMarkerLayer extends React.Component { static propTypes = { ...markerLayerProps, editor: PropTypes.object, + id: PropTypes.number, children: PropTypes.node, handleID: PropTypes.func, handleLayer: PropTypes.func, @@ -33,7 +34,9 @@ class BareMarkerLayer extends React.Component { autobind(this, 'createLayer'); - this.sub = new Disposable(); + this.subs = new CompositeDisposable(); + this.layerSub = new Disposable(); + this.layerHolder = new RefHolder(); this.state = { editorHolder: RefHolder.on(this.props.editor), @@ -76,32 +79,45 @@ class BareMarkerLayer extends React.Component { } componentWillUnmount() { - this.layerHolder.map(layer => layer.destroy()); - this.layerHolder.setter(null); - this.props.handleLayer(undefined); - this.sub.dispose(); + this.subs.dispose(); } observeEditor() { - this.sub.dispose(); - this.sub = this.state.editorHolder.observe(this.createLayer); + this.subs.dispose(); + this.subs = new CompositeDisposable(); + this.subs.add(this.state.editorHolder.observe(this.createLayer)); } createLayer() { - this.layerHolder.map(layer => layer.destroy()); - - const options = extractProps(this.props, markerLayerProps); - - this.layerHolder.setter( - this.state.editorHolder.map(editor => editor.addMarkerLayer(options)).getOr(null), - ); + this.subs.remove(this.layerSub); + this.layerSub.dispose(); + + this.state.editorHolder.map(editor => { + const options = extractProps(this.props, markerLayerProps); + let layer; + if (this.props.id !== undefined) { + layer = editor.getMarkerLayer(this.props.id); + if (!layer) { + throw new Error(`Invalid marker layer ID: ${this.props.id}`); + } + this.layerSub = new Disposable(); + } else { + layer = editor.addMarkerLayer(options); + this.layerSub = new Disposable(() => { + layer.destroy(); + this.props.handleLayer(undefined); + this.props.handleID(undefined); + }); + } + this.layerHolder.setter(layer); + + this.props.handleLayer(layer); + this.props.handleID(layer.id); + + this.subs.add(this.layerSub); - this.props.handleLayer(this.layerHolder.getOr(null)); - this.props.handleID(this.getID()); - } - - getID() { - return this.layerHolder.map(layer => layer.id).getOr(undefined); + return null; + }); } } diff --git a/lib/atom/marker.js b/lib/atom/marker.js index 05cbb3d989..e0f547a1e2 100644 --- a/lib/atom/marker.js +++ b/lib/atom/marker.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {CompositeDisposable} from 'event-kit'; +import {CompositeDisposable, Disposable} from 'event-kit'; import {autobind, extractProps} from '../helpers'; import {RefHolderPropType, RangePropType} from '../prop-types'; @@ -24,7 +24,8 @@ export const DecorableContext = React.createContext(); class BareMarker extends React.Component { static propTypes = { ...markerProps, - bufferRange: RangePropType.isRequired, + id: PropTypes.number, + bufferRange: RangePropType, markableHolder: RefHolderPropType, children: PropTypes.node, onDidChange: PropTypes.func, @@ -43,7 +44,9 @@ class BareMarker extends React.Component { autobind(this, 'createMarker', 'didChange'); + this.markerSubs = new CompositeDisposable(); this.subs = new CompositeDisposable(); + this.markerHolder = new RefHolder(); this.markerHolder.observe(marker => { this.props.handleMarker(marker); @@ -81,26 +84,37 @@ class BareMarker extends React.Component { } componentWillUnmount() { - this.markerHolder.map(marker => marker.destroy()); this.subs.dispose(); } observeMarkable() { this.subs.dispose(); - this.subs = new CompositeDisposable( - this.props.markableHolder.observe(this.createMarker), - ); + this.subs = new CompositeDisposable(); + this.subs.add(this.props.markableHolder.observe(this.createMarker)); } createMarker() { - this.markerHolder.map(marker => marker.destroy()); + this.markerSubs.dispose(); + this.markerSubs = new CompositeDisposable(); + this.subs.add(this.markerSubs); const options = extractProps(this.props, markerProps); this.props.markableHolder.map(markable => { - const marker = markable.markBufferRange(this.props.bufferRange, options); - - this.subs.add(marker.onDidChange(this.didChange)); + let marker; + + if (this.props.id !== undefined) { + marker = markable.getMarker(this.props.id); + if (!marker) { + throw new Error(`Invalid marker ID: ${this.props.id}`); + } + marker.setProperties(options); + } else { + marker = markable.markBufferRange(this.props.bufferRange, options); + this.markerSubs.add(new Disposable(() => marker.destroy())); + } + + this.markerSubs.add(marker.onDidChange(this.didChange)); this.markerHolder.setter(marker); this.props.handleID(marker.id); return null; diff --git a/test/atom/marker-layer.test.js b/test/atom/marker-layer.test.js index 1f140c3098..c068378f30 100644 --- a/test/atom/marker-layer.test.js +++ b/test/atom/marker-layer.test.js @@ -2,13 +2,15 @@ import React from 'react'; import {mount} from 'enzyme'; import MarkerLayer from '../../lib/atom/marker-layer'; +import RefHolder from '../../lib/models/ref-holder'; import AtomTextEditor from '../../lib/atom/atom-text-editor'; describe('MarkerLayer', function() { - let atomEnv, editor, layer, layerID; + let atomEnv, workspace, editor, layer, layerID; beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); + workspace = atomEnv.workspace; editor = await atomEnv.workspace.open(__filename); }); @@ -52,13 +54,39 @@ describe('MarkerLayer', function() { }); it('inherits an editor from a parent node', function() { - const wrapper = mount( - + const refEditor = new RefHolder(); + mount( + , ); - const theEditor = wrapper.instance().getModel(); + const theEditor = refEditor.get(); assert.isDefined(theEditor.getMarkerLayer(layerID)); }); + + describe('with an externally managed layer', function() { + it('locates its marker layer by ID', function() { + const external = editor.addMarkerLayer(); + const wrapper = mount(); + assert.strictEqual(wrapper.find('BareMarkerLayer').instance().layerHolder.get(), external); + }); + + it('locates a marker layer on the buffer', function() { + const external = editor.getBuffer().addMarkerLayer(); + const wrapper = mount(); + assert.strictEqual(wrapper.find('BareMarkerLayer').instance().layerHolder.get().bufferMarkerLayer, external); + }); + + it('fails on construction if its ID is invalid', function() { + assert.throws(() => mount(), /Invalid marker layer ID: 800/); + }); + + it('does not destroy its layer on unmount', function() { + const external = editor.addMarkerLayer(); + const wrapper = mount(); + wrapper.unmount(); + assert.isFalse(external.isDestroyed()); + }); + }); }); diff --git a/test/atom/marker.test.js b/test/atom/marker.test.js index 7bfc30f626..297ce3333a 100644 --- a/test/atom/marker.test.js +++ b/test/atom/marker.test.js @@ -4,13 +4,15 @@ import {Range} from 'atom'; import Marker from '../../lib/atom/marker'; import AtomTextEditor from '../../lib/atom/atom-text-editor'; +import RefHolder from '../../lib/models/ref-holder'; import MarkerLayer from '../../lib/atom/marker-layer'; describe('Marker', function() { - let atomEnv, editor, marker, markerID; + let atomEnv, workspace, editor, marker, markerID; beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); + workspace = atomEnv.workspace; editor = await atomEnv.workspace.open(__filename); }); @@ -88,30 +90,60 @@ describe('Marker', function() { }); it('marks an editor from a parent node', function() { - const wrapper = mount( - + const editorHolder = new RefHolder(); + mount( + , ); - const theEditor = wrapper.instance().getModel(); + const theEditor = editorHolder.get(); const theMarker = theEditor.getMarker(markerID); assert.isTrue(theMarker.getBufferRange().isEqual([[0, 0], [0, 0]])); }); it('marks a marker layer from a parent node', function() { let layerID; - const wrapper = mount( - + const editorHolder = new RefHolder(); + mount( + { layerID = id; }}> , ); - const theEditor = wrapper.instance().getModel(); + const theEditor = editorHolder.get(); const layer = theEditor.getMarkerLayer(layerID); const theMarker = layer.getMarker(markerID); assert.isTrue(theMarker.getBufferRange().isEqual([[0, 0], [0, 0]])); }); + + describe('with an externally managed marker', function() { + it('locates its marker by ID', function() { + const external = editor.markBufferRange([[0, 0], [0, 5]]); + const wrapper = mount(); + const instance = wrapper.find('BareMarker').instance(); + assert.strictEqual(instance.markerHolder.get(), external); + }); + + it('locates its marker on a parent MarkerLayer', function() { + const layer = editor.addMarkerLayer(); + const external = layer.markBufferRange([[0, 0], [0, 5]]); + const wrapper = mount(); + const instance = wrapper.find('BareMarker').instance(); + assert.strictEqual(instance.markerHolder.get(), external); + }); + + it('fails on construction if its ID is invalid', function() { + assert.throws(() => mount(), /Invalid marker ID: 67/); + }); + + it('does not destroy its marker on unmount', function() { + const external = editor.markBufferRange([[0, 0], [0, 5]]); + const wrapper = mount(); + wrapper.unmount(); + assert.isFalse(external.isDestroyed()); + }); + }); }); From bfcfca2bf7593be0ffc9dff241af6ac1c26b833c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 14 Sep 2018 12:04:38 -0400 Subject: [PATCH 0222/4053] Separate update and creation props --- lib/atom/atom-text-editor.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index a68686fea2..41d38ffd37 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -6,8 +6,7 @@ import RefHolder from '../models/ref-holder'; import {RefHolderPropType} from '../prop-types'; import {autobind, extractProps} from '../helpers'; -const editorProps = { - buffer: PropTypes.object, // FIXME make proptype more specific +const editorUpdateProps = { mini: PropTypes.bool, readOnly: PropTypes.bool, placeholderText: PropTypes.string, @@ -16,11 +15,16 @@ const editorProps = { autoWidth: PropTypes.bool, }; +const editorCreationProps = { + buffer: PropTypes.object, // FIXME make proptype more specific + ...editorUpdateProps, +}; + export const TextEditorContext = React.createContext(); export default class AtomTextEditor extends React.Component { static propTypes = { - ...editorProps, + ...editorCreationProps, workspace: PropTypes.object.isRequired, // FIXME make more specific @@ -65,7 +69,7 @@ export default class AtomTextEditor extends React.Component { } componentDidMount() { - const modelProps = extractProps(this.props, editorProps); + const modelProps = extractProps(this.props, editorCreationProps); this.refParent.map(element => { const editor = this.props.workspace.buildTextEditor(modelProps); @@ -83,7 +87,7 @@ export default class AtomTextEditor extends React.Component { } componentDidUpdate(prevProps) { - const modelProps = extractProps(this.props, editorProps); + const modelProps = extractProps(this.props, editorUpdateProps); this.getRefModel().map(editor => editor.update(modelProps)); } @@ -124,4 +128,8 @@ export default class AtomTextEditor extends React.Component { return this.refModel; } + + getModel() { + return this.getRefModel().getOr(undefined); + } } From 1c66ca98c84b7fcb7b5c6ae1d9ac861eeb12fb4e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 14 Sep 2018 12:04:56 -0400 Subject: [PATCH 0223/4053] FilePatchView tests are green again --- lib/views/file-patch-view.js | 171 +++++++++++++++-------------- test/views/file-patch-view.test.js | 74 ++++--------- 2 files changed, 108 insertions(+), 137 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 58242886b6..a720aacb81 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -33,6 +33,7 @@ export default class FilePatchView extends React.Component { selectedRows: PropTypes.object.isRequired, repository: PropTypes.object.isRequired, + workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, @@ -60,12 +61,8 @@ export default class FilePatchView extends React.Component { this.mouseSelectionInProgress = false; this.lastMouseMoveLine = null; this.nextSelectionMode = this.props.selectionMode; - this.hunksByMarkerID = new Map(); - this.hunkMarkerLayerHolder = new RefHolder(); this.refRoot = new RefHolder(); this.refEditor = new RefHolder(); - this.refAdditionLayer = new RefHolder(); - this.refDeletionLayer = new RefHolder(); this.subs = new CompositeDisposable(); } @@ -85,10 +82,10 @@ export default class FilePatchView extends React.Component { } else { this.nextSelectionMode = 'hunk'; const nextHunks = new Set( - Range.fromObject(newSelectionRange).getRows().map(row => this.getHunkAt(row)), + Range.fromObject(newSelectionRange).getRows().map(row => this.props.filePatch.getHunkAt(row)), ); editor.setSelectedBufferRanges( - Array.from(nextHunks, hunk => hunk.getBufferRange()), + Array.from(nextHunks, hunk => hunk.getRange()), ); } @@ -163,8 +160,9 @@ export default class FilePatchView extends React.Component { renderNonEmptyPatch() { return ( this.props.filePatch.getHunkAt(row)), + ); return ( - {/* - The markers on this layer are used to efficiently locate Hunks based on buffer row. - See .getHunkAt(). - */} - - {this.props.filePatch.getHunks().map((hunk, index) => { - return ( - { this.hunksByMarkerID.set(id, hunk); }} - /> - ); - })} - - {/* - These markers are decorated to position hunk headers as block decorations. - */} {this.props.filePatch.getHunks().map((hunk, index) => { - let buttonSuffix = ' Selected Change'; - if (this.props.selectedRows.size > 1) { - buttonSuffix += 's'; + const containsSelection = this.props.selectionMode === 'line' && selectedHunks.has(hunk); + const isSelected = this.props.selectionMode === 'hunk' && selectedHunks.has(hunk); + + let buttonSuffix = ''; + if (containsSelection) { + buttonSuffix += 'Selection'; + if (this.props.selectedRows.size > 1) { + buttonSuffix += 's'; + } + } else { + buttonSuffix += 'Hunk'; + if (selectedHunks.size > 1) { + buttonSuffix += 's'; + } } - const toggleSelectionLabel = `${toggleVerb}${buttonSuffix}`; - const discardSelectionLabel = `Discard${buttonSuffix}`; - const startPoint = hunk.getBufferRange().start; - const startRange = new Range(startPoint, startPoint); + const toggleSelectionLabel = `${toggleVerb} ${buttonSuffix}`; + const discardSelectionLabel = `Discard ${buttonSuffix}`; - const isSelected = this.refEditor.map(editor => { - return editor.getSelectedBufferRanges().some(range => range.containsRange(hunk.getBufferRange())); - }).getOr(false); + const startPoint = hunk.getRange().start; + const startRange = new Range(startPoint, startPoint); return ( @@ -396,7 +387,7 @@ export default class FilePatchView extends React.Component { tooltips={this.props.tooltips} - toggleSelection={this.props.toggleRows} + toggleSelection={() => this.toggleHunkSelection(hunk, containsSelection)} discardSelection={this.props.discardRows} mouseDown={this.didMouseDownOnHeader} /> @@ -426,8 +417,33 @@ export default class FilePatchView extends React.Component { /> ); })} + {this.renderDecorations(lineClass, {line, gutter})} + + ); + } + + renderDecorationsOnLayer(layer, lineClass, {line, gutter}) { + if (layer.getMarkerCount() === 0) { + return null; + } + + return ( + + {this.renderDecorations(lineClass, {line, gutter})} + + ); + } - {line && } + renderDecorations(lineClass, {line, gutter}) { + return ( + + {line && ( + + )} {gutter && ( )} - + ); } + toggleHunkSelection(hunk, containsSelection) { + if (containsSelection) { + return this.props.toggleRows(this.props.selectedRows, this.props.selectionMode); + } else { + const changeRows = new Set( + hunk.getChanges() + .reduce((rows, change) => { + rows.push(...change.getBufferRows()); + return rows; + }, []), + ); + return this.props.toggleRows(changeRows, 'hunk'); + } + } + didMouseDownOnHeader(event, hunk) { - this.handleSelectionEvent(event, hunk.getBufferRange()); + this.nextSelectionMode = 'hunk'; + this.handleSelectionEvent(event, hunk.getRange()); } didMouseDownOnLineNumber(event) { @@ -458,6 +490,7 @@ export default class FilePatchView extends React.Component { return; } + this.nextSelectionMode = 'line'; if (this.handleSelectionEvent(event.domEvent, [[line, 0], [line, Infinity]])) { this.mouseSelectionInProgress = true; } @@ -474,6 +507,7 @@ export default class FilePatchView extends React.Component { } this.lastMouseMoveLine = line; + this.nextSelectionMode = 'line'; this.handleSelectionEvent(event.domEvent, [[line, 0], [line, Infinity]], {add: true}); } @@ -593,7 +627,7 @@ export default class FilePatchView extends React.Component { for (const cursor of editor.getCursors()) { const cursorRow = cursor.getBufferPosition().row; - const hunk = this.getHunkAt(cursorRow); + const hunk = this.props.filePatch.getHunkAt(cursorRow); /* istanbul ignore next */ if (!hunk) { continue; @@ -676,7 +710,7 @@ export default class FilePatchView extends React.Component { } oldLineNumberLabel({bufferRow, softWrapped}) { - const hunk = this.getHunkAt(bufferRow); + const hunk = this.props.filePatch.getHunkAt(bufferRow); if (hunk === undefined) { return this.pad(''); } @@ -690,7 +724,7 @@ export default class FilePatchView extends React.Component { } newLineNumberLabel({bufferRow, softWrapped}) { - const hunk = this.getHunkAt(bufferRow); + const hunk = this.props.filePatch.getHunkAt(bufferRow); if (hunk === undefined) { return this.pad(''); } @@ -702,38 +736,9 @@ export default class FilePatchView extends React.Component { return this.pad(newRow); } - getHunkAt(bufferRow) { - const hunkFromMarker = this.hunkMarkerLayerHolder.map(layer => { - const markers = layer.findMarkers({intersectsRow: bufferRow}); - if (markers.length === 0) { - return null; - } - return this.hunksByMarkerID.get(markers[0].id); - }).getOr(null); - - if (hunkFromMarker !== null) { - return hunkFromMarker; - } - - // Fall back to a linear hunk scan. - for (const hunk of this.props.filePatch.getHunks()) { - if (hunk.includesBufferRow(bufferRow)) { - return hunk; - } - } - - // Hunk not found. - return undefined; - } - isChangeRow(bufferRow) { - for (const holder of [this.refAdditionLayer, this.refDeletionLayer]) { - const changeMarkers = holder.map(layer => layer.findMarkers({intersectsRow: bufferRow})).getOr([]); - if (changeMarkers.length !== 0) { - return true; - } - } - return false; + const changeLayers = [this.props.filePatch.getAdditionLayer(), this.props.filePatch.getDeletionLayer()]; + return changeLayers.some(layer => layer.findMarkers({intersectsRow: bufferRow}).length > 0); } withSelectionMode(callbacks) { diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 6188d9accd..0d4cac7b93 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -8,10 +8,11 @@ import {nullFile} from '../../lib/models/patch/file'; import {nullFilePatch} from '../../lib/models/patch/file-patch'; describe('FilePatchView', function() { - let atomEnv, repository, filePatch; + let atomEnv, workspace, repository, filePatch; beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); + workspace = atomEnv.workspace; const workdirPath = await cloneRepository(); repository = await buildRepository(workdirPath); @@ -52,6 +53,7 @@ describe('FilePatchView', function() { selectedRows: new Set(), repository, + workspace, commands: atomEnv.commands, tooltips: atomEnv.tooltips, @@ -81,7 +83,7 @@ describe('FilePatchView', function() { const wrapper = mount(buildApp()); const editor = wrapper.find('AtomTextEditor'); - assert.strictEqual(editor.instance().getModel().getText(), filePatch.getBufferText()); + assert.strictEqual(editor.instance().getModel().getText(), filePatch.getBuffer().getText()); }); it('preserves the selection index when a new file patch arrives in line selection mode', function() { @@ -111,7 +113,7 @@ describe('FilePatchView', function() { assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [3]); assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'line'); - const editor = wrapper.find('AtomTextEditor').getDOMNode().getModel(); + const editor = wrapper.find('AtomTextEditor').instance().getModel(); assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ [[3, 0], [3, 4]], ]); @@ -190,9 +192,9 @@ describe('FilePatchView', function() { assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [6, 7]); assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'hunk'); - const editor = wrapper.find('AtomTextEditor').getDOMNode().getModel(); + const editor = wrapper.find('AtomTextEditor').instance().getModel(); assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ - [[5, 0], [8, 3]], + [[5, 0], [8, 4]], ]); }); @@ -211,29 +213,6 @@ describe('FilePatchView', function() { assert.isTrue(window.removeEventListener.calledWith('mouseup', handler)); }); - it('locates hunks for a buffer row with or without markers', function() { - const [hunk0, hunk1] = filePatch.getHunks(); - const instance = mount(buildApp()).instance(); - - for (let i = 0; i <= 4; i++) { - assert.strictEqual(instance.getHunkAt(i), hunk0, `buffer row ${i} should retrieve hunk 0 with markers`); - } - for (let j = 5; j <= 8; j++) { - assert.strictEqual(instance.getHunkAt(j), hunk1, `buffer row ${j} should retrieve hunk 1 with markers`); - } - assert.isUndefined(instance.getHunkAt(9)); - - instance.hunkMarkerLayerHolder.map(layer => layer.destroy()); - - for (let i = 0; i <= 4; i++) { - assert.strictEqual(instance.getHunkAt(i), hunk0, `buffer row ${i} should retrieve hunk 0 without markers`); - } - for (let j = 5; j <= 8; j++) { - assert.strictEqual(instance.getHunkAt(j), hunk1, `buffer row ${j} should retrieve hunk 1 without markers`); - } - assert.isUndefined(instance.getHunkAt(9)); - }); - describe('executable mode changes', function() { it('does not render if the mode has not changed', function() { const fp = filePatch.clone({ @@ -476,7 +455,7 @@ describe('FilePatchView', function() { wrapper.find('HunkHeaderView').at(0).prop('toggleSelection')(); assert.sameMembers(Array.from(toggleRows.lastCall.args[0]), [2]); - assert.strictEqual(toggleRows.args[1], 'line'); + assert.strictEqual(toggleRows.lastCall.args[1], 'line'); }); it('handles a toggle click on a hunk not containing a selection', function() { @@ -485,7 +464,7 @@ describe('FilePatchView', function() { wrapper.find('HunkHeaderView').at(1).prop('toggleSelection')(); assert.sameMembers(Array.from(toggleRows.lastCall.args[0]), [6, 7]); - assert.strictEqual(toggleRows.args[1], 'hunk'); + assert.strictEqual(toggleRows.lastCall.args[1], 'hunk'); }); }); @@ -495,7 +474,7 @@ describe('FilePatchView', function() { beforeEach(function() { wrapper = mount(buildApp()); instance = wrapper.instance(); - editor = wrapper.find('AtomTextEditor').getDOMNode().getModel(); + editor = wrapper.find('AtomTextEditor').instance().getModel(); }); it('computes the old line number for a buffer row', function() { @@ -746,12 +725,7 @@ describe('FilePatchView', function() { assert.isTrue(decoration.exists()); const layer = wrapper.find('MarkerLayer').filterWhere(each => each.find(decorationSelector).exists()); - const markers = layer.find('Marker').map(marker => marker.prop('bufferRange').serialize()); - assert.deepEqual(markers, [ - [[1, 0], [2, 3]], - [[4, 0], [5, 3]], - [[12, 0], [14, 3]], - ]); + assert.strictEqual(layer.prop('id'), linesPatch.getAdditionLayer().id); }); it('decorates deleted lines', function() { @@ -762,12 +736,7 @@ describe('FilePatchView', function() { assert.isTrue(decoration.exists()); const layer = wrapper.find('MarkerLayer').filterWhere(each => each.find(decorationSelector).exists()); - const markers = layer.find('Marker').map(marker => marker.prop('bufferRange').serialize()); - assert.deepEqual(markers, [ - [[3, 0], [3, 3]], - [[8, 0], [10, 3]], - [[15, 0], [15, 3]], - ]); + assert.strictEqual(layer.prop('id'), linesPatch.getDeletionLayer().id); }); it('decorates the nonewline line', function() { @@ -778,23 +747,20 @@ describe('FilePatchView', function() { assert.isTrue(decoration.exists()); const layer = wrapper.find('MarkerLayer').filterWhere(each => each.find(decorationSelector).exists()); - const markers = layer.find('Marker').map(marker => marker.prop('bufferRange').serialize()); - assert.deepEqual(markers, [ - [[17, 0], [17, 25]], - ]); + assert.strictEqual(layer.prop('id'), linesPatch.getNoNewlineLayer().id); }); }); it('notifies a callback when the editor selection changes', function() { const selectedRowsChanged = sinon.spy(); const wrapper = mount(buildApp({selectedRowsChanged})); - const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); + const editor = wrapper.find('AtomTextEditor').instance().getModel(); selectedRowsChanged.resetHistory(); - editor.addSelectionForBufferRange([[3, 1], [4, 0]]); + editor.setSelectedBufferRange([[5, 1], [6, 2]]); - assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [3, 4]); + assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [6]); assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'line'); }); @@ -852,7 +818,7 @@ describe('FilePatchView', function() { const openFile = sinon.spy(); const wrapper = mount(buildApp({filePatch: fp, openFile})); - const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); + const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setCursorBufferPosition([7, 2]); atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); @@ -864,7 +830,7 @@ describe('FilePatchView', function() { const openFile = sinon.spy(); const wrapper = mount(buildApp({filePatch: fp, openFile})); - const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); + const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setCursorBufferPosition([8, 3]); atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); @@ -876,7 +842,7 @@ describe('FilePatchView', function() { const openFile = sinon.spy(); const wrapper = mount(buildApp({filePatch: fp, openFile})); - const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); + const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setCursorBufferPosition([9, 2]); atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); @@ -888,7 +854,7 @@ describe('FilePatchView', function() { const openFile = sinon.spy(); const wrapper = mount(buildApp({filePatch: fp, openFile})); - const editor = wrapper.find('atom-text-editor').getDOMNode().getModel(); + const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setCursorBufferPosition([3, 2]); editor.addCursorAtBufferPosition([4, 2]); editor.addCursorAtBufferPosition([1, 3]); From 491f4c4d834e2c4678da74d864331d90db2aaa82 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 14 Sep 2018 14:40:26 -0400 Subject: [PATCH 0224/4053] Adopt the buffer from the previous FilePatch --- lib/containers/file-patch-container.js | 12 +++++++- test/containers/file-patch-container.test.js | 31 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index 66fae68d49..ed61a19f54 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -23,8 +23,9 @@ export default class FilePatchContainer extends React.Component { constructor(props) { super(props); - autobind(this, 'fetchData', 'renderWithData'); + + this.lastFilePatch = null; } fetchData(repository) { @@ -44,9 +45,18 @@ export default class FilePatchContainer extends React.Component { renderWithData(data) { if (this.props.repository.isLoading() || data === null) { + this.lastFilePatch = null; return ; } + if (this.lastFilePatch !== data.filePatch) { + if (this.lastFilePatch) { + data.filePatch.adoptBufferFrom(this.lastFilePatch); + } + + this.lastFilePatch = data.filePatch; + } + return ( Date: Fri, 14 Sep 2018 14:40:40 -0400 Subject: [PATCH 0225/4053] Shuffle some lifecycle methods around --- lib/views/file-patch-view.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index a720aacb81..66caaa7290 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -67,6 +67,14 @@ export default class FilePatchView extends React.Component { this.subs = new CompositeDisposable(); } + componentDidMount() { + window.addEventListener('mouseup', this.didMouseUp); + this.refEditor.map(editor => { + editor.setSelectedBufferRange(this.props.filePatch.getFirstChangeRange()); + return null; + }); + } + componentDidUpdate(prevProps) { if (this.props.filePatch !== prevProps.filePatch) { // Heuristically adjust the editor selection based on the old file patch, the old row selection state, and @@ -96,14 +104,6 @@ export default class FilePatchView extends React.Component { } } - componentDidMount() { - window.addEventListener('mouseup', this.didMouseUp); - this.refEditor.map(editor => { - editor.setSelectedBufferRange(this.props.filePatch.getFirstChangeRange()); - return null; - }); - } - componentWillUnmount() { window.removeEventListener('mouseup', this.didMouseUp); this.subs.dispose(); From 803f58883f9d8803db88bb4c802db09b21c0c099 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Sep 2018 08:08:45 -0400 Subject: [PATCH 0226/4053] buildTextEditor() isn't happy, go back to new TextEditor() --- lib/atom/atom-text-editor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index 41d38ffd37..9f5c130d67 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -1,5 +1,6 @@ import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; +import {TextEditor} from 'atom'; import {CompositeDisposable} from 'event-kit'; import RefHolder from '../models/ref-holder'; @@ -72,7 +73,7 @@ export default class AtomTextEditor extends React.Component { const modelProps = extractProps(this.props, editorCreationProps); this.refParent.map(element => { - const editor = this.props.workspace.buildTextEditor(modelProps); + const editor = new TextEditor(modelProps); element.appendChild(editor.getElement()); this.getRefModel().setter(editor); this.refElement.setter(editor.getElement()); From 733000ea7236ecfb7628c4608557a5e90509505f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Sep 2018 08:09:09 -0400 Subject: [PATCH 0227/4053] Give the AtomTextEditor wrapper
a CSS class --- lib/atom/atom-text-editor.js | 2 +- styles/atom-text-editor.less | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 styles/atom-text-editor.less diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index 9f5c130d67..74939f83e6 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -61,7 +61,7 @@ export default class AtomTextEditor extends React.Component { render() { return ( -
+
{this.props.children} diff --git a/styles/atom-text-editor.less b/styles/atom-text-editor.less new file mode 100644 index 0000000000..3fe58fecf7 --- /dev/null +++ b/styles/atom-text-editor.less @@ -0,0 +1,5 @@ +// Our AtomTextEditor React component adds a parent
to the element. +.github-AtomTextEditor-container { + width: 100%; + height: 100%; +} From 3a2cb7d6b18fb309a1a3fc4323382625a02ea894 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Sep 2018 08:09:28 -0400 Subject: [PATCH 0228/4053] Gutters are sometimes disposed after their containing TextEditors --- lib/atom/gutter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/atom/gutter.js b/lib/atom/gutter.js index 713a337d92..f36b477a80 100644 --- a/lib/atom/gutter.js +++ b/lib/atom/gutter.js @@ -54,7 +54,11 @@ class BareGutter extends React.Component { componentWillUnmount() { if (this.state.gutter !== null) { - this.state.gutter.destroy(); + try { + this.state.gutter.destroy(); + } catch (e) { + // Gutter already destroyed. Disregard. + } } this.sub.dispose(); } From e4ddb96421cca5e54499cf86b16b8f8368a9f803 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Sep 2018 08:09:47 -0400 Subject: [PATCH 0229/4053] Marker and MarkerLayer IDs are strings --- lib/atom/marker-layer.js | 2 +- lib/atom/marker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/atom/marker-layer.js b/lib/atom/marker-layer.js index 714462aa35..0fb234dbf9 100644 --- a/lib/atom/marker-layer.js +++ b/lib/atom/marker-layer.js @@ -18,7 +18,7 @@ class BareMarkerLayer extends React.Component { static propTypes = { ...markerLayerProps, editor: PropTypes.object, - id: PropTypes.number, + id: PropTypes.string, children: PropTypes.node, handleID: PropTypes.func, handleLayer: PropTypes.func, diff --git a/lib/atom/marker.js b/lib/atom/marker.js index e0f547a1e2..4d1e6bd4aa 100644 --- a/lib/atom/marker.js +++ b/lib/atom/marker.js @@ -24,7 +24,7 @@ export const DecorableContext = React.createContext(); class BareMarker extends React.Component { static propTypes = { ...markerProps, - id: PropTypes.number, + id: PropTypes.string, bufferRange: RangePropType, markableHolder: RefHolderPropType, children: PropTypes.node, From 4544e191705d2ee0cbb3223c4f21fb827e933b48 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Sep 2018 08:10:45 -0400 Subject: [PATCH 0230/4053] The commit message editor uses a TextBuffer managed by CommitController --- lib/controllers/commit-controller.js | 29 +++++---- lib/views/commit-view.js | 22 ++++--- test/controllers/commit-controller.test.js | 21 +++--- test/views/commit-view.test.js | 75 +++++++++++----------- 4 files changed, 79 insertions(+), 68 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index cda915f2fe..20a7ab11db 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -1,4 +1,5 @@ import path from 'path'; +import {TextBuffer} from 'atom'; import React from 'react'; import PropTypes from 'prop-types'; @@ -45,10 +46,14 @@ export default class CommitController extends React.Component { this.subscriptions = new CompositeDisposable(); this.refCommitView = new RefHolder(); + + this.commitMessageBuffer = new TextBuffer({text: this.props.repository.getCommitMessage()}); + this.subscriptions.add( + this.commitMessageBuffer.onDidChange(this.handleMessageChange), + ); } - // eslint-disable-next-line camelcase - UNSAFE_componentWillMount() { + componentDidMount() { this.subscriptions.add( this.props.workspace.onDidAddTextEditor(({textEditor}) => { if (this.props.repository.isPresent() && textEditor.getPath() === this.getCommitMessagePath()) { @@ -63,7 +68,7 @@ export default class CommitController extends React.Component { this.getCommitMessageEditors().length === 0) { // we closed the last editor pointing to the commit message file try { - this.setCommitMessage(await fs.readFile(this.getCommitMessagePath(), {encoding: 'utf8'})); + this.commitMessageBuffer.setText(await fs.readFile(this.getCommitMessagePath(), {encoding: 'utf8'})); } catch (e) { if (e.code !== 'ENOENT') { throw e; @@ -74,17 +79,17 @@ export default class CommitController extends React.Component { ); if (this.props.isMerging && !this.getCommitMessage()) { - this.setCommitMessage(this.props.mergeMessage || ''); + this.commitMessageBuffer.setText(this.props.mergeMessage || ''); } } render() { - const message = this.getCommitMessage(); const operationStates = this.props.repository.getOperationStates(); return ( this.forceUpdate()), + this.props.messageBuffer.onDidChange(() => this.forceUpdate()), ); } @@ -164,7 +167,8 @@ export default class CommitView extends React.Component { showInvisibles={false} autoHeight={false} scrollPastEnd={false} - text={this.props.message} + buffer={this.props.messageBuffer} + workspace={this.props.workspace} didChange={this.didChangeCommitMessage} didChangeCursorPosition={this.didMoveCursor} /> @@ -282,7 +286,7 @@ export default class CommitView extends React.Component { } renderHardWrapIcon() { - const singleLineMessage = this.props.message.split(LINE_ENDING_REGEX).length === 1; + const singleLineMessage = this.props.messageBuffer.getText().split(LINE_ENDING_REGEX).length === 1; const hardWrap = this.props.config.get('github.automaticCommitMessageWrapping'); const notApplicable = this.props.deactivateCommitBox || singleLineMessage; @@ -361,7 +365,7 @@ export default class CommitView extends React.Component { } componentWillUnmount() { - this.subscriptions.dispose(); + this.subs.dispose(); } didChangeCommitMessage(editor) { @@ -413,7 +417,7 @@ export default class CommitView extends React.Component { async commit(event, amend) { if (await this.props.prepareToCommit() && this.commitIsEnabled(amend)) { try { - await this.props.commit(this.props.message, this.props.selectedCoAuthors, amend); + await this.props.commit(this.props.messageBuffer.getText(), this.props.selectedCoAuthors, amend); } catch (e) { // do nothing - error was taken care of in pipeline manager if (!atom.isReleasedVersion()) { @@ -462,7 +466,7 @@ export default class CommitView extends React.Component { } commitIsEnabled(amend) { - const messageExists = this.props.message.length > 0; + const messageExists = this.props.messageBuffer.getLength() > 0; return !this.props.isCommitting && (amend || this.props.stagedChangesExist) && !this.props.mergeConflictsExist && @@ -483,7 +487,7 @@ export default class CommitView extends React.Component { } toggleExpandedCommitMessageEditor() { - return this.props.toggleExpandedCommitMessageEditor(this.props.message); + return this.props.toggleExpandedCommitMessageEditor(this.props.messageBuffer.getText()); } matchAuthors(authors, filterText, selectedAuthors) { diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index e2e97cf57c..9fbea7e113 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -86,8 +86,8 @@ describe('CommitController', function() { it('is set to the getCommitMessage() in the default case', function() { repository.setCommitMessage('some message'); - const wrapper = shallow(app, {disableLifecycleMethods: true}); - assert.strictEqual(wrapper.find('CommitView').prop('message'), 'some message'); + const wrapper = shallow(app); + assert.strictEqual(wrapper.find('CommitView').prop('messageBuffer').getText(), 'some message'); }); it('does not cause the repository to update when commit message changes', function() { @@ -103,15 +103,15 @@ describe('CommitController', function() { describe('when a merge message is defined', function() { it('is set to the merge message when merging', function() { app = React.cloneElement(app, {isMerging: true, mergeMessage: 'merge conflict!'}); - const wrapper = shallow(app, {disableLifecycleMethods: true}); - assert.strictEqual(wrapper.find('CommitView').prop('message'), 'merge conflict!'); + const wrapper = shallow(app); + assert.strictEqual(wrapper.find('CommitView').prop('messageBuffer').getText(), 'merge conflict!'); }); it('is set to getCommitMessage() if it is set', function() { repository.setCommitMessage('some commit message'); app = React.cloneElement(app, {isMerging: true, mergeMessage: 'merge conflict!'}); const wrapper = shallow(app, {disableLifecycleMethods: true}); - assert.strictEqual(wrapper.find('CommitView').prop('message'), 'some commit message'); + assert.strictEqual(wrapper.find('CommitView').prop('messageBuffer').getText(), 'some commit message'); }); }); }); @@ -230,7 +230,7 @@ describe('CommitController', function() { describe('toggling between commit box and commit editor', function() { it('transfers the commit message contents of the last editor', async function() { - const wrapper = shallow(app, {disableLifecycleMethods: true}); + const wrapper = shallow(app); wrapper.find('CommitView').prop('toggleExpandedCommitMessageEditor')('message in box'); await assert.async.equal(workspace.getActiveTextEditor().getPath(), wrapper.instance().getCommitMessagePath()); @@ -250,7 +250,7 @@ describe('CommitController', function() { workspace.getActiveTextEditor().destroy(); assert.isTrue(wrapper.find('CommitView').prop('deactivateCommitBox')); - await assert.async.strictEqual(wrapper.update().find('CommitView').prop('message'), 'message in editor'); + await assert.async.strictEqual(wrapper.update().find('CommitView').prop('messageBuffer').getText(), 'message in editor'); }); it('activates editor if already opened but in background', async function() { @@ -270,7 +270,7 @@ describe('CommitController', function() { }); it('closes all open commit message editors if one is in the foreground of a pane, prompting for unsaved changes', async function() { - const wrapper = shallow(app, {disableLifecycleMethods: true}); + const wrapper = shallow(app); wrapper.find('CommitView').prop('toggleExpandedCommitMessageEditor')('sup'); await assert.async.strictEqual(workspace.getActiveTextEditor().getPath(), wrapper.instance().getCommitMessagePath()); @@ -295,7 +295,7 @@ describe('CommitController', function() { wrapper.find('CommitView').prop('toggleExpandedCommitMessageEditor')(); await assert.async.lengthOf(wrapper.instance().getCommitMessageEditors(), 0); assert.isTrue(atomEnvironment.applicationDelegate.confirm.called); - await assert.async.strictEqual(wrapper.update().find('CommitView').prop('message'), 'make some new changes'); + await assert.async.strictEqual(wrapper.update().find('CommitView').prop('messageBuffer').getText(), 'make some new changes'); }); }); @@ -366,7 +366,8 @@ describe('CommitController', function() { sinon.spy(view, 'hasFocus'); sinon.spy(view, 'hasFocusEditor'); - wrapper.instance().rememberFocus({target: wrapper.find('atom-text-editor').getDOMNode()}); + const element = wrapper.find('AtomTextEditor').getDOMNode().querySelector('atom-text-editor'); + wrapper.instance().rememberFocus({target: element}); assert.isTrue(view.rememberFocus.called); wrapper.instance().setFocus(CommitController.focus.EDITOR); diff --git a/test/views/commit-view.test.js b/test/views/commit-view.test.js index f88f15ea53..7666fa22dc 100644 --- a/test/views/commit-view.test.js +++ b/test/views/commit-view.test.js @@ -1,3 +1,4 @@ +import {TextBuffer} from 'atom'; import React from 'react'; import {shallow, mount} from 'enzyme'; @@ -13,6 +14,7 @@ import * as reporterProxy from '../../lib/reporter-proxy'; describe('CommitView', function() { let atomEnv, commandRegistry, tooltips, config, lastCommit; + let messageBuffer; let app; beforeEach(function() { @@ -26,8 +28,11 @@ describe('CommitView', function() { const returnTruthyPromise = () => Promise.resolve(true); const store = new UserStore({config}); + messageBuffer = new TextBuffer(); + app = ( @@ -56,8 +60,8 @@ describe('CommitView', function() { }); describe('amend', function() { it('increments a counter when amend is called', function() { + messageBuffer.setText('yo dawg I heard you like amending'); const wrapper = shallow(app); - wrapper.setProps({message: 'yo dawg I heard you like amending'}); sinon.stub(reporterProxy, 'incrementCounter'); wrapper.instance().amendLastCommit(); @@ -126,7 +130,7 @@ describe('CommitView', function() { }); it('disables the commit button', function() { - app = React.cloneElement(app, {message: 'even with text'}); + messageBuffer.setText('even with text'); const wrapper = shallow(app); assert.isTrue(wrapper.find('.github-CommitView-commit').prop('disabled')); @@ -137,46 +141,39 @@ describe('CommitView', function() { const wrapper = mount(app); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '72'); - // It takes two renders for the remaining characters field to update based on editor state. - // FIXME: make sure this doesn't regress in the actual component - wrapper.setProps({message: 'abcde fghij'}); - wrapper.setProps({}); + messageBuffer.setText('abcde fghij'); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '61'); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); - wrapper.setProps({message: '\nklmno'}); - wrapper.setProps({}); + messageBuffer.setText('\nklmno'); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '∞'); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); - wrapper.setProps({message: 'abcde\npqrst'}); - wrapper.setProps({}); + messageBuffer.setText('abcde\npqrst'); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '∞'); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); - wrapper.find('atom-text-editor').getDOMNode().getModel().setCursorBufferPosition([0, 3]); - wrapper.update(); + wrapper.find('AtomTextEditor').instance().getModel().setCursorBufferPosition([0, 3]); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '67'); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); wrapper.setProps({stagedChangesExist: true, maximumCharacterLimit: 50}); - wrapper.setProps({}); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '45'); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); - wrapper.setProps({message: 'a'.repeat(41)}); - wrapper.setProps({}); + messageBuffer.setText('a'.repeat(41)); + wrapper.update(); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '9'); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isTrue(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); - wrapper.setProps({message: 'a'.repeat(58)}).update(); - wrapper.setProps({}); + messageBuffer.setText('a'.repeat(58)); + wrapper.update(); assert.strictEqual(wrapper.find('.github-CommitView-remaining-characters').text(), '-8'); assert.isTrue(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-error')); assert.isFalse(wrapper.find('.github-CommitView-remaining-characters').hasClass('is-warning')); @@ -189,11 +186,11 @@ describe('CommitView', function() { const workdirPath = await cloneRepository('three-files'); const repository = await buildRepository(workdirPath); + messageBuffer.setText('something'); app = React.cloneElement(app, { repository, stagedChangesExist: true, mergeConflictsExist: false, - message: 'something', }); wrapper = mount(app); }); @@ -215,12 +212,12 @@ describe('CommitView', function() { }); it('is disabled when the commit message is empty', function() { - wrapper.setProps({message: ''}); - wrapper.setProps({}); + messageBuffer.setText(''); + wrapper.update(); assert.isTrue(wrapper.find('.github-CommitView-commit').prop('disabled')); - wrapper.setProps({message: 'Not empty'}); - wrapper.setProps({}); + messageBuffer.setText('Not empty'); + wrapper.update(); assert.isFalse(wrapper.find('.github-CommitView-commit').prop('disabled')); }); @@ -254,10 +251,11 @@ describe('CommitView', function() { const prepareToCommit = () => Promise.resolve(prepareToCommitResolution); commit = sinon.spy(); - app = React.cloneElement(app, {stagedChangesExist: true, prepareToCommit, commit, message: 'Something'}); + messageBuffer.setText('Something'); + app = React.cloneElement(app, {stagedChangesExist: true, prepareToCommit, commit}); wrapper = mount(app); - editorElement = wrapper.find('atom-text-editor').getDOMNode(); + editorElement = wrapper.find('AtomTextEditor').getDOMNode().querySelector('atom-text-editor'); sinon.spy(editorElement, 'focus'); editor = editorElement.getModel(); @@ -349,7 +347,7 @@ describe('CommitView', function() { it('detects when the editor has focus', function() { const wrapper = mount(app); - const editorNode = wrapper.find('atom-text-editor').getDOMNode(); + const editorNode = wrapper.find('AtomTextEditor').getDOMNode().querySelector('atom-text-editor'); sinon.stub(editorNode, 'contains').returns(true); assert.isTrue(wrapper.instance().hasFocusEditor()); @@ -367,14 +365,17 @@ describe('CommitView', function() { wrapper.update(); const foci = [ - ['atom-text-editor', CommitView.focus.EDITOR], + ['AtomTextEditor', CommitView.focus.EDITOR, 'atom-text-editor'], ['.github-CommitView-abortMerge', CommitView.focus.ABORT_MERGE_BUTTON], ['.github-CommitView-commit', CommitView.focus.COMMIT_BUTTON], ['.github-CommitView-coAuthorEditor input', CommitView.focus.COAUTHOR_INPUT], ]; - for (const [selector, focus] of foci) { - const event = {target: wrapper.find(selector).getDOMNode()}; - assert.strictEqual(wrapper.instance().rememberFocus(event), focus); + for (const [selector, focus, subselector] of foci) { + let target = wrapper.find(selector).getDOMNode(); + if (subselector) { + target = target.querySelector(subselector); + } + assert.strictEqual(wrapper.instance().rememberFocus({target}), focus); } assert.isNull(wrapper.instance().rememberFocus({target: document.body})); @@ -391,10 +392,11 @@ describe('CommitView', function() { describe('restoring focus', function() { it('to the editor', function() { const wrapper = mount(app); - sinon.spy(wrapper.find('atom-text-editor').getDOMNode(), 'focus'); + const element = wrapper.find('AtomTextEditor').getDOMNode().querySelector('atom-text-editor'); + sinon.spy(element, 'focus'); assert.isTrue(wrapper.instance().setFocus(CommitView.focus.EDITOR)); - assert.isTrue(wrapper.find('atom-text-editor').getDOMNode().focus.called); + assert.isTrue(element.focus.called); }); it('to the abort merge button', function() { @@ -430,13 +432,14 @@ describe('CommitView', function() { it('when the named element is no longer rendered', function() { const wrapper = mount(app); - sinon.spy(wrapper.find('atom-text-editor').getDOMNode(), 'focus'); + const element = wrapper.find('AtomTextEditor').getDOMNode().querySelector('atom-text-editor'); + sinon.spy(element, 'focus'); assert.isTrue(wrapper.instance().setFocus(CommitView.focus.ABORT_MERGE_BUTTON)); - assert.strictEqual(wrapper.find('atom-text-editor').getDOMNode().focus.callCount, 1); + assert.strictEqual(element.focus.callCount, 1); assert.isTrue(wrapper.instance().setFocus(CommitView.focus.COAUTHOR_INPUT)); - assert.strictEqual(wrapper.find('atom-text-editor').getDOMNode().focus.callCount, 2); + assert.strictEqual(element.focus.callCount, 2); }); it('when refs have not been assigned yet', function() { From 989362f5efdbe8e7901fb1ee874ee222a44af55c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Sep 2018 08:20:18 -0400 Subject: [PATCH 0231/4053] Use distinct objects for each null Patch --- lib/models/patch/patch.js | 94 ++++++++++++++++++++------------- test/models/patch/patch.test.js | 8 ++- 2 files changed, 65 insertions(+), 37 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index c0ea0d402b..4572dffc42 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -102,6 +102,10 @@ class BufferBuilder { } export default class Patch { + static createNull() { + return new NullPatch(); + } + constructor({status, hunks, buffer, layers}) { this.status = status; this.hunks = hunks; @@ -482,56 +486,55 @@ export default class Patch { } } -const emptyTextBuffer = new TextBuffer(); - -const emptyTextBufferLayers = { - hunk: emptyTextBuffer.addMarkerLayer(), - unchanged: emptyTextBuffer.addMarkerLayer(), - addition: emptyTextBuffer.addMarkerLayer(), - deletion: emptyTextBuffer.addMarkerLayer(), - noNewline: emptyTextBuffer.addMarkerLayer(), -}; +class NullPatch { + constructor() { + this.buffer = new TextBuffer(); + this.hunkLayer = this.buffer.addMarkerLayer(); + this.unchangedLayer = this.buffer.addMarkerLayer(); + this.additionLayer = this.buffer.addMarkerLayer(); + this.deletionLayer = this.buffer.addMarkerLayer(); + this.noNewlineLayer = this.buffer.addMarkerLayer(); + } -export const nullPatch = { getStatus() { return null; - }, + } getHunks() { return []; - }, + } getBuffer() { - return emptyTextBuffer; - }, + return this.buffer; + } getHunkLayer() { - return emptyTextBufferLayers.hunk; - }, + return this.hunkLayer; + } getUnchangedLayer() { - return emptyTextBufferLayers.unchanged; - }, + return this.unchangedLayer; + } getAdditionLayer() { - return emptyTextBufferLayers.addition; - }, + return this.additionLayer; + } getDeletionLayer() { - return emptyTextBufferLayers.deletion; - }, + return this.deletionLayer; + } getNoNewlineLayer() { - return emptyTextBufferLayers.noNewline; - }, + return this.noNewlineLayer; + } getByteSize() { return 0; - }, + } getChangedLineCount() { return 0; - }, + } clone(opts = {}) { if ( @@ -555,37 +558,56 @@ export const nullPatch = { }, }); } - }, + } getStagePatchForLines() { return this; - }, + } getUnstagePatchForLines() { return this; - }, + } getFullUnstagedPatch() { return this; - }, + } getFirstChangeRange() { return [[0, 0], [0, 0]]; - }, + } getNextSelectionRange() { return [[0, 0], [0, 0]]; - }, + } + + adoptBufferFrom(lastPatch) { + lastPatch.getHunkLayer().clear(); + lastPatch.getUnchangedLayer().clear(); + lastPatch.getAdditionLayer().clear(); + lastPatch.getDeletionLayer().clear(); + lastPatch.getNoNewlineLayer().clear(); + + const nextBuffer = lastPatch.getBuffer(); + nextBuffer.setText(''); + + this.hunkLayer = lastPatch.getHunkLayer(); + this.unchangedLayer = lastPatch.getUnchangedLayer(); + this.additionLayer = lastPatch.getAdditionLayer(); + this.deletionLayer = lastPatch.getDeletionLayer(); + this.noNewlineLayer = lastPatch.getNoNewlineLayer(); + + this.buffer = nextBuffer; + } getMaxLineNumberWidth() { return 0; - }, + } toString() { return ''; - }, + } isPresent() { return false; - }, -}; + } +} diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 4c625313a0..ed5ca26283 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -1,6 +1,6 @@ import {TextBuffer} from 'atom'; -import Patch, {nullPatch} from '../../../lib/models/patch/patch'; +import Patch from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; import {Unchanged, Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; import {assertInPatch} from '../../helpers'; @@ -161,10 +161,13 @@ describe('Patch', function() { }); it('clones a nullPatch as a nullPatch', function() { + const nullPatch = Patch.createNull(); assert.strictEqual(nullPatch, nullPatch.clone()); }); it('clones a nullPatch to a real Patch if properties are provided', function() { + const nullPatch = Patch.createNull(); + const dup0 = nullPatch.clone({status: 'added'}); assert.notStrictEqual(dup0, nullPatch); assert.strictEqual(dup0.getStatus(), 'added'); @@ -379,6 +382,7 @@ describe('Patch', function() { }); it('returns a nullPatch as a nullPatch', function() { + const nullPatch = Patch.createNull(); assert.strictEqual(nullPatch.getStagePatchForLines(new Set([1, 2, 3])), nullPatch); }); }); @@ -625,6 +629,7 @@ describe('Patch', function() { }); it('returns a nullPatch as a nullPatch', function() { + const nullPatch = Patch.createNull(); assert.strictEqual(nullPatch.getUnstagePatchForLines(new Set([1, 2, 3])), nullPatch); assert.strictEqual(nullPatch.getFullUnstagedPatch(), nullPatch); }); @@ -859,6 +864,7 @@ describe('Patch', function() { }); it('has a stubbed nullPatch counterpart', function() { + const nullPatch = Patch.createNull(); assert.isNull(nullPatch.getStatus()); assert.deepEqual(nullPatch.getHunks(), []); assert.strictEqual(nullPatch.getBuffer().getText(), ''); From 090fe648c7e9fe276491e7f88f0640d4852be6fc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Sep 2018 08:20:50 -0400 Subject: [PATCH 0232/4053] Use Patch.createNull() and FilePatch.createNull() instead of singletons --- lib/models/patch/builder.js | 4 ++-- lib/models/patch/file-patch.js | 8 +++++--- lib/models/repository-states/state.js | 4 ++-- test/models/patch/file-patch.test.js | 4 +++- test/views/file-patch-view.test.js | 6 +++--- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 89aa32165a..a773e3d976 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -2,7 +2,7 @@ import {TextBuffer} from 'atom'; import Hunk from './hunk'; import File, {nullFile} from './file'; -import Patch, {nullPatch} from './patch'; +import Patch from './patch'; import {Unchanged, Addition, Deletion, NoNewline} from './region'; import FilePatch from './file-patch'; @@ -19,7 +19,7 @@ export default function buildFilePatch(diffs) { } function emptyDiffFilePatch() { - return new FilePatch(nullFile, nullFile, nullPatch); + return FilePatch.createNull(); } function singleDiffFilePatch(diff) { diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 5c6847b4d2..5b796436a0 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -1,8 +1,12 @@ import {nullFile} from './file'; -import {nullPatch} from './patch'; +import Patch from './patch'; import {toGitPathSep} from '../../helpers'; export default class FilePatch { + static createNull() { + return new this(nullFile, nullFile, Patch.createNull()); + } + constructor(oldFile, newFile, patch) { this.oldFile = oldFile; this.newFile = newFile; @@ -267,5 +271,3 @@ export default class FilePatch { return header; } } - -export const nullFilePatch = new FilePatch(nullFile, nullFile, nullPatch); diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index c9d5c82b55..dc3b8e9bb4 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -2,7 +2,7 @@ import {nullCommit} from '../commit'; import BranchSet from '../branch-set'; import RemoteSet from '../remote-set'; import {nullOperationStates} from '../operation-states'; -import {nullFilePatch} from '../patch/file-patch'; +import FilePatch from '../patch/file-patch'; /** * Map of registered subclasses to allow states to transition to one another without circular dependencies. @@ -275,7 +275,7 @@ export default class State { } getFilePatchForPath(filePath, options = {}) { - return Promise.resolve(nullFilePatch); + return Promise.resolve(FilePatch.createNull()); } readFileFromIndex(filePath) { diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index bfe3333c15..0eb32e18ef 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -1,6 +1,6 @@ import {TextBuffer} from 'atom'; -import FilePatch, {nullFilePatch} from '../../../lib/models/patch/file-patch'; +import FilePatch from '../../../lib/models/patch/file-patch'; import File, {nullFile} from '../../../lib/models/patch/file'; import Patch from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; @@ -798,6 +798,8 @@ describe('FilePatch', function() { const buffer = new TextBuffer({text: '0\n1\n2\n3\n'}); const marker = markRange(buffer, 0, 1); + const nullFilePatch = FilePatch.createNull(); + assert.isFalse(nullFilePatch.isPresent()); assert.isFalse(nullFilePatch.getOldFile().isPresent()); assert.isFalse(nullFilePatch.getNewFile().isPresent()); diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 0d4cac7b93..dd7d618db4 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -5,7 +5,7 @@ import {cloneRepository, buildRepository} from '../helpers'; import FilePatchView from '../../lib/views/file-patch-view'; import {buildFilePatch} from '../../lib/models/patch'; import {nullFile} from '../../lib/models/patch/file'; -import {nullFilePatch} from '../../lib/models/patch/file-patch'; +import FilePatch from '../../lib/models/patch/file-patch'; describe('FilePatchView', function() { let atomEnv, workspace, repository, filePatch; @@ -766,13 +766,13 @@ describe('FilePatchView', function() { describe('when viewing an empty patch', function() { it('renders an empty patch message', function() { - const wrapper = shallow(buildApp({filePatch: nullFilePatch})); + const wrapper = shallow(buildApp({filePatch: FilePatch.createNull()})); assert.isTrue(wrapper.find('.github-FilePatchView').hasClass('github-FilePatchView--blank')); assert.isTrue(wrapper.find('.github-FilePatchView-message').exists()); }); it('shows navigation controls', function() { - const wrapper = shallow(buildApp({filePatch: nullFilePatch})); + const wrapper = shallow(buildApp({filePatch: FilePatch.createNull()})); assert.isTrue(wrapper.find('FilePatchHeaderView').exists()); }); }); From 1ed20aa165538fd5384f32fbb70497b3bf3be260 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Sep 2018 08:31:43 -0400 Subject: [PATCH 0233/4053] Soft-wrap patch editor --- lib/atom/atom-text-editor.js | 1 + lib/views/file-patch-view.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index 74939f83e6..abf5164906 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -14,6 +14,7 @@ const editorUpdateProps = { lineNumberGutterVisible: PropTypes.bool, autoHeight: PropTypes.bool, autoWidth: PropTypes.bool, + softWrapped: PropTypes.bool, }; const editorCreationProps = { diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 66caaa7290..1fbb0f5de6 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -167,6 +167,8 @@ export default class FilePatchView extends React.Component { autoWidth={false} autoHeight={false} readOnly={true} + softWrapped={true} + didAddSelection={this.didAddSelection} didChangeSelectionRange={this.didChangeSelectionRange} didDestroySelection={this.didDestroySelection} From d787102b017ab188a72f34f5fa0a76499b846337 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Sep 2018 08:44:28 -0400 Subject: [PATCH 0234/4053] Move BufferBuilder to the end of the file --- lib/models/patch/patch.js | 196 +++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 4572dffc42..6b57435d14 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -3,104 +3,6 @@ import {TextBuffer} from 'atom'; import Hunk from './hunk'; import {Unchanged, Addition, Deletion, NoNewline} from './region'; -class BufferBuilder { - constructor(original) { - this.originalBuffer = original; - this.buffer = new TextBuffer(); - this.layers = new Map( - [Unchanged, Addition, Deletion, NoNewline, 'hunk'].map(key => { - return [key, this.buffer.addMarkerLayer()]; - }), - ); - this.offset = 0; - - this.hunkBufferText = ''; - this.hunkRowCount = 0; - this.hunkStartOffset = 0; - this.hunkRegions = []; - this.hunkRange = null; - - this.lastOffset = 0; - } - - append(range) { - this.hunkBufferText += this.originalBuffer.getTextInRange(range) + '\n'; - this.hunkRowCount += range.getRowCount(); - } - - remove(range) { - this.offset -= range.getRowCount(); - } - - markRegion(range, RegionKind) { - const finalRange = this.offset !== 0 - ? range.translate([this.offset, 0], [this.offset, 0]) - : range; - - // Collapse consecutive ranges of the same RegionKind into one continuous region. - const lastRegion = this.hunkRegions[this.hunkRegions.length - 1]; - if (lastRegion && lastRegion.RegionKind === RegionKind && finalRange.start.row - lastRegion.range.end.row === 1) { - lastRegion.range.end = finalRange.end; - } else { - this.hunkRegions.push({RegionKind, range: finalRange}); - } - } - - markHunkRange(range) { - let finalRange = range; - if (this.hunkStartOffset !== 0 || this.offset !== 0) { - finalRange = finalRange.translate([this.hunkStartOffset, 0], [this.offset, 0]); - } - this.hunkRange = finalRange; - } - - latestHunkWasIncluded() { - this.buffer.append(this.hunkBufferText, {normalizeLineEndings: false}); - - const regions = this.hunkRegions.map(({RegionKind, range}) => { - return new RegionKind( - this.layers.get(RegionKind).markRange(range, {invalidate: 'never', exclusive: false}), - ); - }); - - const marker = this.layers.get('hunk').markRange(this.hunkRange, {invalidate: 'never', exclusive: false}); - - this.hunkBufferText = ''; - this.hunkRowCount = 0; - this.hunkStartOffset = this.offset; - this.hunkRegions = []; - this.hunkRange = null; - - return {regions, marker}; - } - - latestHunkWasDiscarded() { - this.offset -= this.hunkRowCount; - - this.hunkBufferText = ''; - this.hunkRowCount = 0; - this.hunkStartOffset = this.offset; - this.hunkRegions = []; - this.hunkRange = null; - - return {regions: [], marker: null}; - } - - getBuffer() { - return this.buffer; - } - - getLayers() { - return { - hunk: this.layers.get('hunk'), - unchanged: this.layers.get(Unchanged), - addition: this.layers.get(Addition), - deletion: this.layers.get(Deletion), - noNewline: this.layers.get(NoNewline), - }; - } -} - export default class Patch { static createNull() { return new NullPatch(); @@ -611,3 +513,101 @@ class NullPatch { return false; } } + +class BufferBuilder { + constructor(original) { + this.originalBuffer = original; + this.buffer = new TextBuffer(); + this.layers = new Map( + [Unchanged, Addition, Deletion, NoNewline, 'hunk'].map(key => { + return [key, this.buffer.addMarkerLayer()]; + }), + ); + this.offset = 0; + + this.hunkBufferText = ''; + this.hunkRowCount = 0; + this.hunkStartOffset = 0; + this.hunkRegions = []; + this.hunkRange = null; + + this.lastOffset = 0; + } + + append(range) { + this.hunkBufferText += this.originalBuffer.getTextInRange(range) + '\n'; + this.hunkRowCount += range.getRowCount(); + } + + remove(range) { + this.offset -= range.getRowCount(); + } + + markRegion(range, RegionKind) { + const finalRange = this.offset !== 0 + ? range.translate([this.offset, 0], [this.offset, 0]) + : range; + + // Collapse consecutive ranges of the same RegionKind into one continuous region. + const lastRegion = this.hunkRegions[this.hunkRegions.length - 1]; + if (lastRegion && lastRegion.RegionKind === RegionKind && finalRange.start.row - lastRegion.range.end.row === 1) { + lastRegion.range.end = finalRange.end; + } else { + this.hunkRegions.push({RegionKind, range: finalRange}); + } + } + + markHunkRange(range) { + let finalRange = range; + if (this.hunkStartOffset !== 0 || this.offset !== 0) { + finalRange = finalRange.translate([this.hunkStartOffset, 0], [this.offset, 0]); + } + this.hunkRange = finalRange; + } + + latestHunkWasIncluded() { + this.buffer.append(this.hunkBufferText, {normalizeLineEndings: false}); + + const regions = this.hunkRegions.map(({RegionKind, range}) => { + return new RegionKind( + this.layers.get(RegionKind).markRange(range, {invalidate: 'never', exclusive: false}), + ); + }); + + const marker = this.layers.get('hunk').markRange(this.hunkRange, {invalidate: 'never', exclusive: false}); + + this.hunkBufferText = ''; + this.hunkRowCount = 0; + this.hunkStartOffset = this.offset; + this.hunkRegions = []; + this.hunkRange = null; + + return {regions, marker}; + } + + latestHunkWasDiscarded() { + this.offset -= this.hunkRowCount; + + this.hunkBufferText = ''; + this.hunkRowCount = 0; + this.hunkStartOffset = this.offset; + this.hunkRegions = []; + this.hunkRange = null; + + return {regions: [], marker: null}; + } + + getBuffer() { + return this.buffer; + } + + getLayers() { + return { + hunk: this.layers.get('hunk'), + unchanged: this.layers.get(Unchanged), + addition: this.layers.get(Addition), + deletion: this.layers.get(Deletion), + noNewline: this.layers.get(NoNewline), + }; + } +} From f668c016bbd3f670115faf4d334ffc10c08b9518 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Sep 2018 09:35:31 -0400 Subject: [PATCH 0235/4053] Remove unused onChangeMessage callback --- lib/views/commit-view.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index 5415feb792..03e65bc210 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -57,7 +57,7 @@ export default class CommitView extends React.Component { super(props, context); autobind( this, - 'submitNewCoAuthor', 'cancelNewCoAuthor', 'didChangeCommitMessage', 'didMoveCursor', 'toggleHardWrap', + 'submitNewCoAuthor', 'cancelNewCoAuthor', 'didMoveCursor', 'toggleHardWrap', 'toggleCoAuthorInput', 'abortMerge', 'commit', 'amendLastCommit', 'toggleExpandedCommitMessageEditor', 'renderCoAuthorListItem', 'onSelectedCoAuthorsChanged', 'excludeCoAuthor', ); @@ -169,7 +169,6 @@ export default class CommitView extends React.Component { scrollPastEnd={false} buffer={this.props.messageBuffer} workspace={this.props.workspace} - didChange={this.didChangeCommitMessage} didChangeCursorPosition={this.didMoveCursor} /> ); } From b82e732ca7e1d6e642fdb4f133447064647d51a5 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Sun, 23 Sep 2018 20:38:05 -0700 Subject: [PATCH 0289/4053] Hide "Undo discard" text for narrow width. Add tooltip --- lib/views/git-tab-view.js | 1 + lib/views/staging-view.js | 20 +++++++++++++++++--- styles/staging-view.less | 4 ++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/views/git-tab-view.js b/lib/views/git-tab-view.js index d748e3bf71..3711dcbdee 100644 --- a/lib/views/git-tab-view.js +++ b/lib/views/git-tab-view.js @@ -151,6 +151,7 @@ export default class GitTabView extends React.Component { commandRegistry={this.props.commandRegistry} notificationManager={this.props.notificationManager} workspace={this.props.workspace} + tooltips={this.props.tooltips} stagedChanges={this.props.stagedChanges} unstagedChanges={this.props.unstagedChanges} mergeConflicts={this.props.mergeConflicts} diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 63d31c02f9..577055db91 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -15,6 +15,7 @@ import ResolutionProgress from '../models/conflicts/resolution-progress'; import RefHolder from '../models/ref-holder'; import FilePatchController from '../controllers/file-patch-controller'; import Commands, {Command} from '../atom/commands'; +import SimpleTooltip from '../atom/simple-tooltip'; import {autobind} from '../helpers'; const debounce = (fn, wait) => { @@ -57,6 +58,7 @@ export default class StagingView extends React.Component { commandRegistry: PropTypes.object.isRequired, notificationManager: PropTypes.object.isRequired, workspace: PropTypes.object.isRequired, + tooltips: PropTypes.object.isRequired, openFiles: PropTypes.func.isRequired, attemptFileStageOperation: PropTypes.func.isRequired, discardWorkDirChangesForPaths: PropTypes.func.isRequired, @@ -171,6 +173,12 @@ export default class StagingView extends React.Component { element.scrollIntoViewIfNeeded(); } } + + if (this.header.clientWidth < 365) { + this.header.classList.add('github-StagingView-header--narrow-width'); + } else { + this.header.classList.remove('github-StagingView-header--narrow-width'); + } } render() { @@ -191,7 +199,7 @@ export default class StagingView extends React.Component { tabIndex="-1"> {this.renderCommands()}
-
+
(this.header = c)} className="github-StagingView-header"> Unstaged Changes {this.props.hasUndoHistory ? this.renderUndoButton() : null} @@ -297,8 +305,14 @@ export default class StagingView extends React.Component { renderUndoButton() { return ( - + + + ); } diff --git a/styles/staging-view.less b/styles/staging-view.less index 7f429f1078..1024de7a37 100644 --- a/styles/staging-view.less +++ b/styles/staging-view.less @@ -61,6 +61,10 @@ border-left: none; border-bottom: 1px solid @panel-heading-border-color; } + + .github-StagingView-header--narrow-width &-text { + display: none; + } } From 0f1b6f53e51d21792b92f23893fee4178d0f8d5a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 14 Nov 2017 18:31:12 -0800 Subject: [PATCH 0290/4053] Ask to confirm undo discard if head has moved since discard --- lib/models/discard-history.js | 38 +++++++++++++++++++------ lib/models/repository-states/present.js | 7 +++-- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/lib/models/discard-history.js b/lib/models/discard-history.js index 3521ae3b81..e407562dfb 100644 --- a/lib/models/discard-history.js +++ b/lib/models/discard-history.js @@ -72,11 +72,11 @@ export default class DiscardHistory { return historySha; } - async storeBeforeAndAfterBlobs(filePaths, isSafe, destructiveAction, partialDiscardFilePath = null) { + async storeBeforeAndAfterBlobs(filePaths, isSafe, destructiveAction, partialDiscardFilePath = null, commitSha) { if (partialDiscardFilePath) { - return await this.storeBlobsForPartialFileHistory(partialDiscardFilePath, isSafe, destructiveAction); + return await this.storeBlobsForPartialFileHistory(partialDiscardFilePath, isSafe, destructiveAction, commitSha); } else { - return await this.storeBlobsForWholeFileHistory(filePaths, isSafe, destructiveAction); + return await this.storeBlobsForWholeFileHistory(filePaths, isSafe, destructiveAction, commitSha); } } @@ -91,10 +91,13 @@ export default class DiscardHistory { return snapshots; } - async storeBlobsForWholeFileHistory(filePaths, isSafe, destructiveAction) { + async storeBlobsForWholeFileHistory(filePaths, isSafe, destructiveAction, commitSha) { const snapshotsByPath = {}; const beforePromises = filePaths.map(async filePath => { - snapshotsByPath[filePath] = {beforeSha: await this.createBlob({filePath})}; + snapshotsByPath[filePath] = { + beforeSha: await this.createBlob({filePath}), + commitSha, + }; }); await Promise.all(beforePromises); const isNotSafe = !(await isSafe()); @@ -108,14 +111,31 @@ export default class DiscardHistory { return snapshotsByPath; } - async restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath = null) { + async restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath = null, lastCommitSha) { + const expandAndMerge = async () => { + const tempFolderPaths = await this.expandBlobsToFilesInTempFolder(lastDiscardSnapshots); + if (!isSafe()) { return []; } + return await this.mergeFiles(tempFolderPaths); + }; + let lastDiscardSnapshots = this.getLastSnapshots(partialDiscardFilePath); if (partialDiscardFilePath) { lastDiscardSnapshots = lastDiscardSnapshots ? [lastDiscardSnapshots] : []; } - const tempFolderPaths = await this.expandBlobsToFilesInTempFolder(lastDiscardSnapshots); - if (!isSafe()) { return []; } - return await this.mergeFiles(tempFolderPaths); + if (lastDiscardSnapshots[0].commitSha !== lastCommitSha) { + const choice = atom.confirm({ + message: 'You have committed since discarding.', + detailedMessage: 'Are you sure you want to undo the last discard?', + buttons: ['Undo', 'Cancel'], + }); + if (choice === 0) { + return expandAndMerge(); + } else { + return []; + } + } else { + return expandAndMerge(); + } } async expandBlobsToFilesInTempFolder(snapshots) { diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 4e6d421d73..85788e78df 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -476,11 +476,13 @@ export default class Present extends State { } async storeBeforeAndAfterBlobs(filePaths, isSafe, destructiveAction, partialDiscardFilePath = null) { + const commitSha = (await this.getLastCommit()).getSha(); const snapshots = await this.discardHistory.storeBeforeAndAfterBlobs( filePaths, isSafe, destructiveAction, partialDiscardFilePath, + commitSha, ); if (snapshots) { await this.saveDiscardHistory(); @@ -488,8 +490,9 @@ export default class Present extends State { return snapshots; } - restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath = null) { - return this.discardHistory.restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath); + async restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath = null) { + const lastCommitSha = (await this.getLastCommit()).getSha(); + return this.discardHistory.restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath, lastCommitSha); } async popDiscardHistory(partialDiscardFilePath = null) { From 0b834a0c29caceb3a440f2d55962caac7cc04253 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 24 Sep 2018 23:10:27 -0700 Subject: [PATCH 0291/4053] Revert "Ask to confirm undo discard if head has moved since discard" This reverts commit 0f1b6f53e51d21792b92f23893fee4178d0f8d5a. --- lib/models/discard-history.js | 38 ++++++------------------- lib/models/repository-states/present.js | 7 ++--- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/lib/models/discard-history.js b/lib/models/discard-history.js index e407562dfb..3521ae3b81 100644 --- a/lib/models/discard-history.js +++ b/lib/models/discard-history.js @@ -72,11 +72,11 @@ export default class DiscardHistory { return historySha; } - async storeBeforeAndAfterBlobs(filePaths, isSafe, destructiveAction, partialDiscardFilePath = null, commitSha) { + async storeBeforeAndAfterBlobs(filePaths, isSafe, destructiveAction, partialDiscardFilePath = null) { if (partialDiscardFilePath) { - return await this.storeBlobsForPartialFileHistory(partialDiscardFilePath, isSafe, destructiveAction, commitSha); + return await this.storeBlobsForPartialFileHistory(partialDiscardFilePath, isSafe, destructiveAction); } else { - return await this.storeBlobsForWholeFileHistory(filePaths, isSafe, destructiveAction, commitSha); + return await this.storeBlobsForWholeFileHistory(filePaths, isSafe, destructiveAction); } } @@ -91,13 +91,10 @@ export default class DiscardHistory { return snapshots; } - async storeBlobsForWholeFileHistory(filePaths, isSafe, destructiveAction, commitSha) { + async storeBlobsForWholeFileHistory(filePaths, isSafe, destructiveAction) { const snapshotsByPath = {}; const beforePromises = filePaths.map(async filePath => { - snapshotsByPath[filePath] = { - beforeSha: await this.createBlob({filePath}), - commitSha, - }; + snapshotsByPath[filePath] = {beforeSha: await this.createBlob({filePath})}; }); await Promise.all(beforePromises); const isNotSafe = !(await isSafe()); @@ -111,31 +108,14 @@ export default class DiscardHistory { return snapshotsByPath; } - async restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath = null, lastCommitSha) { - const expandAndMerge = async () => { - const tempFolderPaths = await this.expandBlobsToFilesInTempFolder(lastDiscardSnapshots); - if (!isSafe()) { return []; } - return await this.mergeFiles(tempFolderPaths); - }; - + async restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath = null) { let lastDiscardSnapshots = this.getLastSnapshots(partialDiscardFilePath); if (partialDiscardFilePath) { lastDiscardSnapshots = lastDiscardSnapshots ? [lastDiscardSnapshots] : []; } - if (lastDiscardSnapshots[0].commitSha !== lastCommitSha) { - const choice = atom.confirm({ - message: 'You have committed since discarding.', - detailedMessage: 'Are you sure you want to undo the last discard?', - buttons: ['Undo', 'Cancel'], - }); - if (choice === 0) { - return expandAndMerge(); - } else { - return []; - } - } else { - return expandAndMerge(); - } + const tempFolderPaths = await this.expandBlobsToFilesInTempFolder(lastDiscardSnapshots); + if (!isSafe()) { return []; } + return await this.mergeFiles(tempFolderPaths); } async expandBlobsToFilesInTempFolder(snapshots) { diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 85788e78df..4e6d421d73 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -476,13 +476,11 @@ export default class Present extends State { } async storeBeforeAndAfterBlobs(filePaths, isSafe, destructiveAction, partialDiscardFilePath = null) { - const commitSha = (await this.getLastCommit()).getSha(); const snapshots = await this.discardHistory.storeBeforeAndAfterBlobs( filePaths, isSafe, destructiveAction, partialDiscardFilePath, - commitSha, ); if (snapshots) { await this.saveDiscardHistory(); @@ -490,9 +488,8 @@ export default class Present extends State { return snapshots; } - async restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath = null) { - const lastCommitSha = (await this.getLastCommit()).getSha(); - return this.discardHistory.restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath, lastCommitSha); + restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath = null) { + return this.discardHistory.restoreLastDiscardInTempFiles(isSafe, partialDiscardFilePath); } async popDiscardHistory(partialDiscardFilePath = null) { From dee888e3774787bbcbd7ab3e99241928c8e60bd4 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 24 Sep 2018 23:10:56 -0700 Subject: [PATCH 0292/4053] Revert "Hide "Undo discard" text for narrow width. Add tooltip" This reverts commit b82e732ca7e1d6e642fdb4f133447064647d51a5. --- lib/views/git-tab-view.js | 1 - lib/views/staging-view.js | 20 +++----------------- styles/staging-view.less | 4 ---- 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/lib/views/git-tab-view.js b/lib/views/git-tab-view.js index 3711dcbdee..d748e3bf71 100644 --- a/lib/views/git-tab-view.js +++ b/lib/views/git-tab-view.js @@ -151,7 +151,6 @@ export default class GitTabView extends React.Component { commandRegistry={this.props.commandRegistry} notificationManager={this.props.notificationManager} workspace={this.props.workspace} - tooltips={this.props.tooltips} stagedChanges={this.props.stagedChanges} unstagedChanges={this.props.unstagedChanges} mergeConflicts={this.props.mergeConflicts} diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 577055db91..63d31c02f9 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -15,7 +15,6 @@ import ResolutionProgress from '../models/conflicts/resolution-progress'; import RefHolder from '../models/ref-holder'; import FilePatchController from '../controllers/file-patch-controller'; import Commands, {Command} from '../atom/commands'; -import SimpleTooltip from '../atom/simple-tooltip'; import {autobind} from '../helpers'; const debounce = (fn, wait) => { @@ -58,7 +57,6 @@ export default class StagingView extends React.Component { commandRegistry: PropTypes.object.isRequired, notificationManager: PropTypes.object.isRequired, workspace: PropTypes.object.isRequired, - tooltips: PropTypes.object.isRequired, openFiles: PropTypes.func.isRequired, attemptFileStageOperation: PropTypes.func.isRequired, discardWorkDirChangesForPaths: PropTypes.func.isRequired, @@ -173,12 +171,6 @@ export default class StagingView extends React.Component { element.scrollIntoViewIfNeeded(); } } - - if (this.header.clientWidth < 365) { - this.header.classList.add('github-StagingView-header--narrow-width'); - } else { - this.header.classList.remove('github-StagingView-header--narrow-width'); - } } render() { @@ -199,7 +191,7 @@ export default class StagingView extends React.Component { tabIndex="-1"> {this.renderCommands()}
-
(this.header = c)} className="github-StagingView-header"> +
Unstaged Changes {this.props.hasUndoHistory ? this.renderUndoButton() : null} @@ -305,14 +297,8 @@ export default class StagingView extends React.Component { renderUndoButton() { return ( - - - + ); } diff --git a/styles/staging-view.less b/styles/staging-view.less index 1024de7a37..7f429f1078 100644 --- a/styles/staging-view.less +++ b/styles/staging-view.less @@ -61,10 +61,6 @@ border-left: none; border-bottom: 1px solid @panel-heading-border-color; } - - .github-StagingView-header--narrow-width &-text { - display: none; - } } From ddb7c46ed8aa7be3cce875ff13b43ec9de47470c Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 24 Sep 2018 23:11:16 -0700 Subject: [PATCH 0293/4053] Revert "Move undo discard button to header" This reverts commit 0d7303642ddc9cebf3a0255e71435dd1f4f4786f. --- lib/views/staging-view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 63d31c02f9..c0d567d445 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -194,9 +194,9 @@ export default class StagingView extends React.Component {
Unstaged Changes - {this.props.hasUndoHistory ? this.renderUndoButton() : null} {this.props.unstagedChanges.length ? this.renderStageAllButton() : null}
+ {this.props.hasUndoHistory ? this.renderUndoButton() : null}
{ this.state.unstagedChanges.map(filePatch => ( @@ -297,7 +297,7 @@ export default class StagingView extends React.Component { renderUndoButton() { return ( - ); } From 71abb971d5856efe0d8e7c433cd26181bc1c212a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 24 Sep 2018 16:47:04 -0700 Subject: [PATCH 0294/4053] :fire: "Undo Discard" btn and move to actions menu in header --- lib/views/staging-view.js | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index c0d567d445..f3058eab5d 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -82,7 +82,7 @@ export default class StagingView extends React.Component { 'dblclickOnItem', 'contextMenuOnItem', 'mousedownOnItem', 'mousemoveOnItem', 'mouseup', 'registerItemElement', 'renderBody', 'openFile', 'discardChanges', 'activateNextList', 'activatePreviousList', 'activateLastList', 'stageAll', 'unstageAll', 'stageAllMergeConflicts', 'discardAll', 'confirmSelectedItems', 'selectAll', - 'selectFirst', 'selectLast', 'diveIntoSelection', 'showDiffView', 'showBulkResolveMenu', + 'selectFirst', 'selectLast', 'diveIntoSelection', 'showDiffView', 'showBulkResolveMenu', 'showActionsMenu', 'resolveCurrentAsOurs', 'resolveCurrentAsTheirs', 'quietlySelectItem', 'didChangeSelectedItems', 'undoLastDiscard', ); @@ -194,9 +194,9 @@ export default class StagingView extends React.Component {
Unstaged Changes + {this.renderActionsMenu()} {this.props.unstagedChanges.length ? this.renderStageAllButton() : null}
- {this.props.hasUndoHistory ? this.renderUndoButton() : null}
{ this.state.unstagedChanges.map(filePatch => ( @@ -295,6 +295,19 @@ export default class StagingView extends React.Component { ); } + renderActionsMenu() { + if (this.props.unstagedChanges.length || this.props.hasUndoHistory) { + return ( + + ); + } else { + return null; + } + } + renderUndoButton() { return ( ); From ea195cfd3ecb7330d4aaad694ca52c548c6f0da5 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 25 Sep 2018 15:06:15 -0700 Subject: [PATCH 0298/4053] add GitHub icon to status bar --- lib/controllers/root-controller.js | 1 + lib/controllers/status-bar-tile-controller.js | 7 ++++ lib/views/github-status-bar-tile.js | 33 +++++++++++++++++++ styles/changed-files-count-view.less | 2 +- styles/github-status-bar-tile.less | 9 +++++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 lib/views/github-status-bar-tile.js create mode 100644 styles/github-status-bar-tile.less diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index b4f6ae647a..34e0550060 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -161,6 +161,7 @@ export default class RootController extends React.Component { tooltips={this.props.tooltips} confirm={this.props.confirm} toggleGitTab={this.gitTabTracker.toggle} + toggleGithubTab={this.githubTabTracker.toggle} ensureGitTabVisible={this.gitTabTracker.ensureVisible} /> diff --git a/lib/controllers/status-bar-tile-controller.js b/lib/controllers/status-bar-tile-controller.js index 95cf29b31a..30f86aa1c4 100644 --- a/lib/controllers/status-bar-tile-controller.js +++ b/lib/controllers/status-bar-tile-controller.js @@ -5,6 +5,7 @@ import BranchView from '../views/branch-view'; import BranchMenuView from '../views/branch-menu-view'; import PushPullView from '../views/push-pull-view'; import ChangedFilesCountView from '../views/changed-files-count-view'; +import GithubStatusBarTile from '../views/github-status-bar-tile'; import Tooltip from '../atom/tooltip'; import Commands, {Command} from '../atom/commands'; import ObserveModel from '../views/observe-model'; @@ -20,8 +21,13 @@ export default class StatusBarTileController extends React.Component { confirm: PropTypes.func.isRequired, repository: PropTypes.object.isRequired, toggleGitTab: PropTypes.func, + toggleGithubTab: PropTypes.func, ensureGitTabVisible: PropTypes.func, } + // todo: ensureGitTabVisible is unused. + // it looks like we used to pop open the git tab if merge conflicts + // were present. Is that something we want to keep doing? + // if so we should fix it. constructor(props) { super(props); @@ -88,6 +94,7 @@ export default class StatusBarTileController extends React.Component { return ( {this.renderTiles(repoProps)} + + + GitHub + + ); + } +} diff --git a/styles/changed-files-count-view.less b/styles/changed-files-count-view.less index 093cd2c98a..56a43bdeb4 100644 --- a/styles/changed-files-count-view.less +++ b/styles/changed-files-count-view.less @@ -6,7 +6,7 @@ background-color: inherit; border: none; - &.icon-diff::before { + &.icon-git-commit::before { margin-right: .2em; } diff --git a/styles/github-status-bar-tile.less b/styles/github-status-bar-tile.less new file mode 100644 index 0000000000..31b699dd8b --- /dev/null +++ b/styles/github-status-bar-tile.less @@ -0,0 +1,9 @@ +.github-StatusBarTile { + background-color: inherit; + border: none; + + &.icon-mark-github::before { + margin-right: .2em; + } + +} From 6915b89f682a6f1a1a0c5065d46d7457f3cd7f34 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 25 Sep 2018 15:14:31 -0700 Subject: [PATCH 0299/4053] fix ChangedFileCountView tests --- test/views/changed-files-count-view.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/views/changed-files-count-view.test.js b/test/views/changed-files-count-view.test.js index cf3cfc89f3..e7a84c7ee4 100644 --- a/test/views/changed-files-count-view.test.js +++ b/test/views/changed-files-count-view.test.js @@ -9,7 +9,7 @@ describe('ChangedFilesCountView', function() { it('renders diff icon', function() { wrapper = shallow(); - assert.isTrue(wrapper.html().includes('icon-diff')); + assert.isTrue(wrapper.html().includes('git-commit')); }); it('renders merge conflict icon if there is a merge conflict', function() { @@ -19,12 +19,12 @@ describe('ChangedFilesCountView', function() { it('renders singular count for one file', function() { wrapper = shallow(); - assert.isTrue(wrapper.text().includes('1 file')); + assert.isTrue(wrapper.text().includes('Git (1)')); }); it('renders multiple count if more than one file', function() { wrapper = shallow(); - assert.isTrue(wrapper.text().includes('2 files')); + assert.isTrue(wrapper.text().includes('Git (2)')); }); it('records an event on click', function() { From f810ed0b78e22c676e1a07b82f81292dada948e1 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 25 Sep 2018 15:18:26 -0700 Subject: [PATCH 0300/4053] add test for toggling github status bar tile --- .../status-bar-tile-controller.test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/controllers/status-bar-tile-controller.test.js b/test/controllers/status-bar-tile-controller.test.js index b4bc40f07e..d2a06098da 100644 --- a/test/controllers/status-bar-tile-controller.test.js +++ b/test/controllers/status-bar-tile-controller.test.js @@ -11,6 +11,7 @@ import Repository from '../../lib/models/repository'; import StatusBarTileController from '../../lib/controllers/status-bar-tile-controller'; import BranchView from '../../lib/views/branch-view'; import ChangedFilesCountView from '../../lib/views/changed-files-count-view'; +import GithubStatusBarTile from '../../lib/views/github-status-bar-tile'; describe('StatusBarTileController', function() { let atomEnvironment; @@ -646,6 +647,21 @@ describe('StatusBarTileController', function() { }); }); + describe('github tile', function() { + it('toggles the github panel when clicked', async function() { + const workdirPath = await cloneRepository('three-files'); + const repository = await buildRepository(workdirPath); + + const toggleGithubTab = sinon.spy(); + + const wrapper = await mountAndLoad(buildApp({repository, toggleGithubTab})); + + wrapper.find(GithubStatusBarTile).simulate('click'); + assert(toggleGithubTab.calledOnce); + }); + }); + + describe('changed files', function() { it('toggles the git panel when clicked', async function() { From 926b033a5221a01b28aec6bfc5ce04fbd65a08fd Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 25 Sep 2018 15:43:21 -0700 Subject: [PATCH 0301/4053] add tests for GithubStatusBarTile --- test/views/github-status-bar-tile.test.js | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/views/github-status-bar-tile.test.js diff --git a/test/views/github-status-bar-tile.test.js b/test/views/github-status-bar-tile.test.js new file mode 100644 index 0000000000..edd138d43c --- /dev/null +++ b/test/views/github-status-bar-tile.test.js @@ -0,0 +1,29 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import GithubStatusBarTile from '../../lib/views/github-status-bar-tile'; +import * as reporterProxy from '../../lib/reporter-proxy'; + +describe('GithubStatusBarTile', function() { + let wrapper, clickSpy; + beforeEach(function() { + clickSpy = sinon.spy(); + wrapper = shallow(); + }); + + it('renders github icon and text', function() { + assert.isTrue(wrapper.html().includes('mark-github')); + assert.isTrue(wrapper.text().includes('GitHub')); + }); + + it('calls props.didClick when clicked', function() { + wrapper.simulate('click'); + assert.isTrue(clickSpy.calledOnce); + }); + + it('records an event on click', function() { + sinon.stub(reporterProxy, 'addEvent'); + wrapper.simulate('click'); + assert.isTrue(reporterProxy.addEvent.calledWith('click', {package: 'github', component: 'GithubStatusBarTile'})); + }); +}); From 7505f9b74259ef2c101bedd5a930153e7d99f225 Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 26 Sep 2018 11:41:05 +0900 Subject: [PATCH 0302/4053] Use ); } else { return null; diff --git a/styles/staging-view.less b/styles/staging-view.less index 7f429f1078..5993c0bc81 100644 --- a/styles/staging-view.less +++ b/styles/staging-view.less @@ -61,6 +61,10 @@ border-left: none; border-bottom: 1px solid @panel-heading-border-color; } + + &--iconOnly:before { + width: auto; + } } From 07d1ef2954178e5194df4f4e5b7e5172512db002 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 26 Sep 2018 18:54:22 +0200 Subject: [PATCH 0303/4053] pull and fetch with current branch's full ref instead of just the name in case the local branch is named differently from upstream --- lib/controllers/status-bar-tile-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/status-bar-tile-controller.js b/lib/controllers/status-bar-tile-controller.js index 95cf29b31a..d7b39bf640 100644 --- a/lib/controllers/status-bar-tile-controller.js +++ b/lib/controllers/status-bar-tile-controller.js @@ -176,10 +176,10 @@ export default class StatusBarTileController extends React.Component { } pull(data) { - return () => this.props.repository.pull(data.currentBranch.getName()); + return () => this.props.repository.pull(data.currentBranch.getFullRef()); } fetch(data) { - return () => this.props.repository.fetch(data.currentBranch.getName()); + return () => this.props.repository.fetch(data.currentBranch.getFullRef()); } } From 6a49d369a2e46acbd3cae20a03f9ae39056cf845 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 26 Sep 2018 10:58:04 -0700 Subject: [PATCH 0304/4053] GithubStatusBarTile => GithubTileView --- lib/controllers/status-bar-tile-controller.js | 4 ++-- .../{github-status-bar-tile.js => github-tile-view.js} | 4 ++-- test/controllers/status-bar-tile-controller.test.js | 4 ++-- ...b-status-bar-tile.test.js => github-tile-view.test.js} | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) rename lib/views/{github-status-bar-tile.js => github-tile-view.js} (80%) rename test/views/{github-status-bar-tile.test.js => github-tile-view.test.js} (74%) diff --git a/lib/controllers/status-bar-tile-controller.js b/lib/controllers/status-bar-tile-controller.js index 30f86aa1c4..5a27ad98f2 100644 --- a/lib/controllers/status-bar-tile-controller.js +++ b/lib/controllers/status-bar-tile-controller.js @@ -5,7 +5,7 @@ import BranchView from '../views/branch-view'; import BranchMenuView from '../views/branch-menu-view'; import PushPullView from '../views/push-pull-view'; import ChangedFilesCountView from '../views/changed-files-count-view'; -import GithubStatusBarTile from '../views/github-status-bar-tile'; +import GithubTileView from '../views/github-tile-view'; import Tooltip from '../atom/tooltip'; import Commands, {Command} from '../atom/commands'; import ObserveModel from '../views/observe-model'; @@ -94,7 +94,7 @@ export default class StatusBarTileController extends React.Component { return ( {this.renderTiles(repoProps)} - + ); + wrapper = shallow(); }); it('renders github icon and text', function() { @@ -24,6 +24,6 @@ describe('GithubStatusBarTile', function() { it('records an event on click', function() { sinon.stub(reporterProxy, 'addEvent'); wrapper.simulate('click'); - assert.isTrue(reporterProxy.addEvent.calledWith('click', {package: 'github', component: 'GithubStatusBarTile'})); + assert.isTrue(reporterProxy.addEvent.calledWith('click', {package: 'github', component: 'GithubTileView'})); }); }); From de369648961ea6c7e7924d688608898b82af06cd Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 26 Sep 2018 20:18:16 +0200 Subject: [PATCH 0305/4053] be specific with which remote to fetch/pull from --- lib/controllers/status-bar-tile-controller.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/controllers/status-bar-tile-controller.js b/lib/controllers/status-bar-tile-controller.js index d7b39bf640..68172525e9 100644 --- a/lib/controllers/status-bar-tile-controller.js +++ b/lib/controllers/status-bar-tile-controller.js @@ -176,10 +176,14 @@ export default class StatusBarTileController extends React.Component { } pull(data) { - return () => this.props.repository.pull(data.currentBranch.getFullRef()); + return () => this.props.repository.pull(data.currentBranch.getFullRef(), { + remoteName: data.currentRemote.getName(), + }); } fetch(data) { - return () => this.props.repository.fetch(data.currentBranch.getFullRef()); + return () => this.props.repository.fetch(data.currentBranch.getFullRef(), { + remoteName: data.currentRemote.getName(), + }); } } From d12a2e85111edfb767db89d9ae44efe68463cbe2 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 27 Sep 2018 00:22:26 +0200 Subject: [PATCH 0306/4053] =?UTF-8?q?test=20test=20test=20=F0=9F=95=B5?= =?UTF-8?q?=EF=B8=8F=E2=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../status-bar-tile-controller.test.js | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/test/controllers/status-bar-tile-controller.test.js b/test/controllers/status-bar-tile-controller.test.js index b4bc40f07e..ec90b01c55 100644 --- a/test/controllers/status-bar-tile-controller.test.js +++ b/test/controllers/status-bar-tile-controller.test.js @@ -12,7 +12,7 @@ import StatusBarTileController from '../../lib/controllers/status-bar-tile-contr import BranchView from '../../lib/views/branch-view'; import ChangedFilesCountView from '../../lib/views/changed-files-count-view'; -describe('StatusBarTileController', function() { +describe.only('StatusBarTileController', function() { let atomEnvironment; let workspace, workspaceElement, commandRegistry, notificationManager, tooltips, confirm; @@ -646,6 +646,39 @@ describe('StatusBarTileController', function() { }); }); + describe('when the local branch is named differently from the remote branch it\'s tracking', function() { + let repository; + beforeEach(async function() { + const {localRepoPath} = await setUpLocalAndRemoteRepositories(); + repository = await buildRepository(localRepoPath); + await repository.git.exec(['branch', '-m', 'another-name']); + }); + + it('fetches properly', async function() { + sinon.spy(repository, 'fetch'); + await commandRegistry.dispatch(workspaceElement, 'github:fetch'); + assert.isTrue(repository.fetch.called); + }); + + it('pulls from the correct remote', async function() { + const prePullSHA = await repository.git.exec(['rev-parse', 'HEAD']); + await repository.git.exec(['reset', '--hard', 'HEAD~2']); + sinon.spy(repository, 'pull'); + await commandRegistry.dispatch(workspaceElement, 'github:pull'); + const postPullSHA = await repository.git.exec(['rev-parse', 'HEAD']); + assert.isTrue(repository.pull.called); + assert.equal(prePullSHA, postPullSHA); + }); + + it('pushes', async function() { + await repository.git.commit('new local commit', {allowEmpty: true}); + sinon.spy(repository, 'push'); + await commandRegistry.dispatch(workspaceElement, 'github:push'); + assert.isTrue(repository.push.called); + }); + + }); + describe('changed files', function() { it('toggles the git panel when clicked', async function() { From 43b33e371421481d645d19ac59d7fc5c7539e758 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 26 Sep 2018 20:59:32 -0400 Subject: [PATCH 0307/4053] Screw it, press commit --- docs/rfcs/XXX-pull-request-review.md | 63 ++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 docs/rfcs/XXX-pull-request-review.md diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md new file mode 100644 index 0000000000..f536e84b27 --- /dev/null +++ b/docs/rfcs/XXX-pull-request-review.md @@ -0,0 +1,63 @@ +# Feature title + +Pull Request Review + +## Status + +Proposed + +## Summary + +Give or receive code reviews on pull requests within Atom. + +## Motivation + +Workflows around pull request reviews involve many trips between your editor and your browser. If you check out a pull request locally to test it and want to leave comments, you need to map the issues that you've found in your working copy back to lines on the diff to comment appropriately. Similarly, when you're given a review, you have to mentally correlate review comments on the diff on GitHub with the corresponding lines in your local working copy, then map _back_ to diff lines to respond once you've established context. By revealing review comments as decorations directly within the editor, we can eliminate all of these round-trips and streamline the review process for all involved. + +## Explanation + +### Entry points + +Reviews on the current pull request are rendered as a list on the current pull request tile. + +![review-list](https://user-images.githubusercontent.com/378023/44708426-1f582000-aae2-11e8-86bd-3074ae259e2d.png) + +* The review summary bubble is elided after the first sentence or N characters if necessary. +* Clicking the review summary bubble opens an `IssueishPaneItem` in the workspace center, open to the reviews tab. +* Clicking a line comment opens or activates an editor on the referenced file and scrolls to center the comment's line, translated according to local changes if appropriate. +* Line comments within the review are rendered: _with a dot_ before the file has been opened and the corresponding decoration is visible; _with no icon_ after the file and decoration have been seen; and _with a checkmark_ after the comment has been marked "resolved" with the control on its decoration. + +Pull request tiles other than the current pull request display a one-line review summary, showing the number of accepting, comment, and change-request reviews made on each. Clicking the review summary opens the `IssueishPaneItem` for that pull request and opens the review tab. + +> TODO: sketch here + +Each `IssueishPaneItem` opened on a pull request has a "Reviews" tab that shows the active reviews. + +## Drawbacks + + + +## Rationale and alternatives + + + +## Unresolved questions + + + +## Implementation phases + + From 2aac6fd0013c8cb66b94b14952358e85bb112d7c Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 27 Sep 2018 13:01:10 +0200 Subject: [PATCH 0308/4053] should be pushing and pulling using the correct ref now --- lib/controllers/status-bar-tile-controller.js | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/controllers/status-bar-tile-controller.js b/lib/controllers/status-bar-tile-controller.js index 68172525e9..799c5c51ad 100644 --- a/lib/controllers/status-bar-tile-controller.js +++ b/lib/controllers/status-bar-tile-controller.js @@ -171,19 +171,30 @@ export default class StatusBarTileController extends React.Component { push(data) { return ({force, setUpstream} = {}) => { - return this.props.repository.push(data.currentBranch.getName(), {force, setUpstream}); + const upstream = data.currentBranch.getUpstream(); + const branchRef = upstream.getShortRemoteRef() !== data.currentBranch.getName() + ? `${data.currentBranch.getName()}:${upstream.getShortRemoteRef()}` + : data.currentBranch.getName(); + return this.props.repository.push(branchRef, { + force, + setUpstream, + remoteName: upstream.getRemoteName(), + }); }; } pull(data) { - return () => this.props.repository.pull(data.currentBranch.getFullRef(), { - remoteName: data.currentRemote.getName(), - }); + return () => { + debugger; + const upstream = data.currentBranch.getUpstream(); + return this.props.repository.pull(upstream.getRemoteRef(), {remoteName: upstream.getRemoteName()}); + }; } fetch(data) { - return () => this.props.repository.fetch(data.currentBranch.getFullRef(), { - remoteName: data.currentRemote.getName(), - }); + return () => { + const upstream = data.currentBranch.getUpstream(); + return this.props.repository.fetch(upstream.getRemoteRef(), {remoteName: upstream.getRemoteName()}); + }; } } From a7affcd4e11bffdb8b44b76eb000543d66606618 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 27 Sep 2018 13:47:27 +0200 Subject: [PATCH 0309/4053] add handling of refspec --- lib/controllers/status-bar-tile-controller.js | 23 +++++++++++-------- lib/git-shell-out-strategy.js | 4 ++-- lib/models/branch.js | 18 +++++++++++++++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/lib/controllers/status-bar-tile-controller.js b/lib/controllers/status-bar-tile-controller.js index 799c5c51ad..226c9ebb5f 100644 --- a/lib/controllers/status-bar-tile-controller.js +++ b/lib/controllers/status-bar-tile-controller.js @@ -171,14 +171,19 @@ export default class StatusBarTileController extends React.Component { push(data) { return ({force, setUpstream} = {}) => { - const upstream = data.currentBranch.getUpstream(); - const branchRef = upstream.getShortRemoteRef() !== data.currentBranch.getName() - ? `${data.currentBranch.getName()}:${upstream.getShortRemoteRef()}` - : data.currentBranch.getName(); - return this.props.repository.push(branchRef, { + // const upstream = data.currentBranch.getUpstream(); + // const branchRef = upstream.getShortRemoteRef() !== data.currentBranch.getName() + // ? `${data.currentBranch.getName()}:${upstream.getShortRemoteRef()}` + // : data.currentBranch.getName(); + // return this.props.repository.push(branchRef, { + // force, + // setUpstream, + // remoteName: upstream.getRemoteName(), + // }); + return this.props.repository.push(data.currentBranch.getName(), { force, setUpstream, - remoteName: upstream.getRemoteName(), + refSpec: data.currentBranch.getRefSpec('PUSH'), }); }; } @@ -186,8 +191,8 @@ export default class StatusBarTileController extends React.Component { pull(data) { return () => { debugger; - const upstream = data.currentBranch.getUpstream(); - return this.props.repository.pull(upstream.getRemoteRef(), {remoteName: upstream.getRemoteName()}); + // const upstream = data.currentBranch.getUpstream(); + return this.props.repository.pull(data.currentBranch.getName(), {refSpec: data.currentBranch.getRefSpec('PULL')}); }; } @@ -197,4 +202,4 @@ export default class StatusBarTileController extends React.Component { return this.props.repository.fetch(upstream.getRemoteRef(), {remoteName: upstream.getRemoteName()}); }; } -} +} \ No newline at end of file diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index c8d5ae1a2b..c80f242d82 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -787,7 +787,7 @@ export default class GitShellOutStrategy { } pull(remoteName, branchName, options = {}) { - const args = ['pull', remoteName, branchName]; + const args = ["pull", remoteName, options.refSpec || branchName]; if (options.ffOnly) { args.push('--ff-only'); } @@ -795,7 +795,7 @@ export default class GitShellOutStrategy { } push(remoteName, branchName, options = {}) { - const args = ['push', remoteName || 'origin', `refs/heads/${branchName}`]; + const args = ['push', remoteName || 'origin', options.refSpec || `refs/heads/${branchName}`]; if (options.setUpstream) { args.push('--set-upstream'); } if (options.force) { args.push('--force'); } return this.exec(args, {useGitPromptServer: true, writeOperation: true}); diff --git a/lib/models/branch.js b/lib/models/branch.js index fdf62446e9..1ff2acae36 100644 --- a/lib/models/branch.js +++ b/lib/models/branch.js @@ -67,6 +67,23 @@ export default class Branch { return this.getRemoteRef().replace(/^(refs\/)?((heads|remotes)\/)?/, ''); } + getRefSpec(action) { + debugger + if (this.isRemoteTracking()) { + return ''; + } + const remoteBranch = action === 'PUSH' ? this.push : this.upstream; + const remoteBranchName = remoteBranch.getShortRemoteRef(); + const localBranchName = this.getName(); + if (remoteBranchName !== localBranchName) { + const refSpec = action === 'PUSH' + ? `${localBranchName}:${remoteBranchName}` + : `${remoteBranchName}:${localBranchName}`; + return refSpec; + } + return localBranchName; + } + getSha() { return this.attributes.sha || ''; } @@ -94,6 +111,7 @@ export default class Branch { isPresent() { return true; } + } export const nullBranch = { From 8a2ebace56dfaa6168d31f1d43ee68fe03cfbe5d Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 27 Sep 2018 13:54:15 +0200 Subject: [PATCH 0310/4053] some cleanup --- lib/controllers/status-bar-tile-controller.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/lib/controllers/status-bar-tile-controller.js b/lib/controllers/status-bar-tile-controller.js index 226c9ebb5f..5cf5ab714d 100644 --- a/lib/controllers/status-bar-tile-controller.js +++ b/lib/controllers/status-bar-tile-controller.js @@ -171,15 +171,6 @@ export default class StatusBarTileController extends React.Component { push(data) { return ({force, setUpstream} = {}) => { - // const upstream = data.currentBranch.getUpstream(); - // const branchRef = upstream.getShortRemoteRef() !== data.currentBranch.getName() - // ? `${data.currentBranch.getName()}:${upstream.getShortRemoteRef()}` - // : data.currentBranch.getName(); - // return this.props.repository.push(branchRef, { - // force, - // setUpstream, - // remoteName: upstream.getRemoteName(), - // }); return this.props.repository.push(data.currentBranch.getName(), { force, setUpstream, @@ -190,16 +181,18 @@ export default class StatusBarTileController extends React.Component { pull(data) { return () => { - debugger; - // const upstream = data.currentBranch.getUpstream(); - return this.props.repository.pull(data.currentBranch.getName(), {refSpec: data.currentBranch.getRefSpec('PULL')}); + return this.props.repository.pull(data.currentBranch.getName(), { + // refSpec: data.currentBranch.getRefSpec("PULL") + }); }; } fetch(data) { return () => { const upstream = data.currentBranch.getUpstream(); - return this.props.repository.fetch(upstream.getRemoteRef(), {remoteName: upstream.getRemoteName()}); + return this.props.repository.fetch(upstream.getRemoteRef(), { + remoteName: upstream.getRemoteName() + }); }; } } \ No newline at end of file From 241d0b986f31e2a0794e9b3f2ed52f8846dce8dd Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 27 Sep 2018 14:41:11 +0200 Subject: [PATCH 0311/4053] make tests better --- lib/controllers/status-bar-tile-controller.js | 6 ++--- lib/models/branch.js | 3 +-- .../status-bar-tile-controller.test.js | 27 +++++++++++++------ 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/controllers/status-bar-tile-controller.js b/lib/controllers/status-bar-tile-controller.js index 5cf5ab714d..1a9225b764 100644 --- a/lib/controllers/status-bar-tile-controller.js +++ b/lib/controllers/status-bar-tile-controller.js @@ -182,7 +182,7 @@ export default class StatusBarTileController extends React.Component { pull(data) { return () => { return this.props.repository.pull(data.currentBranch.getName(), { - // refSpec: data.currentBranch.getRefSpec("PULL") + refSpec: data.currentBranch.getRefSpec('PULL'), }); }; } @@ -191,8 +191,8 @@ export default class StatusBarTileController extends React.Component { return () => { const upstream = data.currentBranch.getUpstream(); return this.props.repository.fetch(upstream.getRemoteRef(), { - remoteName: upstream.getRemoteName() + remoteName: upstream.getRemoteName(), }); }; } -} \ No newline at end of file +} diff --git a/lib/models/branch.js b/lib/models/branch.js index 1ff2acae36..148dc8a3ca 100644 --- a/lib/models/branch.js +++ b/lib/models/branch.js @@ -68,14 +68,13 @@ export default class Branch { } getRefSpec(action) { - debugger if (this.isRemoteTracking()) { return ''; } const remoteBranch = action === 'PUSH' ? this.push : this.upstream; const remoteBranchName = remoteBranch.getShortRemoteRef(); const localBranchName = this.getName(); - if (remoteBranchName !== localBranchName) { + if (remoteBranchName && remoteBranchName !== localBranchName) { const refSpec = action === 'PUSH' ? `${localBranchName}:${remoteBranchName}` : `${remoteBranchName}:${localBranchName}`; diff --git a/test/controllers/status-bar-tile-controller.test.js b/test/controllers/status-bar-tile-controller.test.js index ec90b01c55..7b43b180c2 100644 --- a/test/controllers/status-bar-tile-controller.test.js +++ b/test/controllers/status-bar-tile-controller.test.js @@ -647,34 +647,45 @@ describe.only('StatusBarTileController', function() { }); describe('when the local branch is named differently from the remote branch it\'s tracking', function() { - let repository; + let repository, wrapper; beforeEach(async function() { const {localRepoPath} = await setUpLocalAndRemoteRepositories(); repository = await buildRepository(localRepoPath); - await repository.git.exec(['branch', '-m', 'another-name']); + wrapper = await mountAndLoad(buildApp({repository})); + await repository.git.exec(['checkout', '-b', 'another-name', '--track', 'origin/master']); + repository.refresh(); }); - it('fetches properly', async function() { + it('fetches with no git error', async function() { sinon.spy(repository, 'fetch'); - await commandRegistry.dispatch(workspaceElement, 'github:fetch'); + await wrapper + .instance() + .fetch(await wrapper.instance().fetchData(repository))(); assert.isTrue(repository.fetch.called); }); - it('pulls from the correct remote', async function() { + it('pulls from the correct branch', async function() { const prePullSHA = await repository.git.exec(['rev-parse', 'HEAD']); await repository.git.exec(['reset', '--hard', 'HEAD~2']); sinon.spy(repository, 'pull'); - await commandRegistry.dispatch(workspaceElement, 'github:pull'); + await wrapper + .instance() + .pull(await wrapper.instance().fetchData(repository))(); const postPullSHA = await repository.git.exec(['rev-parse', 'HEAD']); assert.isTrue(repository.pull.called); assert.equal(prePullSHA, postPullSHA); }); - it('pushes', async function() { + it('pushes to the correct branch', async function() { await repository.git.commit('new local commit', {allowEmpty: true}); + const localSHA = await repository.git.exec(['rev-parse', 'another-name']); sinon.spy(repository, 'push'); - await commandRegistry.dispatch(workspaceElement, 'github:push'); + await wrapper + .instance() + .push(await wrapper.instance().fetchData(repository))(); + const remoteSHA = await repository.git.exec(['rev-parse', 'origin/master']); assert.isTrue(repository.push.called); + assert.equal(localSHA, remoteSHA); }); }); From ae81e6b3a15751c0c5b9bd1c03d0690a38fb87b8 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 27 Sep 2018 14:41:44 +0200 Subject: [PATCH 0312/4053] run all the tests! --- test/controllers/status-bar-tile-controller.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers/status-bar-tile-controller.test.js b/test/controllers/status-bar-tile-controller.test.js index 7b43b180c2..267f913596 100644 --- a/test/controllers/status-bar-tile-controller.test.js +++ b/test/controllers/status-bar-tile-controller.test.js @@ -12,7 +12,7 @@ import StatusBarTileController from '../../lib/controllers/status-bar-tile-contr import BranchView from '../../lib/views/branch-view'; import ChangedFilesCountView from '../../lib/views/changed-files-count-view'; -describe.only('StatusBarTileController', function() { +describe('StatusBarTileController', function() { let atomEnvironment; let workspace, workspaceElement, commandRegistry, notificationManager, tooltips, confirm; From 2c93c25f98f577be056e5247ed8f1fc1f6556924 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 27 Sep 2018 14:54:07 +0200 Subject: [PATCH 0313/4053] lint --- lib/git-shell-out-strategy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index c80f242d82..2b67539a46 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -787,7 +787,7 @@ export default class GitShellOutStrategy { } pull(remoteName, branchName, options = {}) { - const args = ["pull", remoteName, options.refSpec || branchName]; + const args = ['pull', remoteName, options.refSpec || branchName]; if (options.ffOnly) { args.push('--ff-only'); } From 6af2960ca81578f3b58b6c8491ffc86425583167 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 27 Sep 2018 10:03:22 -0400 Subject: [PATCH 0314/4053] and not or --- docs/rfcs/XXX-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index f536e84b27..2955152a26 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -8,7 +8,7 @@ Proposed ## Summary -Give or receive code reviews on pull requests within Atom. +Give and receive code reviews on pull requests within Atom. ## Motivation From c9a0f4bfc73ce077060799a1c4a33e7fb921504a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 27 Sep 2018 10:40:46 -0400 Subject: [PATCH 0315/4053] Rename prop from "markable" to "decorable" --- lib/atom/decoration.js | 44 +++++++++++++------------- lib/controllers/conflict-controller.js | 12 +++---- test/atom/decoration.test.js | 14 ++++---- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/lib/atom/decoration.js b/lib/atom/decoration.js index e62575f2b2..15fe3ceacb 100644 --- a/lib/atom/decoration.js +++ b/lib/atom/decoration.js @@ -25,7 +25,7 @@ const decorationPropTypes = { class BareDecoration extends React.Component { static propTypes = { editorHolder: RefHolderPropType.isRequired, - markableHolder: RefHolderPropType.isRequired, + decorableHolder: RefHolderPropType.isRequired, decorateMethod: PropTypes.oneOf(['decorateMarker', 'decorateMarkerLayer']), itemHolder: RefHolderPropType, children: PropTypes.node, @@ -43,7 +43,7 @@ class BareDecoration extends React.Component { this.decorationHolder = new RefHolder(); this.editorSub = new Disposable(); - this.markableSub = new Disposable(); + this.decorableSub = new Disposable(); this.domNode = null; this.item = null; @@ -59,7 +59,7 @@ class BareDecoration extends React.Component { componentDidMount() { this.editorSub = this.props.editorHolder.observe(this.observeParents); - this.markableSub = this.props.markableHolder.observe(this.observeParents); + this.decorableSub = this.props.decorableHolder.observe(this.observeParents); } componentDidUpdate(prevProps) { @@ -68,9 +68,9 @@ class BareDecoration extends React.Component { this.editorSub = this.state.editorHolder.observe(this.observeParents); } - if (this.props.markableHolder !== prevProps.markableHolder) { - this.markableSub.dispose(); - this.markableSub = this.state.markableHolder.observe(this.observeParents); + if (this.props.decorableHolder !== prevProps.decorableHolder) { + this.decorableSub.dispose(); + this.decorableSub = this.state.decorableHolder.observe(this.observeParents); } if ( @@ -96,11 +96,11 @@ class BareDecoration extends React.Component { this.decorationHolder.map(decoration => decoration.destroy()); const editorValid = this.props.editorHolder.map(editor => !editor.isDestroyed()).getOr(false); - const markableValid = this.props.markableHolder.map(markable => !markable.isDestroyed()).getOr(false); + const markableValid = this.props.decorableHolder.map(decorable => !decorable.isDestroyed()).getOr(false); // Ensure the Marker or MarkerLayer corresponds to the context's TextEditor - const markableMatches = this.props.markableHolder.map(markable => this.props.editorHolder.map(editor => { - const layer = markable.layer || markable; + const markableMatches = this.props.decorableHolder.map(decorable => this.props.editorHolder.map(editor => { + const layer = decorable.layer || decorable; const displayLayer = editor.getMarkerLayer(layer.id); if (!displayLayer) { return false; @@ -125,17 +125,17 @@ class BareDecoration extends React.Component { const opts = this.getDecorationOpts(this.props); const editor = this.props.editorHolder.get(); - const markable = this.props.markableHolder.get(); + const decorable = this.props.decorableHolder.get(); this.decorationHolder.setter( - editor[this.props.decorateMethod](markable, opts), + editor[this.props.decorateMethod](decorable, opts), ); } componentWillUnmount() { this.decorationHolder.map(decoration => decoration.destroy()); this.editorSub.dispose(); - this.markableSub.dispose(); + this.decorableSub.dispose(); } getDecorationOpts(props) { @@ -149,7 +149,7 @@ class BareDecoration extends React.Component { export default class Decoration extends React.Component { static propTypes = { editor: PropTypes.object, - markable: PropTypes.object, + decorable: PropTypes.object, } constructor(props) { @@ -157,7 +157,7 @@ export default class Decoration extends React.Component { this.state = { editorHolder: RefHolder.on(this.props.editor), - markableHolder: RefHolder.on(this.props.markable), + decorableHolder: RefHolder.on(this.props.decorable), }; } @@ -165,11 +165,11 @@ export default class Decoration extends React.Component { const editorChanged = state.editorHolder .map(editor => editor !== props.editor) .getOr(props.editor !== undefined); - const markableChanged = state.markableHolder - .map(markable => markable !== props.markable) - .getOr(props.markable !== undefined); + const decorableChanged = state.decorableHolder + .map(decorable => decorable !== props.decorable) + .getOr(props.decorable !== undefined); - if (!editorChanged && !markableChanged) { + if (!editorChanged && !decorableChanged) { return null; } @@ -178,18 +178,18 @@ export default class Decoration extends React.Component { nextState.editorHolder = RefHolder.on(props.editor); } if (markableChanged) { - nextState.markableHolder = RefHolder.on(props.markable); + nextState.decorableHolder = RefHolder.on(props.decorable); } return nextState; } render() { - if (!this.state.editorHolder.isEmpty() && !this.state.markableHolder.isEmpty()) { + if (!this.state.editorHolder.isEmpty() && !this.state.decorableHolder.isEmpty()) { return ( ); } @@ -201,7 +201,7 @@ export default class Decoration extends React.Component { {({holder, decorateMethod}) => ( diff --git a/lib/controllers/conflict-controller.js b/lib/controllers/conflict-controller.js index 1a73fcd0c8..f51e1236f2 100644 --- a/lib/controllers/conflict-controller.js +++ b/lib/controllers/conflict-controller.js @@ -99,7 +99,7 @@ export default class ConflictController extends React.Component { @@ -110,7 +110,7 @@ export default class ConflictController extends React.Component { return ( @@ -128,7 +128,7 @@ export default class ConflictController extends React.Component { @@ -136,7 +136,7 @@ export default class ConflictController extends React.Component { @@ -144,14 +144,14 @@ export default class ConflictController extends React.Component {
diff --git a/test/atom/decoration.test.js b/test/atom/decoration.test.js index 0b04cc022c..dcde9c4412 100644 --- a/test/atom/decoration.test.js +++ b/test/atom/decoration.test.js @@ -26,7 +26,7 @@ describe('Decoration', function() { const app = ( +
This is a subtree
@@ -62,7 +62,7 @@ describe('Decoration', function() { it('creates an overlay decoration', function() { const app = ( - +
This is a subtree
@@ -80,7 +80,7 @@ describe('Decoration', function() { it('creates a gutter decoration', function() { const app = ( - +
This is a subtree
@@ -101,7 +101,7 @@ describe('Decoration', function() { const app = ( @@ -145,7 +145,7 @@ describe('Decoration', function() { const app = ( Date: Thu, 27 Sep 2018 10:58:04 -0400 Subject: [PATCH 0316/4053] Editor reference is unused on getSnapshotBeforeUpdate --- lib/views/file-patch-view.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 911607df4d..3c7a088c4d 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -80,20 +80,16 @@ export default class FilePatchView extends React.Component { getSnapshotBeforeUpdate(prevProps) { let newSelectionRange = null; if (this.props.filePatch !== prevProps.filePatch) { - this.refEditor.map(editor => { - // Heuristically adjust the editor selection based on the old file patch, the old row selection state, and - // the incoming patch. - newSelectionRange = this.props.filePatch.getNextSelectionRange( - prevProps.filePatch, - prevProps.selectedRows, - ); - - this.suppressChanges = true; - this.props.filePatch.adoptBufferFrom(prevProps.filePatch); - this.suppressChanges = false; + // Heuristically adjust the editor selection based on the old file patch, the old row selection state, and + // the incoming patch. + newSelectionRange = this.props.filePatch.getNextSelectionRange( + prevProps.filePatch, + prevProps.selectedRows, + ); - return null; - }); + this.suppressChanges = true; + this.props.filePatch.adoptBufferFrom(prevProps.filePatch); + this.suppressChanges = false; } return newSelectionRange; } From 0517598a62abeecc80999539e9676ad3efb50cab Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 27 Sep 2018 10:58:21 -0400 Subject: [PATCH 0317/4053] Let the garbage collector handle buffer release --- lib/models/patch/patch.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 7a09a26a73..7f2afd4b44 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -389,7 +389,6 @@ export default class Patch { this.deletionLayer = lastPatch.getDeletionLayer(); this.noNewlineLayer = lastPatch.getNoNewlineLayer(); - this.getBuffer().release(); this.buffer = nextBuffer; this.hunksByMarker = new Map(this.getHunks().map(hunk => [hunk.getMarker(), hunk])); } From d9cf215127d6ff528f4fae2a70343ab5a88ac5f7 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 27 Sep 2018 17:25:29 +0200 Subject: [PATCH 0318/4053] be more specific with conditional statement --- lib/models/branch.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/models/branch.js b/lib/models/branch.js index 148dc8a3ca..97425d118c 100644 --- a/lib/models/branch.js +++ b/lib/models/branch.js @@ -75,10 +75,11 @@ export default class Branch { const remoteBranchName = remoteBranch.getShortRemoteRef(); const localBranchName = this.getName(); if (remoteBranchName && remoteBranchName !== localBranchName) { - const refSpec = action === 'PUSH' - ? `${localBranchName}:${remoteBranchName}` - : `${remoteBranchName}:${localBranchName}`; - return refSpec; + if (action === 'PUSH') { + return `${localBranchName}:${remoteBranchName}`; + } else if (action === 'PULL') { + return `${remoteBranchName}:${localBranchName}`; + } } return localBranchName; } From f582b2d9312fb72dee7b3c3b8e8893a7954dc644 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 27 Sep 2018 11:49:11 -0400 Subject: [PATCH 0319/4053] Use underscore-plus for keystroke humanize functions --- package-lock.json | 21 ++++++++++++++++++--- package.json | 1 + 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a4b0e27551..aa983798cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5163,7 +5163,7 @@ }, "semver": { "version": "5.5.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "strip-ansi": { @@ -5915,8 +5915,8 @@ "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-2.3.0.tgz", "integrity": "sha512-pYaefgVy76/36AMEP+B8YuVVzDHa3C5UFZ3REU78zolk0qMxEhKvUFofvDCXyLZwf0RZjxIfiwok1BEb18nHyA==", "requires": { - "classnames": "2.2.6", - "prop-types": "15.6.2" + "classnames": "^2.2.0", + "prop-types": "^15.5.0" } }, "react-test-renderer": { @@ -7377,6 +7377,21 @@ "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=", "dev": true }, + "underscore-plus": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.6.8.tgz", + "integrity": "sha512-88PrCeMKeAAC1L4xjSiiZ3Fg6kZOYrLpLGVPPeqKq/662DfQe/KTSKdSR/Q/tucKNnfW2MNAUGSCkDf8HmXC5Q==", + "requires": { + "underscore": "~1.8.3" + }, + "dependencies": { + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + } + } + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", diff --git a/package.json b/package.json index 115aaddf9d..7f026884c6 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "temp": "0.8.3", "tinycolor2": "1.4.1", "tree-kill": "1.2.0", + "underscore-plus": "1.6.8", "what-the-diff": "0.4.0", "what-the-status": "1.0.3", "yubikiri": "1.0.0" From 2c9cfe0749652ac369e5fb0d67918b3d07d3ae38 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 27 Sep 2018 11:49:23 -0400 Subject: [PATCH 0320/4053] element to render keystrokes --- lib/atom/keystroke.js | 26 ++++++++++++++++++++++ test/atom/keystroke.test.js | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 lib/atom/keystroke.js create mode 100644 test/atom/keystroke.test.js diff --git a/lib/atom/keystroke.js b/lib/atom/keystroke.js new file mode 100644 index 0000000000..2e2afab1cd --- /dev/null +++ b/lib/atom/keystroke.js @@ -0,0 +1,26 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {humanizeKeystroke} from 'underscore-plus'; + +export default class Keystoke extends React.Component { + static propTypes = { + keymaps: PropTypes.shape({ + findKeyBindings: PropTypes.func.isRequired, + }).isRequired, + command: PropTypes.string.isRequired, + target: PropTypes.instanceOf(Element), + } + + render() { + const [keybinding] = this.props.keymaps.findKeyBindings({ + command: this.props.command, + target: this.props.target, + }); + + if (!keybinding) { + return null; + } + + return {humanizeKeystroke(keybinding.keystrokes)}; + } +} diff --git a/test/atom/keystroke.test.js b/test/atom/keystroke.test.js new file mode 100644 index 0000000000..d629cc148e --- /dev/null +++ b/test/atom/keystroke.test.js @@ -0,0 +1,43 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import Keystroke from '../../lib/atom/keystroke'; + +describe('Keystroke', function() { + let atomEnv, keymaps, root, rootFn; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + keymaps = atomEnv.keymaps; + + root = document.createElement('div'); + root.className = 'github-KeystrokeTest'; + + atomEnv.commands.add(root, 'keystroke-test:root', () => {}); + keymaps.add(__filename, { + '.github-KeystrokeTest': { + 'ctrl-x': 'keystroke-test:root', + }, + }); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + it('renders nothing for an unmapped command', function() { + const wrapper = shallow( + , + ); + + assert.isFalse(wrapper.find('span.keystroke').exists()); + }); + + it('renders a registered keystroke', function() { + const wrapper = shallow( + , + ); + + assert.strictEqual(wrapper.find('span.keystroke').text(), '\u2303X'); + }); +}); From 65305ce68a2db47b40038f9618d371d8c1627262 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 27 Sep 2018 11:30:37 -0700 Subject: [PATCH 0321/4053] open the git tab if merge conflicts are present. --- lib/controllers/status-bar-tile-controller.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/controllers/status-bar-tile-controller.js b/lib/controllers/status-bar-tile-controller.js index 5a27ad98f2..16572d5718 100644 --- a/lib/controllers/status-bar-tile-controller.js +++ b/lib/controllers/status-bar-tile-controller.js @@ -24,10 +24,6 @@ export default class StatusBarTileController extends React.Component { toggleGithubTab: PropTypes.func, ensureGitTabVisible: PropTypes.func, } - // todo: ensureGitTabVisible is unused. - // it looks like we used to pop open the git tab if merge conflicts - // were present. Is that something we want to keep doing? - // if so we should fix it. constructor(props) { super(props); @@ -79,6 +75,9 @@ export default class StatusBarTileController extends React.Component { mergeConflictsPresent = Object.keys(data.statusesForChangedFiles.mergeConflictFiles).length > 0; } + if (mergeConflictsPresent) { + this.props.ensureGitTabVisible(); + } const repoProps = { repository: this.props.repository, currentBranch: data.currentBranch, From 1a869339bb8dbd12167230ca440750a03d34133d Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 27 Sep 2018 11:44:39 -0700 Subject: [PATCH 0322/4053] add unit test --- test/controllers/status-bar-tile-controller.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/controllers/status-bar-tile-controller.test.js b/test/controllers/status-bar-tile-controller.test.js index f4709e3d95..f21d0fb2af 100644 --- a/test/controllers/status-bar-tile-controller.test.js +++ b/test/controllers/status-bar-tile-controller.test.js @@ -40,6 +40,8 @@ describe('StatusBarTileController', function() { notificationManager={notificationManager} tooltips={tooltips} confirm={confirm} + toggleGitTab={() => {}} + toggleGithubTab={() => {}} ensureGitTabVisible={() => {}} {...props} /> @@ -620,13 +622,14 @@ describe('StatusBarTileController', function() { await assert.async.isFalse(repository.getOperationStates().isPushInProgress()); }); + it('displays a warning notification when pull results in merge conflicts', async function() { const {localRepoPath} = await setUpLocalAndRemoteRepositories('multiple-commits', {remoteAhead: true}); fs.writeFileSync(path.join(localRepoPath, 'file.txt'), 'apple'); const repository = await buildRepositoryWithPipeline(localRepoPath, {confirm, notificationManager, workspace}); await repository.git.exec(['commit', '-am', 'Add conflicting change']); - const wrapper = await mountAndLoad(buildApp({repository})); + const wrapper = await mountAndLoad(buildApp({repository, ensureGitTabVisible: sinon.stub()})); sinon.stub(notificationManager, 'addWarning'); @@ -637,6 +640,8 @@ describe('StatusBarTileController', function() { } repository.refresh(); + await assert.async.isTrue(wrapper.instance().props.ensureGitTabVisible.called); + await assert.async.isTrue(notificationManager.addWarning.called); const notificationArgs = notificationManager.addWarning.args[0]; assert.equal(notificationArgs[0], 'Merge conflicts'); From 0d25de0248e5789ddc1d9dd074f3e2b74050869c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 27 Sep 2018 14:54:37 -0400 Subject: [PATCH 0323/4053] Use a RefHolder for a Keystroke's target --- lib/atom/keystroke.js | 56 +++++++++++++++++++++++++++++++------ test/atom/keystroke.test.js | 53 +++++++++++++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 11 deletions(-) diff --git a/lib/atom/keystroke.js b/lib/atom/keystroke.js index 2e2afab1cd..89482fdc5b 100644 --- a/lib/atom/keystroke.js +++ b/lib/atom/keystroke.js @@ -1,6 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import {humanizeKeystroke} from 'underscore-plus'; +import {Disposable} from 'event-kit'; + +import {autobind} from '../helpers'; +import {RefHolderPropType} from '../prop-types'; export default class Keystoke extends React.Component { static propTypes = { @@ -8,19 +12,55 @@ export default class Keystoke extends React.Component { findKeyBindings: PropTypes.func.isRequired, }).isRequired, command: PropTypes.string.isRequired, - target: PropTypes.instanceOf(Element), + refTarget: RefHolderPropType, } - render() { - const [keybinding] = this.props.keymaps.findKeyBindings({ - command: this.props.command, - target: this.props.target, - }); + constructor(props) { + super(props); + autobind(this, 'didChangeTarget'); + + this.sub = new Disposable(); + this.state = {keybinding: null}; + } + + componentDidMount() { + this.observeTarget(); + } + + componentDidUpdate(prevProps, prevState) { + if (this.props.refTarget !== prevProps.refTarget) { + this.observeTarget(); + } else if (this.props.command !== prevProps.command) { + this.didChangeTarget(this.props.refTarget.getOr(null)); + } + } - if (!keybinding) { + componentWillUnmount() { + this.sub.dispose(); + } + + render() { + if (!this.state.keybinding) { return null; } - return {humanizeKeystroke(keybinding.keystrokes)}; + return {humanizeKeystroke(this.state.keybinding.keystrokes)}; + } + + observeTarget() { + this.sub.dispose(); + if (this.props.refTarget) { + this.sub = this.props.refTarget.observe(this.didChangeTarget); + } else { + this.didChangeTarget(null); + } + } + + didChangeTarget(target) { + const [keybinding] = this.props.keymaps.findKeyBindings({ + command: this.props.command, + target, + }); + this.setState({keybinding}); } } diff --git a/test/atom/keystroke.test.js b/test/atom/keystroke.test.js index d629cc148e..21c11f9605 100644 --- a/test/atom/keystroke.test.js +++ b/test/atom/keystroke.test.js @@ -1,10 +1,11 @@ import React from 'react'; import {shallow} from 'enzyme'; +import RefHolder from '../../lib/models/ref-holder'; import Keystroke from '../../lib/atom/keystroke'; describe('Keystroke', function() { - let atomEnv, keymaps, root, rootFn; + let atomEnv, keymaps, root, child; beforeEach(function() { atomEnv = global.buildAtomEnvironment(); @@ -13,11 +14,20 @@ describe('Keystroke', function() { root = document.createElement('div'); root.className = 'github-KeystrokeTest'; + child = document.createElement('div'); + child.className = 'github-KeystrokeTest-child'; + root.appendChild(child); + atomEnv.commands.add(root, 'keystroke-test:root', () => {}); + atomEnv.commands.add(child, 'keystroke-test:child', () => {}); keymaps.add(__filename, { '.github-KeystrokeTest': { 'ctrl-x': 'keystroke-test:root', }, + '.github-KeystrokeTest-child': { + 'alt-x': 'keystroke-test:root', + 'ctrl-y': 'keystroke-test:child', + }, }); }); @@ -33,11 +43,48 @@ describe('Keystroke', function() { assert.isFalse(wrapper.find('span.keystroke').exists()); }); + it('renders nothing for a command that does not apply to the current target', function() { + const wrapper = shallow( + , + ); + + console.log(wrapper.debug()); + + assert.isFalse(wrapper.find('span.keystroke').exists()); + }); + it('renders a registered keystroke', function() { const wrapper = shallow( - , + , ); - assert.strictEqual(wrapper.find('span.keystroke').text(), '\u2303X'); + assert.strictEqual(wrapper.find('span.keystroke').text(), process.platform === 'darwin' ? '\u2303X' : 'Ctrl+X'); + + // Exercise some other edge cases in the component lifecycle that are not particularly interesting + wrapper.setProps({}); + wrapper.unmount(); + }); + + it('uses the target to disambiguate keystroke bindings', function() { + const wrapper = shallow( + , + ); + + assert.strictEqual(wrapper.find('span.keystroke').text(), process.platform === 'darwin' ? '\u2303X' : 'Ctrl+X'); + + wrapper.setProps({refTarget: RefHolder.on(child)}); + + assert.strictEqual(wrapper.find('span.keystroke').text(), process.platform === 'darwin' ? '\u2325X' : 'Alt+X'); + }); + + it('re-renders if the command prop changes', function() { + const wrapper = shallow( + , + ); + assert.strictEqual(wrapper.find('span.keystroke').text(), process.platform === 'darwin' ? '\u2325X' : 'Alt+X'); + + wrapper.setProps({command: 'keystroke-test:child'}); + + assert.strictEqual(wrapper.find('span.keystroke').text(), process.platform === 'darwin' ? '\u2303Y' : 'Ctrl+Y'); }); }); From 83aa206d2d1917ff1f9886bd2cc30eb8b8989582 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 27 Sep 2018 15:10:36 -0400 Subject: [PATCH 0324/4053] ... "Keystoke" ??? --- lib/atom/keystroke.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/atom/keystroke.js b/lib/atom/keystroke.js index 89482fdc5b..d61d8d2e3a 100644 --- a/lib/atom/keystroke.js +++ b/lib/atom/keystroke.js @@ -6,7 +6,7 @@ import {Disposable} from 'event-kit'; import {autobind} from '../helpers'; import {RefHolderPropType} from '../prop-types'; -export default class Keystoke extends React.Component { +export default class Keystroke extends React.Component { static propTypes = { keymaps: PropTypes.shape({ findKeyBindings: PropTypes.func.isRequired, From 1e575edf0e378eac5cda1d4914f6da37de4f27ba Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 27 Sep 2018 15:11:16 -0400 Subject: [PATCH 0325/4053] Pass Keymaps through the FilePatch components --- lib/containers/file-patch-container.js | 1 + lib/controllers/file-patch-controller.js | 1 + lib/items/file-patch-item.js | 1 + lib/views/file-patch-view.js | 2 ++ lib/views/hunk-header-view.js | 2 ++ test/containers/file-patch-container.test.js | 1 + test/controllers/file-patch-controller.test.js | 1 + test/items/file-patch-item.test.js | 1 + test/views/file-patch-view.test.js | 1 + test/views/hunk-header-view.test.js | 1 + 10 files changed, 12 insertions(+) diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index e75342ad2b..6c41e9de1d 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -15,6 +15,7 @@ export default class FilePatchContainer extends React.Component { workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, + keymaps: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 95423b2946..ec1aa9a5d2 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -15,6 +15,7 @@ export default class FilePatchController extends React.Component { workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, + keymaps: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, diff --git a/lib/items/file-patch-item.js b/lib/items/file-patch-item.js index c8ed878ce2..4e408cb56a 100644 --- a/lib/items/file-patch-item.js +++ b/lib/items/file-patch-item.js @@ -16,6 +16,7 @@ export default class FilePatchItem extends React.Component { workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, + keymaps: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, discardLines: PropTypes.func.isRequired, diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 3c7a088c4d..cafafbaf34 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -35,6 +35,7 @@ export default class FilePatchView extends React.Component { workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, + keymaps: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, selectedRowsChanged: PropTypes.func.isRequired, @@ -409,6 +410,7 @@ export default class FilePatchView extends React.Component { discardSelectionLabel={discardSelectionLabel} tooltips={this.props.tooltips} + keymaps={this.props.keymaps} toggleSelection={() => this.toggleHunkSelection(hunk, containsSelection)} discardSelection={() => this.discardHunkSelection(hunk, containsSelection)} diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index ec92b92c96..c1c076dab0 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -12,6 +12,7 @@ function theBuckStopsHere(event) { export default class HunkHeaderView extends React.Component { static propTypes = { + refRoot: RefHolderPropType.isRequired, hunk: PropTypes.object.isRequired, isSelected: PropTypes.bool.isRequired, stagingStatus: PropTypes.oneOf(['unstaged', 'staged']).isRequired, @@ -20,6 +21,7 @@ export default class HunkHeaderView extends React.Component { discardSelectionLabel: PropTypes.string.isRequired, tooltips: PropTypes.object.isRequired, + keymaps: PropTypes.object.isRequired, toggleSelection: PropTypes.func.isRequired, discardSelection: PropTypes.func.isRequired, diff --git a/test/containers/file-patch-container.test.js b/test/containers/file-patch-container.test.js index e5f4d6d299..c06cfa43d8 100644 --- a/test/containers/file-patch-container.test.js +++ b/test/containers/file-patch-container.test.js @@ -36,6 +36,7 @@ describe('FilePatchContainer', function() { relPath: 'a.txt', workspace: atomEnv.workspace, commands: atomEnv.commands, + keymaps: atomEnv.keymaps, tooltips: atomEnv.tooltips, discardLines: () => {}, undoLastDiscard: () => {}, diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index 61681ecf43..7d1095708d 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -34,6 +34,7 @@ describe('FilePatchController', function() { filePatch, workspace: atomEnv.workspace, commands: atomEnv.commands, + keymaps: atomEnv.keymaps, tooltips: atomEnv.tooltips, destroy: () => {}, discardLines: () => {}, diff --git a/test/items/file-patch-item.test.js b/test/items/file-patch-item.test.js index 660ab60b4f..8c95387a1e 100644 --- a/test/items/file-patch-item.test.js +++ b/test/items/file-patch-item.test.js @@ -31,6 +31,7 @@ describe('FilePatchItem', function() { workdirContextPool: pool, workspace: atomEnv.workspace, commands: atomEnv.commands, + keymaps: atomEnv.keymaps, tooltips: atomEnv.tooltips, discardLines: () => {}, undoLastDiscard: () => {}, diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index a989e169cb..cf977af042 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -55,6 +55,7 @@ describe('FilePatchView', function() { workspace, commands: atomEnv.commands, + keymaps: atomEnv.keymaps, tooltips: atomEnv.tooltips, selectedRowsChanged: () => {}, diff --git a/test/views/hunk-header-view.test.js b/test/views/hunk-header-view.test.js index 8d7625d10d..605a5ed36d 100644 --- a/test/views/hunk-header-view.test.js +++ b/test/views/hunk-header-view.test.js @@ -29,6 +29,7 @@ describe('HunkHeaderView', function() { discardSelectionLabel={'default'} tooltips={atomEnv.tooltips} + keymaps={atomEnv.keymaps} toggleSelection={() => {}} discardSelection={() => {}} From 1c41ddae19334b4e18b6eb5fa314cea9fec70490 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 27 Sep 2018 15:13:13 -0400 Subject: [PATCH 0326/4053] Render the keystroke --- lib/views/hunk-header-view.js | 3 +++ test/views/hunk-header-view.test.js | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index c1c076dab0..c01f80ce02 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -3,8 +3,10 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import {autobind} from '../helpers'; +import {RefHolderPropType} from '../prop-types'; import RefHolder from '../models/ref-holder'; import Tooltip from '../atom/tooltip'; +import Keystroke from '../atom/keystroke'; function theBuckStopsHere(event) { event.stopPropagation(); @@ -50,6 +52,7 @@ export default class HunkHeaderView extends React.Component { className="github-HunkHeaderView-stageButton" onClick={this.props.toggleSelection} onMouseDown={theBuckStopsHere}> + {this.props.toggleSelectionLabel} {this.props.stagingStatus === 'unstaged' && ( diff --git a/test/views/hunk-header-view.test.js b/test/views/hunk-header-view.test.js index 605a5ed36d..4a24708b41 100644 --- a/test/views/hunk-header-view.test.js +++ b/test/views/hunk-header-view.test.js @@ -2,6 +2,7 @@ import React from 'react'; import {shallow} from 'enzyme'; import HunkHeaderView from '../../lib/views/hunk-header-view'; +import RefHolder from '../../lib/models/ref-holder'; import Hunk from '../../lib/models/patch/hunk'; describe('HunkHeaderView', function() { @@ -21,6 +22,7 @@ describe('HunkHeaderView', function() { function buildApp(overrideProps = {}) { return ( Date: Thu, 27 Sep 2018 15:17:26 -0400 Subject: [PATCH 0327/4053] Pass keymaps in to the RootController --- lib/controllers/root-controller.js | 2 ++ lib/github-package.js | 5 ++++- lib/index.js | 1 + test/controllers/root-controller.test.js | 1 + test/github-package.test.js | 4 +++- 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index e66c288695..e61d7cde5e 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -35,6 +35,7 @@ export default class RootController extends React.Component { deserializers: PropTypes.object.isRequired, notificationManager: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, + keymaps: PropTypes.object.isRequired, grammars: PropTypes.object.isRequired, config: PropTypes.object.isRequired, project: PropTypes.object.isRequired, @@ -315,6 +316,7 @@ export default class RootController extends React.Component { tooltips={this.props.tooltips} commands={this.props.commandRegistry} + keymaps={this.props.keymaps} workspace={this.props.workspace} discardLines={this.discardLines} diff --git a/lib/github-package.js b/lib/github-package.js index 86e7271182..b5b65be47e 100644 --- a/lib/github-package.js +++ b/lib/github-package.js @@ -30,7 +30,8 @@ const defaultState = { export default class GithubPackage { constructor({ - workspace, project, commandRegistry, notificationManager, tooltips, styles, grammars, config, deserializers, + workspace, project, commandRegistry, notificationManager, tooltips, styles, grammars, + keymaps, config, deserializers, confirm, getLoadSettings, configDirPath, renderFn, loginModel, @@ -51,6 +52,7 @@ export default class GithubPackage { this.config = config; this.styles = styles; this.grammars = grammars; + this.keymaps = keymaps; this.configPath = path.join(configDirPath, 'github.cson'); this.styleCalculator = new StyleCalculator(this.styles, this.config); @@ -280,6 +282,7 @@ export default class GithubPackage { notificationManager={this.notificationManager} tooltips={this.tooltips} grammars={this.grammars} + keymaps={this.keymaps} config={this.config} project={this.project} confirm={this.confirm} diff --git a/lib/index.js b/lib/index.js index 545587602a..accea10ea8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,6 +10,7 @@ const entry = { notificationManager: atom.notifications, tooltips: atom.tooltips, styles: atom.styles, + keymaps: atom.keymaps, grammars: atom.grammars, config: atom.config, deserializers: atom.deserializers, diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 8bbc7ad45f..1c39c2a360 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -53,6 +53,7 @@ describe('RootController', function() { config={config} confirm={confirm} project={project} + keymaps={atomEnv.keymaps} loginModel={loginModel} repository={absentRepository} resolutionProgress={emptyResolutionProgress} diff --git a/test/github-package.test.js b/test/github-package.test.js index 9495555f43..2bbdaaa70c 100644 --- a/test/github-package.test.js +++ b/test/github-package.test.js @@ -23,6 +23,7 @@ describe('GithubPackage', function() { notificationManager = atomEnv.notifications; tooltips = atomEnv.tooltips; config = atomEnv.config; + keymaps = atomEnv.keymaps; confirm = atomEnv.confirm.bind(atomEnv); styles = atomEnv.styles; grammars = atomEnv.grammars; @@ -30,7 +31,8 @@ describe('GithubPackage', function() { configDirPath = path.join(__dirname, 'fixtures', 'atomenv-config'); githubPackage = new GithubPackage({ - workspace, project, commandRegistry, notificationManager, tooltips, styles, grammars, config, deserializers, + workspace, project, commandRegistry, notificationManager, tooltips, styles, grammars, + keymaps, config, deserializers, confirm, getLoadSettings, configDirPath, renderFn: sinon.stub().callsFake((component, element, callback) => { From ecce849814b93929141fbd82d566feb2a025c751 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 27 Sep 2018 15:19:21 -0400 Subject: [PATCH 0328/4053] Pass the root element as a keystroke target --- lib/views/file-patch-view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index cafafbaf34..65ebd861eb 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -402,6 +402,7 @@ export default class FilePatchView extends React.Component { Date: Thu, 27 Sep 2018 15:41:41 -0400 Subject: [PATCH 0329/4053] Call the target ref a refTarget --- lib/views/hunk-header-view.js | 4 ++-- test/views/hunk-header-view.test.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index c01f80ce02..cb72feb36a 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -14,7 +14,7 @@ function theBuckStopsHere(event) { export default class HunkHeaderView extends React.Component { static propTypes = { - refRoot: RefHolderPropType.isRequired, + refTarget: RefHolderPropType.isRequired, hunk: PropTypes.object.isRequired, isSelected: PropTypes.bool.isRequired, stagingStatus: PropTypes.oneOf(['unstaged', 'staged']).isRequired, @@ -52,7 +52,7 @@ export default class HunkHeaderView extends React.Component { className="github-HunkHeaderView-stageButton" onClick={this.props.toggleSelection} onMouseDown={theBuckStopsHere}> - + {this.props.toggleSelectionLabel} {this.props.stagingStatus === 'unstaged' && ( diff --git a/test/views/hunk-header-view.test.js b/test/views/hunk-header-view.test.js index 4a24708b41..ae1b80fefe 100644 --- a/test/views/hunk-header-view.test.js +++ b/test/views/hunk-header-view.test.js @@ -22,7 +22,7 @@ describe('HunkHeaderView', function() { function buildApp(overrideProps = {}) { return ( Date: Thu, 27 Sep 2018 15:41:59 -0400 Subject: [PATCH 0330/4053] Use the editor element to look up keybindings --- lib/views/file-patch-view.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 65ebd861eb..5a70d4bd9f 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -66,8 +66,15 @@ export default class FilePatchView extends React.Component { this.suppressChanges = false; this.refRoot = new RefHolder(); this.refEditor = new RefHolder(); + this.refEditorElement = new RefHolder(); this.subs = new CompositeDisposable(); + + this.subs.add( + this.refEditor.observe(editor => { + this.refEditorElement.setter(editor.getElement()); + }), + ); } componentDidMount() { @@ -402,7 +409,7 @@ export default class FilePatchView extends React.Component { Date: Thu, 27 Sep 2018 15:50:19 -0400 Subject: [PATCH 0331/4053] Some inexpert styling --- styles/hunk-header-view.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/styles/hunk-header-view.less b/styles/hunk-header-view.less index dba94a9178..a3f88c8ba0 100644 --- a/styles/hunk-header-view.less +++ b/styles/hunk-header-view.less @@ -36,6 +36,10 @@ cursor: default; &:hover { background-color: mix(@syntax-text-color, @syntax-background-color, 8%); } &:active { background-color: mix(@syntax-text-color, @syntax-background-color, 2%); } + + .keystroke { + margin-right: 1em; + } } // pixel fit the icon From 04294bb297e40c26ac7c4128ccfb46b53621aba1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 27 Sep 2018 16:04:29 -0400 Subject: [PATCH 0332/4053] Prepare 0.19.1-3 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa983798cd..015c340d38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.19.1-2", + "version": "0.19.1-3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7f026884c6..df329533d1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.19.1-2", + "version": "0.19.1-3", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From 48a31dcbd6507d6f5ec1f247cb2ed578a04e3437 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 28 Sep 2018 12:41:46 -0400 Subject: [PATCH 0333/4053] A little more justification --- docs/rfcs/XXX-pull-request-review.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 2955152a26..43cc07ab34 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -14,6 +14,8 @@ Give and receive code reviews on pull requests within Atom. Workflows around pull request reviews involve many trips between your editor and your browser. If you check out a pull request locally to test it and want to leave comments, you need to map the issues that you've found in your working copy back to lines on the diff to comment appropriately. Similarly, when you're given a review, you have to mentally correlate review comments on the diff on GitHub with the corresponding lines in your local working copy, then map _back_ to diff lines to respond once you've established context. By revealing review comments as decorations directly within the editor, we can eliminate all of these round-trips and streamline the review process for all involved. +Peer review is also a critical part of the path to acceptance for pull requests in many common workflows. By surfacing progress through code review, we provide context on the progress of each unit of work alongside existing indicators like commit status. + ## Explanation ### Entry points From 438a9e662d235df973754b9c11cf0fde7111df18 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 28 Sep 2018 12:43:59 -0400 Subject: [PATCH 0334/4053] Prose for the rest of the UI components --- docs/rfcs/XXX-pull-request-review.md | 42 ++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 43cc07ab34..92496ab956 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -18,7 +18,7 @@ Peer review is also a critical part of the path to acceptance for pull requests ## Explanation -### Entry points +### Current pull request tile Reviews on the current pull request are rendered as a list on the current pull request tile. @@ -29,11 +29,47 @@ Reviews on the current pull request are rendered as a list on the current pull r * Clicking a line comment opens or activates an editor on the referenced file and scrolls to center the comment's line, translated according to local changes if appropriate. * Line comments within the review are rendered: _with a dot_ before the file has been opened and the corresponding decoration is visible; _with no icon_ after the file and decoration have been seen; and _with a checkmark_ after the comment has been marked "resolved" with the control on its decoration. -Pull request tiles other than the current pull request display a one-line review summary, showing the number of accepting, comment, and change-request reviews made on each. Clicking the review summary opens the `IssueishPaneItem` for that pull request and opens the review tab. +### Non-current pull request tiles + +Pull request tiles other than the current pull request display a one-line review summary, showing the number of accepting, comment, and change-request reviews made on each. Clicking the review summary opens the `IssueishPaneItem` for that pull request and opens the reviews tab. + +> TODO: sketch here + +### IssueishPaneItem "Changes" tab + +Each `IssueishPaneItem` opened on a pull request has a "Changes" tab that shows the full PR diff, annotated with comments from all reviews. + +![changes-tab](https://user-images.githubusercontent.com/378023/44789879-ba332600-abd8-11e8-9247-a19015ccd760.png) + +* The up and down arrow buttons quickly scroll to center the next or previous comment within this tab. +* Clicking the :hamburger: button navigates to the "Reviews" tab, expands the owning review, and scrolls to the same comment within that view. +* Clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the review comment decoration there. +* Clicking within the "Reply..." text editor expands the editor to several lines and focuses it. +* Clicking "mark as resolved" marks the comment as resolved with on GitHub. If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. +* The "comment" button is disabled unless the "reply" editor is expanded and has non-whitespace content. +* Clicking "comment" submits the response as a new stand-alone comment on that thread. + +### IssueishPaneItem "Reviews" tab + +Additionally, each has a "Reviews" tab that shows all reviews associated with this pull request in an accordion-style list. Unexpanded, each review is shown as its full summary comment and chosen outcome (comment, approve, or request changes). Expanded, its associated review comments are listed as well on their proximate diffs. > TODO: sketch here -Each `IssueishPaneItem` opened on a pull request has a "Reviews" tab that shows the active reviews. +* The "Mark as resolved" and "comment" buttons and the "reply" text areas match their behavior in the "Changes" tab. +* The up and down arrow buttons and :hamburger: button are not present here. +* The "code" (`<>`) button also behaves as it does on the "Changes" tab. + +### In-editor decorations + +When opening a TextEditor on a file that has been annotated with review comments on the current pull request, a block decoration is used to show the comment content at the corresponding position within the file content. Also, a gutter decoration is used to reveal lines that are included within the current pull requests' diff and may therefore include comments. + +![in-editor](https://user-images.githubusercontent.com/378023/44790482-69bcc800-abda-11e8-8a0f-922c0942b8c6.png) + +> TODO: add gutter decoration? + +* The comment's position is calculated from the position acquired by the GitHub API response, modified based on the git diff of that file (following renames) between the owning review's attached commit and the current state of the working copy (including any local modifications). Once created, the associated marker will also track unsaved modifications to the file in real time. +* The up and down arrow buttons navigate to the next and previous review comments within this review within their respective TextEditors. +* The "diff" button navigates to the "Reviews" tab of the corresponding pull request's `IssueishPaneItem`, expands the owning review, and scrolls to center the same comment within that view. ## Drawbacks From bcc742debe84b7f49c2f991db46980330365390b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 28 Sep 2018 13:31:20 -0400 Subject: [PATCH 0335/4053] Drawbacks and rationale --- docs/rfcs/XXX-pull-request-review.md | 50 ++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 92496ab956..88b50356b8 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -73,25 +73,49 @@ When opening a TextEditor on a file that has been annotated with review comments ## Drawbacks - +This adds a substantial amount of complexity to the UI, which is only justified for users that use GitHub pull request reviews. + +Showing all reviews in the current pull request tile can easily overwhelm the other pull request information included there. It also limits our ability to expand the information we provide there in the future (like associated issues, say). + +Rendering pull request comments within TextEditors can be intrusive: if there are many, or if your reviewers are particularly verbose, they could easily crowd out the code that you're trying to write and obscure your context. ## Rationale and alternatives - +One alternative may be to show review comments _only_ within the "changes" tab of an `IssueishPaneItem`. This simplifies flow considerably, because it removes the need to provide navigation among different views of the same review, and unifies the handling of current and non-current pull requests. However, I believe that the renderings of reviews in all three places each serve a unique purpose: + +* Reviews within the "Changes" tab of the `IssueishPaneItem` reveal a narrative formed by all of the reviews on a specific pull request together, as a conversation among reviewers. +* Reviews within the "Review" tab of the `IssueishPaneItem` reveal the narrative flow within each review individually. For example, review comments that refer to other comments within the same review (e.g. "same here" or "and again") become clearer here. +* Review comments within open TextEditors allow the reader to use more context within the source code to evaluate, address, or respond to each individual comment thread: consistency with functions that are not visible within the immediate diff, context within algorithms that span many lines. They also allow the receiver of a review to preserve the mental context of the review communication as they move back and forth between reading the content of a review and applying it to their source. ## Unresolved questions - +_Questions I expect to address before this is merged:_ + +How do we author new reviews within Atom? + +Can we access "draft" reviews from the GitHub API, to unify them between Atom and GitHub? + +How do we represent the resolution of a comment thread? Where can we reveal this progress through each review, and of all required reviews? + +Are there any design choices we can make to lessen the emotional weight of a "requests changes" review? Peer review has the most value when it discovers issues for the pull request author to address, but accepting criticism is a vulnerable moment. + +Similarly, are there any ways we can encourage empathy within the review authoring process? Can we encourage reviewers to make positive comments or demonstrate humility and open-mindedness? + +_Questions I expect to resolve throughout the implementation process:_ + +Review comment positioning within live TextEditors will be a tricky problem to address satisfactorily. What are the edge cases we need to handle there? + +The GraphQL API paths we need to interact with all involve multiple levels of pagination: pull requests, pull request reviews, review comments. How do we handle these within Relay? Or do we interact directly with GraphQL requests? + +How do we handle comment threads? + +_Questions I consider out of scope of this RFC:_ + +What other pull request information can we add to the GitHub pane item? + +Are there other tabs that we need within the `IssueishPaneItem`? + +How can we notify users when new information, including reviews, is available, preferably without being intrusive or disruptive? ## Implementation phases From 410f8e30a7f8786bbcd337af0777bd314e2af276 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 28 Sep 2018 19:40:23 +0200 Subject: [PATCH 0336/4053] code review feedback: be more specific with which arguments are expected in each test --- test/controllers/status-bar-tile-controller.test.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/controllers/status-bar-tile-controller.test.js b/test/controllers/status-bar-tile-controller.test.js index 267f913596..002bc1f621 100644 --- a/test/controllers/status-bar-tile-controller.test.js +++ b/test/controllers/status-bar-tile-controller.test.js @@ -661,7 +661,9 @@ describe('StatusBarTileController', function() { await wrapper .instance() .fetch(await wrapper.instance().fetchData(repository))(); - assert.isTrue(repository.fetch.called); + assert.isTrue(repository.fetch.calledWith('refs/heads/master', { + remoteName: 'origin' + })); }); it('pulls from the correct branch', async function() { @@ -672,7 +674,9 @@ describe('StatusBarTileController', function() { .instance() .pull(await wrapper.instance().fetchData(repository))(); const postPullSHA = await repository.git.exec(['rev-parse', 'HEAD']); - assert.isTrue(repository.pull.called); + assert.isTrue(repository.pull.calledWith('another-name', { + refSpec: 'master:another-name', + })); assert.equal(prePullSHA, postPullSHA); }); @@ -684,7 +688,9 @@ describe('StatusBarTileController', function() { .instance() .push(await wrapper.instance().fetchData(repository))(); const remoteSHA = await repository.git.exec(['rev-parse', 'origin/master']); - assert.isTrue(repository.push.called); + assert.isTrue(repository.push.calledWith('another-name', + sinon.match({refSpec: 'another-name:master'}), + )); assert.equal(localSHA, remoteSHA); }); From e5410a31e8a9de99072c9efa8a9813d1af925180 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 28 Sep 2018 13:44:22 -0400 Subject: [PATCH 0337/4053] Review creation --- docs/rfcs/XXX-pull-request-review.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 88b50356b8..ba4cf9e5af 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -29,6 +29,14 @@ Reviews on the current pull request are rendered as a list on the current pull r * Clicking a line comment opens or activates an editor on the referenced file and scrolls to center the comment's line, translated according to local changes if appropriate. * Line comments within the review are rendered: _with a dot_ before the file has been opened and the corresponding decoration is visible; _with no icon_ after the file and decoration have been seen; and _with a checkmark_ after the comment has been marked "resolved" with the control on its decoration. +If a new review has been started locally, it appears at the top of the "Reviews" section within this tile: + +![pending-review](https://user-images.githubusercontent.com/378023/40404269-db852df6-5e91-11e8-9e16-bae433d3a5f9.png) + +* The review summary is a TextEditor that may be used to compose a summary comment. +* Choosing "Cancel" dismisses the review and any comments made. If there are local review comments that will be lost, a confirmation prompt is shown first. +* Choosing "Submit review" submits the drafted review to GitHub. + ### Non-current pull request tiles Pull request tiles other than the current pull request display a one-line review summary, showing the number of accepting, comment, and change-request reviews made on each. Clicking the review summary opens the `IssueishPaneItem` for that pull request and opens the reviews tab. @@ -49,6 +57,8 @@ Each `IssueishPaneItem` opened on a pull request has a "Changes" tab that shows * The "comment" button is disabled unless the "reply" editor is expanded and has non-whitespace content. * Clicking "comment" submits the response as a new stand-alone comment on that thread. +Hovering in the diff's gutter reveals a `+` icon that allows users to begin creating a new review with the same UI as described in the "In-editor decorations" section. + ### IssueishPaneItem "Reviews" tab Additionally, each has a "Reviews" tab that shows all reviews associated with this pull request in an accordion-style list. Unexpanded, each review is shown as its full summary comment and chosen outcome (comment, approve, or request changes). Expanded, its associated review comments are listed as well on their proximate diffs. @@ -59,6 +69,10 @@ Additionally, each has a "Reviews" tab that shows all reviews associated with th * The up and down arrow buttons and :hamburger: button are not present here. * The "code" (`<>`) button also behaves as it does on the "Changes" tab. +A local, pending review created by the user is also shown at the top of this list, with controls to edit its summary comment and choose its final state. + +> TODO: sketch here + ### In-editor decorations When opening a TextEditor on a file that has been annotated with review comments on the current pull request, a block decoration is used to show the comment content at the corresponding position within the file content. Also, a gutter decoration is used to reveal lines that are included within the current pull requests' diff and may therefore include comments. @@ -71,6 +85,14 @@ When opening a TextEditor on a file that has been annotated with review comments * The up and down arrow buttons navigate to the next and previous review comments within this review within their respective TextEditors. * The "diff" button navigates to the "Reviews" tab of the corresponding pull request's `IssueishPaneItem`, expands the owning review, and scrolls to center the same comment within that view. +Hovering along the gutter within a pull request diff region reveals a `+` icon, which may be clicked to begin a new review: + +![plus-icon](https://user-images.githubusercontent.com/378023/40348708-6698b2ea-5ddf-11e8-8eaa-9d95bc483fb1.png) + +Clicking the `+` reveals a new comment box, which may be used to submit a single comment or begin a multi-comment review: + +![single-review](https://user-images.githubusercontent.com/378023/40351475-78a527c2-5de7-11e8-8006-72d859514ecc.png) + ## Drawbacks This adds a substantial amount of complexity to the UI, which is only justified for users that use GitHub pull request reviews. From 3b7ac4a873458cc56fcd775db567acaab6a2e9de Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 28 Sep 2018 13:52:00 -0400 Subject: [PATCH 0338/4053] Use real headers --- docs/rfcs/XXX-pull-request-review.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index ba4cf9e5af..7884f96938 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -111,7 +111,7 @@ One alternative may be to show review comments _only_ within the "changes" tab o ## Unresolved questions -_Questions I expect to address before this is merged:_ +### Questions I expect to address before this is merged How do we author new reviews within Atom? @@ -123,7 +123,7 @@ Are there any design choices we can make to lessen the emotional weight of a "re Similarly, are there any ways we can encourage empathy within the review authoring process? Can we encourage reviewers to make positive comments or demonstrate humility and open-mindedness? -_Questions I expect to resolve throughout the implementation process:_ +### Questions I expect to resolve throughout the implementation process Review comment positioning within live TextEditors will be a tricky problem to address satisfactorily. What are the edge cases we need to handle there? @@ -131,7 +131,7 @@ The GraphQL API paths we need to interact with all involve multiple levels of pa How do we handle comment threads? -_Questions I consider out of scope of this RFC:_ +### Questions I consider out of scope of this RFC What other pull request information can we add to the GitHub pane item? From 85ba758e03c49ac7b5a8802439a948451917b2d0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 28 Sep 2018 13:52:38 -0400 Subject: [PATCH 0339/4053] Kind of answered that one myself --- docs/rfcs/XXX-pull-request-review.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 7884f96938..1bfb65741e 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -113,8 +113,6 @@ One alternative may be to show review comments _only_ within the "changes" tab o ### Questions I expect to address before this is merged -How do we author new reviews within Atom? - Can we access "draft" reviews from the GitHub API, to unify them between Atom and GitHub? How do we represent the resolution of a comment thread? Where can we reveal this progress through each review, and of all required reviews? From 194d36295371071b467a0f49ab08db14c3d2697b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 28 Sep 2018 15:11:26 -0400 Subject: [PATCH 0340/4053] Add a mock-up for non-current pull request tiles --- docs/rfcs/XXX-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 1bfb65741e..26178edf59 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -41,7 +41,7 @@ If a new review has been started locally, it appears at the top of the "Reviews" Pull request tiles other than the current pull request display a one-line review summary, showing the number of accepting, comment, and change-request reviews made on each. Clicking the review summary opens the `IssueishPaneItem` for that pull request and opens the reviews tab. -> TODO: sketch here +![non-current pull request tile](https://user-images.githubusercontent.com/17565/46228625-a2aea080-c330-11e8-945b-72be7824623f.png) ### IssueishPaneItem "Changes" tab From 81a53e07dc5375edd3af12e9b1974d6257489863 Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 1 Oct 2018 15:44:10 +0900 Subject: [PATCH 0341/4053] Update review-list mockup --- docs/rfcs/XXX-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 26178edf59..aa2cc0ab82 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -22,7 +22,7 @@ Peer review is also a critical part of the path to acceptance for pull requests Reviews on the current pull request are rendered as a list on the current pull request tile. -![review-list](https://user-images.githubusercontent.com/378023/44708426-1f582000-aae2-11e8-86bd-3074ae259e2d.png) +![review-list](https://user-images.githubusercontent.com/378023/46273505-b9533280-c590-11e8-840e-a8eac8023cad.png) * The review summary bubble is elided after the first sentence or N characters if necessary. * Clicking the review summary bubble opens an `IssueishPaneItem` in the workspace center, open to the reviews tab. From 12532be1ff3a9489d3fd842a9b6be44ffe544f7a Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 1 Oct 2018 16:22:58 +0900 Subject: [PATCH 0342/4053] Add reviews-tab mockup --- docs/rfcs/XXX-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index aa2cc0ab82..51b7f2283c 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -63,7 +63,7 @@ Hovering in the diff's gutter reveals a `+` icon that allows users to begin crea Additionally, each has a "Reviews" tab that shows all reviews associated with this pull request in an accordion-style list. Unexpanded, each review is shown as its full summary comment and chosen outcome (comment, approve, or request changes). Expanded, its associated review comments are listed as well on their proximate diffs. -> TODO: sketch here +![reviews-tab](https://user-images.githubusercontent.com/378023/46274944-20bfb100-c596-11e8-83c0-363904ca7d5f.png) * The "Mark as resolved" and "comment" buttons and the "reply" text areas match their behavior in the "Changes" tab. * The up and down arrow buttons and :hamburger: button are not present here. From 3279a2692d363a157b7d286f405a1f312ea0d765 Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 1 Oct 2018 16:48:01 +0900 Subject: [PATCH 0343/4053] Update pending-review mockup --- docs/rfcs/XXX-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 51b7f2283c..70dd5005e5 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -31,7 +31,7 @@ Reviews on the current pull request are rendered as a list on the current pull r If a new review has been started locally, it appears at the top of the "Reviews" section within this tile: -![pending-review](https://user-images.githubusercontent.com/378023/40404269-db852df6-5e91-11e8-9e16-bae433d3a5f9.png) +![pending-review](https://user-images.githubusercontent.com/378023/46275946-9bd69680-c599-11e8-9889-66c35458286a.png) * The review summary is a TextEditor that may be used to compose a summary comment. * Choosing "Cancel" dismisses the review and any comments made. If there are local review comments that will be lost, a confirmation prompt is shown first. From d902dcfee9f6e76775827d5e659696fc2c48d8b0 Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 1 Oct 2018 21:06:57 +0900 Subject: [PATCH 0344/4053] Replace changes and reviews tab mockups --- docs/rfcs/XXX-pull-request-review.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 70dd5005e5..8489d9f2eb 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -47,7 +47,7 @@ Pull request tiles other than the current pull request display a one-line review Each `IssueishPaneItem` opened on a pull request has a "Changes" tab that shows the full PR diff, annotated with comments from all reviews. -![changes-tab](https://user-images.githubusercontent.com/378023/44789879-ba332600-abd8-11e8-9247-a19015ccd760.png) +![changes-tab](https://user-images.githubusercontent.com/378023/46287431-6e9bdf80-c5bd-11e8-99eb-f3f81ba64e81.png) * The up and down arrow buttons quickly scroll to center the next or previous comment within this tab. * Clicking the :hamburger: button navigates to the "Reviews" tab, expands the owning review, and scrolls to the same comment within that view. @@ -63,7 +63,7 @@ Hovering in the diff's gutter reveals a `+` icon that allows users to begin crea Additionally, each has a "Reviews" tab that shows all reviews associated with this pull request in an accordion-style list. Unexpanded, each review is shown as its full summary comment and chosen outcome (comment, approve, or request changes). Expanded, its associated review comments are listed as well on their proximate diffs. -![reviews-tab](https://user-images.githubusercontent.com/378023/46274944-20bfb100-c596-11e8-83c0-363904ca7d5f.png) +![reviews-tab](https://user-images.githubusercontent.com/378023/46287432-6e9bdf80-c5bd-11e8-8290-50dd17a2c733.png) * The "Mark as resolved" and "comment" buttons and the "reply" text areas match their behavior in the "Changes" tab. * The up and down arrow buttons and :hamburger: button are not present here. From a1324c5da3366a660975d184127a0cbf72c4f7cb Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 1 Oct 2018 14:53:31 +0200 Subject: [PATCH 0345/4053] linter again argh --- test/controllers/status-bar-tile-controller.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/controllers/status-bar-tile-controller.test.js b/test/controllers/status-bar-tile-controller.test.js index aaf332e2af..60a4f2ce16 100644 --- a/test/controllers/status-bar-tile-controller.test.js +++ b/test/controllers/status-bar-tile-controller.test.js @@ -654,7 +654,7 @@ describe('StatusBarTileController', function() { describe('when the local branch is named differently from the remote branch it\'s tracking', function() { let repository, wrapper; - + beforeEach(async function() { const {localRepoPath} = await setUpLocalAndRemoteRepositories(); repository = await buildRepository(localRepoPath); @@ -669,7 +669,7 @@ describe('StatusBarTileController', function() { .instance() .fetch(await wrapper.instance().fetchData(repository))(); assert.isTrue(repository.fetch.calledWith('refs/heads/master', { - remoteName: 'origin' + remoteName: 'origin', })); }); From e79d18113165d1aadbfab0415ca237d64f32bd9f Mon Sep 17 00:00:00 2001 From: MimiDumpling Date: Mon, 1 Oct 2018 15:27:08 -0700 Subject: [PATCH 0346/4053] fixed merged commit text Co-Authored-By: tilde --- lib/views/timeline-items/merged-event-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/timeline-items/merged-event-view.js b/lib/views/timeline-items/merged-event-view.js index 246ab991f4..441c7a5c94 100644 --- a/lib/views/timeline-items/merged-event-view.js +++ b/lib/views/timeline-items/merged-event-view.js @@ -27,7 +27,7 @@ export class BareMergedEventView extends React.Component { {actor && {actor.login}} - {actor ? actor.login : 'someone'} merged + {actor ? actor.login : 'someone'} merged{' '} {this.renderCommit()} into {' '}{mergeRefName} on From 90c0a44a9b6502fa5cf8e184b8476b303922eb7a Mon Sep 17 00:00:00 2001 From: MimiDumpling Date: Mon, 1 Oct 2018 15:44:28 -0700 Subject: [PATCH 0347/4053] created test for merged commit text Co-Authored-By: tilde --- test/views/timeline-items/merged-event-view.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/views/timeline-items/merged-event-view.test.js b/test/views/timeline-items/merged-event-view.test.js index b66d726acf..61caee1884 100644 --- a/test/views/timeline-items/merged-event-view.test.js +++ b/test/views/timeline-items/merged-event-view.test.js @@ -60,4 +60,11 @@ describe('MergedEventView', function() { assert.strictEqual(wrapper.find('.username').text(), 'someone'); assert.isFalse(wrapper.find('.sha').exists()); }); + + it('renders a space between merged and commit', function() { + const wrapper = shallow(buildApp({})); + const text = wrapper.find(".merged-event-header").text(); + + assert.isTrue(text.includes("merged commit")); + }) }); From 86ad935a788f041e750bd56146ca2dd138e4ebc6 Mon Sep 17 00:00:00 2001 From: MimiDumpling Date: Mon, 1 Oct 2018 15:58:46 -0700 Subject: [PATCH 0348/4053] :shirt: Co-Authored-By: tilde --- test/views/timeline-items/merged-event-view.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/views/timeline-items/merged-event-view.test.js b/test/views/timeline-items/merged-event-view.test.js index 61caee1884..268a4c8e1a 100644 --- a/test/views/timeline-items/merged-event-view.test.js +++ b/test/views/timeline-items/merged-event-view.test.js @@ -63,8 +63,8 @@ describe('MergedEventView', function() { it('renders a space between merged and commit', function() { const wrapper = shallow(buildApp({})); - const text = wrapper.find(".merged-event-header").text(); + const text = wrapper.find('.merged-event-header').text(); - assert.isTrue(text.includes("merged commit")); - }) + assert.isTrue(text.includes('merged commit')); + }); }); From 2b268a8c2e533742fead74f11dbe4e6175272452 Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Tue, 2 Oct 2018 16:44:53 +0530 Subject: [PATCH 0349/4053] Add commit template feature --- lib/get-repo-pipeline-manager.js | 3 ++- lib/git-shell-out-strategy.js | 7 +++++++ lib/models/repository-states/present.js | 14 ++++++++++++++ lib/models/repository-states/state.js | 4 ++++ lib/models/repository.js | 1 + 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/get-repo-pipeline-manager.js b/lib/get-repo-pipeline-manager.js index 489eea22a1..0e3bddc549 100644 --- a/lib/get-repo-pipeline-manager.js +++ b/lib/get-repo-pipeline-manager.js @@ -210,7 +210,8 @@ export default function({confirm, notificationManager, workspace}) { commitPipeline.addMiddleware('failed-to-commit-error', async (next, repository) => { try { const result = await next(); - repository.setCommitMessage(''); + const message = await repository.getCommitMessageFromTemplate(); + repository.setCommitMessage(message || ''); destroyFilePatchPaneItems({onlyStaged: true}, workspace); return result; } catch (error) { diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 2b67539a46..2553aebc76 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -415,6 +415,13 @@ export default class GitShellOutStrategy { return this.exec(args, {writeOperation: true}); } + async getCommitMessageFromTemplate() { + const templatePath = await this.getConfig('commit.template'); + if (!templatePath || ! await fileExists(templatePath)) return; + const message = await fs.readFile(templatePath, { encoding: 'utf8'}) + return message + } + unstageFiles(paths, commit = 'HEAD') { if (paths.length === 0) { return Promise.resolve(null); } const args = ['reset', commit, '--'].concat(paths.map(toGitPathSep)); diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 4e6d421d73..e873c39d51 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -41,6 +41,7 @@ export default class Present extends State { this.operationStates = new OperationStates({didUpdate: this.didUpdate.bind(this)}); this.commitMessage = ''; + this.setCommitMessageFromTemplate(); if (history) { this.discardHistory.updateHistory(history); @@ -55,6 +56,19 @@ export default class Present extends State { return this.commitMessage; } + async setCommitMessageFromTemplate() { + const message = await this.getCommitMessageFromTemplate(); + if (!message) { + return; + } + this.setCommitMessage(message) + this.didUpdate() + } + + async getCommitMessageFromTemplate() { + return await this.git().getCommitMessageFromTemplate(); + } + getOperationStates() { return this.operationStates; } diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 9c66928e7b..afdd3c2c59 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -373,6 +373,10 @@ export default class State { return ''; } + getCommitMessageFromTemplate() { + return unsupportedOperationPromise(this, 'getCommitMessageFromTemplate'); + } + // Cache getCache() { diff --git a/lib/models/repository.js b/lib/models/repository.js index ab50db5f52..0d80e05950 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -358,6 +358,7 @@ const delegates = [ 'setCommitMessage', 'getCommitMessage', + 'getCommitMessageFromTemplate', 'getCache', ]; From 2e22995374a2526c76bddcf1209668d62e01b391 Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Tue, 2 Oct 2018 16:52:55 +0530 Subject: [PATCH 0350/4053] Add new fixture repo for commit.template testing --- test/fixtures/repo-commit-template/a.txt | 1 + test/fixtures/repo-commit-template/b.txt | 1 + test/fixtures/repo-commit-template/c.txt | 1 + .../repo-commit-template/dot-git/HEAD | 1 + .../repo-commit-template/dot-git/config | 9 + .../repo-commit-template/dot-git/description | 1 + .../dot-git/hooks/applypatch-msg.sample | 15 ++ .../dot-git/hooks/commit-msg.sample | 24 +++ .../dot-git/hooks/fsmonitor-watchman.sample | 114 ++++++++++++ .../dot-git/hooks/post-update.sample | 8 + .../dot-git/hooks/pre-applypatch.sample | 14 ++ .../dot-git/hooks/pre-commit.sample | 49 +++++ .../dot-git/hooks/pre-push.sample | 53 ++++++ .../dot-git/hooks/pre-rebase.sample | 169 ++++++++++++++++++ .../dot-git/hooks/pre-receive.sample | 24 +++ .../dot-git/hooks/prepare-commit-msg.sample | 42 +++++ .../dot-git/hooks/update.sample | 128 +++++++++++++ .../repo-commit-template/dot-git/index | Bin 0 -> 634 bytes .../repo-commit-template/dot-git/info/exclude | 6 + .../repo-commit-template/dot-git/logs/HEAD | 2 + .../dot-git/logs/refs/heads/master | 2 + .../25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 | Bin 0 -> 19 bytes .../3f/af8f2e3b6247c9d7e86d3849612cba95e94af4 | Bin 0 -> 179 bytes .../4b/825dc642cb6eb9a060e54bf8d69288fbee4904 | Bin 0 -> 15 bytes .../57/16ca5987cbf97d6bb54920bea6adde242d87e6 | Bin 0 -> 19 bytes .../76/018072e09c5d31c8c6e3113b8aa0fe625195ca | Bin 0 -> 19 bytes .../d4/d73b42443436ee23eebd43174645bb4b9eaf31 | 2 + .../ea/e629e0574add4da0f67056fd3ec128c18b2c47 | Bin 0 -> 102 bytes .../ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 | Bin 0 -> 151 bytes .../f9/14a98b83588057c160f6a29b8c0a0f54e44548 | Bin 0 -> 50 bytes .../dot-git/refs/heads/master | 1 + .../repo-commit-template/gitmessage.txt | 1 + .../repo-commit-template/subdir-1/a.txt | 1 + .../repo-commit-template/subdir-1/b.txt | 1 + .../repo-commit-template/subdir-1/c.txt | 1 + 35 files changed, 671 insertions(+) create mode 100644 test/fixtures/repo-commit-template/a.txt create mode 100644 test/fixtures/repo-commit-template/b.txt create mode 100644 test/fixtures/repo-commit-template/c.txt create mode 100644 test/fixtures/repo-commit-template/dot-git/HEAD create mode 100644 test/fixtures/repo-commit-template/dot-git/config create mode 100644 test/fixtures/repo-commit-template/dot-git/description create mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/applypatch-msg.sample create mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/commit-msg.sample create mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/fsmonitor-watchman.sample create mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/post-update.sample create mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/pre-applypatch.sample create mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/pre-commit.sample create mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/pre-push.sample create mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/pre-rebase.sample create mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/pre-receive.sample create mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/prepare-commit-msg.sample create mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/update.sample create mode 100644 test/fixtures/repo-commit-template/dot-git/index create mode 100644 test/fixtures/repo-commit-template/dot-git/info/exclude create mode 100644 test/fixtures/repo-commit-template/dot-git/logs/HEAD create mode 100644 test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/3f/af8f2e3b6247c9d7e86d3849612cba95e94af4 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/76/018072e09c5d31c8c6e3113b8aa0fe625195ca create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/d4/d73b42443436ee23eebd43174645bb4b9eaf31 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/ea/e629e0574add4da0f67056fd3ec128c18b2c47 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 create mode 100644 test/fixtures/repo-commit-template/dot-git/refs/heads/master create mode 100644 test/fixtures/repo-commit-template/gitmessage.txt create mode 100644 test/fixtures/repo-commit-template/subdir-1/a.txt create mode 100644 test/fixtures/repo-commit-template/subdir-1/b.txt create mode 100644 test/fixtures/repo-commit-template/subdir-1/c.txt diff --git a/test/fixtures/repo-commit-template/a.txt b/test/fixtures/repo-commit-template/a.txt new file mode 100644 index 0000000000..257cc5642c --- /dev/null +++ b/test/fixtures/repo-commit-template/a.txt @@ -0,0 +1 @@ +foo diff --git a/test/fixtures/repo-commit-template/b.txt b/test/fixtures/repo-commit-template/b.txt new file mode 100644 index 0000000000..5716ca5987 --- /dev/null +++ b/test/fixtures/repo-commit-template/b.txt @@ -0,0 +1 @@ +bar diff --git a/test/fixtures/repo-commit-template/c.txt b/test/fixtures/repo-commit-template/c.txt new file mode 100644 index 0000000000..76018072e0 --- /dev/null +++ b/test/fixtures/repo-commit-template/c.txt @@ -0,0 +1 @@ +baz diff --git a/test/fixtures/repo-commit-template/dot-git/HEAD b/test/fixtures/repo-commit-template/dot-git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/fixtures/repo-commit-template/dot-git/config b/test/fixtures/repo-commit-template/dot-git/config new file mode 100644 index 0000000000..52b216917a --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/config @@ -0,0 +1,9 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[commit] + template = gitmessage.txt diff --git a/test/fixtures/repo-commit-template/dot-git/description b/test/fixtures/repo-commit-template/dot-git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/applypatch-msg.sample b/test/fixtures/repo-commit-template/dot-git/hooks/applypatch-msg.sample new file mode 100755 index 0000000000..a5d7b84a67 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/commit-msg.sample b/test/fixtures/repo-commit-template/dot-git/hooks/commit-msg.sample new file mode 100755 index 0000000000..b58d1184a9 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/fsmonitor-watchman.sample b/test/fixtures/repo-commit-template/dot-git/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000000..e673bb3980 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/hooks/fsmonitor-watchman.sample @@ -0,0 +1,114 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 1) and a time in nanoseconds +# formatted as a string and outputs to stdout all files that have been +# modified since the given time. Paths must be relative to the root of +# the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $time) = @ARGV; + +# Check the hook interface version + +if ($version == 1) { + # convert nanoseconds to seconds + $time = int $time / 1000000000; +} else { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree; +if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $git_work_tree = Win32::GetCwd(); + $git_work_tree =~ tr/\\/\//; +} else { + require Cwd; + $git_work_tree = Cwd::cwd(); +} + +my $retry = 1; + +launch_watchman(); + +sub launch_watchman { + + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $time but were not transient (ie created after + # $time but no longer exist). + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + # + # The category of transient files that we want to ignore will have a + # creation clock (cclock) newer than $time_t value and will also not + # currently exist. + + my $query = <<" END"; + ["query", "$git_work_tree", { + "since": $time, + "fields": ["name"], + "expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]] + }] + END + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + my $json_pkg; + eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; + } or do { + require JSON::PP; + $json_pkg = "JSON::PP"; + }; + + my $o = $json_pkg->new->utf8->decode($response); + + if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) { + print STDERR "Adding '$git_work_tree' to watchman's watch list.\n"; + $retry--; + qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + print "/\0"; + eval { launch_watchman() }; + exit 0; + } + + die "Watchman: $o->{error}.\n" . + "Falling back to scanning...\n" if $o->{error}; + + binmode STDOUT, ":utf8"; + local $, = "\0"; + print @{$o->{files}}; +} diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/post-update.sample b/test/fixtures/repo-commit-template/dot-git/hooks/post-update.sample new file mode 100755 index 0000000000..ec17ec1939 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/pre-applypatch.sample b/test/fixtures/repo-commit-template/dot-git/hooks/pre-applypatch.sample new file mode 100755 index 0000000000..4142082bcb --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/pre-commit.sample b/test/fixtures/repo-commit-template/dot-git/hooks/pre-commit.sample new file mode 100755 index 0000000000..68d62d5446 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/pre-push.sample b/test/fixtures/repo-commit-template/dot-git/hooks/pre-push.sample new file mode 100755 index 0000000000..6187dbf439 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/pre-rebase.sample b/test/fixtures/repo-commit-template/dot-git/hooks/pre-rebase.sample new file mode 100755 index 0000000000..6cbef5c370 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/pre-receive.sample b/test/fixtures/repo-commit-template/dot-git/hooks/pre-receive.sample new file mode 100755 index 0000000000..a1fd29ec14 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/hooks/pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/prepare-commit-msg.sample b/test/fixtures/repo-commit-template/dot-git/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000..10fa14c5ab --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/hooks/prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/update.sample b/test/fixtures/repo-commit-template/dot-git/hooks/update.sample new file mode 100755 index 0000000000..80ba94135c --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/test/fixtures/repo-commit-template/dot-git/index b/test/fixtures/repo-commit-template/dot-git/index new file mode 100644 index 0000000000000000000000000000000000000000..064b3f3adc4f9d0fc3c482a70cba225911936212 GIT binary patch literal 634 zcmZ?q402{*U|<4b_UO&Q%Y@^<9)i(~3=Av`wqHva7#f!_Ffe`vsu2NV7S)=gDLNY$ zgnZ~ZVXr&IF6^(}zL^ZHiFzd!K&3zc)}O7U2&19ql%ksxE_N!i{q)b;?5&;(`|E#?bbH1q(We#6@W=U>padBdLD$GETdqm}?yoJ$FcdbP?4{JE_6_+NZ zWESZf>cayJY>t-jH5d&wZymaMSi=ip9z5W{=9vBa45Ojutw%QxYq%lIg9l(pkgF>& zTCEt&6%4uFC-e%>Jbb|T{O6xvmd?Lk_3(4+6quJ7j1>&HUOm%%5bkx?cfq#;V9GeC eaj;v*ecSuJ?D7jr_FNVZiJvO}^4Q|L0U7{%+uH#E literal 0 HcmV?d00001 diff --git a/test/fixtures/repo-commit-template/dot-git/info/exclude b/test/fixtures/repo-commit-template/dot-git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/test/fixtures/repo-commit-template/dot-git/logs/HEAD b/test/fixtures/repo-commit-template/dot-git/logs/HEAD new file mode 100644 index 0000000000..678e774168 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 d4d73b42443436ee23eebd43174645bb4b9eaf31 Gaurav Chikhale 1538479109 +0530 commit (initial): Initial commit +d4d73b42443436ee23eebd43174645bb4b9eaf31 3faf8f2e3b6247c9d7e86d3849612cba95e94af4 Gaurav Chikhale 1538479155 +0530 commit: Add git message diff --git a/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master b/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master new file mode 100644 index 0000000000..678e774168 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 d4d73b42443436ee23eebd43174645bb4b9eaf31 Gaurav Chikhale 1538479109 +0530 commit (initial): Initial commit +d4d73b42443436ee23eebd43174645bb4b9eaf31 3faf8f2e3b6247c9d7e86d3849612cba95e94af4 Gaurav Chikhale 1538479155 +0530 commit: Add git message diff --git a/test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 b/test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 new file mode 100644 index 0000000000000000000000000000000000000000..bdcf704c9e663f3a11b3146b1b455bc2581b4761 GIT binary patch literal 19 acmb^)#C@5-_ugq9xq9V=;Nnfiq+2m1Fl`G#EQFf(CW zmZ?J54cP;pr6gc%1 I09Vl}{eDC-*#H0l literal 0 HcmV?d00001 diff --git a/test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 b/test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 new file mode 100644 index 0000000000000000000000000000000000000000..a90cc1ccb50a8c244093ae8c44b4903ad0394260 GIT binary patch literal 151 zcmV;I0BHYs0V^p=O;s>7H)Aj~FfcPQQApG)sVHGktvQ;avvEPlhn^Gmx>M}J{@U%E z3005;RuC?BDzg3b&)V#*o(lVxt-YtB+x`ryAQ`NnjIp8U!JJsb6UQD4T6Zn@mlQbl z6jVWaW=U>padBdLDo&Lq20)-tT$+@US)^;o@amc7gK)3Az6-t;0G)DB<6yUrI{@-l FOLuJZM@Ikv literal 0 HcmV?d00001 diff --git a/test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 b/test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 new file mode 100644 index 0000000000000000000000000000000000000000..6e37521f9b2536eebd27c18ce54b0f62bd26e992 GIT binary patch literal 50 zcmV-20L}k+0V^p=O;s>9WiT-S0)^tzq?F7eT| Date: Tue, 2 Oct 2018 17:17:06 +0530 Subject: [PATCH 0351/4053] Update helper to set commit.template's absolute file location properly --- test/helpers.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/helpers.js b/test/helpers.js index afff422aa5..60da235b7c 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -42,9 +42,20 @@ export async function cloneRepository(repoName = 'three-files') { if (!cachedClonedRepos[repoName]) { const cachedPath = temp.mkdirSync('git-fixture-cache-'); const git = new GitShellOutStrategy(cachedPath); - await git.clone(path.join(__dirname, 'fixtures', `repo-${repoName}`, 'dot-git'), {noLocal: true}); + const repoPath = path.join(__dirname, 'fixtures', `repo-${repoName}`, 'dot-git'); + + // templatePath is the absolute path of the + // commit.template file(gitmessage.txt) stored in temp location + let templatePath = ''; + try { + const templateFile = await git.exec(['config', '--file', repoPath + '/config', 'commit.template']); + templatePath = `${cachedPath}/${templateFile}`; + } catch(err) {} + + await git.clone(repoPath, {noLocal: true}); await git.exec(['config', '--local', 'core.autocrlf', 'false']); await git.exec(['config', '--local', 'commit.gpgsign', 'false']); + await git.exec(['config', '--local', 'commit.template', templatePath]); await git.exec(['config', '--local', 'user.email', FAKE_USER.email]); await git.exec(['config', '--local', 'user.name', FAKE_USER.name]); await git.exec(['checkout', '--', '.']); // discard \r in working directory From a1450f1213bd8ad215d7ac4446ac18d21148a49b Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Tue, 2 Oct 2018 17:56:18 +0530 Subject: [PATCH 0352/4053] Add some tests --- test/controllers/commit-controller.test.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index a214d0e081..892ed9c192 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -60,6 +60,9 @@ describe('CommitController', function() { const repository1 = await buildRepository(workdirPath1); const workdirPath2 = await cloneRepository('three-files'); const repository2 = await buildRepository(workdirPath2); + const workdirPath3 = await cloneRepository('commit-template'); + const repository3 = await buildRepository(workdirPath3); + const templateCommitMessage = await repository3.git.getCommitMessageFromTemplate(); app = React.cloneElement(app, {repository: repository1}); const wrapper = shallow(app, {disableLifecycleMethods: true}); @@ -74,6 +77,8 @@ describe('CommitController', function() { wrapper.setProps({repository: repository1}); assert.equal(wrapper.instance().getCommitMessage(), 'message 1'); + wrapper.setProps({repository: repository3}); + assert.equal(wrapper.instance().getCommitMessage(), templateCommitMessage); }); describe('the passed commit message', function() { @@ -140,6 +145,21 @@ describe('CommitController', function() { assert.strictEqual(repository.getCommitMessage(), ''); }); + it('reload the commit messages from commit template', async function() { + const workdirPath = await cloneRepository('commit-template'); + const repository = await buildRepositoryWithPipeline(workdirPath, {confirm, notificationManager, workspace}); + const templateCommitMessage = await repository.git.getCommitMessageFromTemplate(); + const commit = sinon.stub().callsFake((...args) => repository.commit(...args)); + const app2 = React.cloneElement(app, {repository, commit}); + + await fs.writeFile(path.join(workdirPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); + await repository.git.exec(['add', '.']); + + const wrapper = shallow(app2, {disableLifecycleMethods: true}); + await wrapper.instance().commit('some message'); + assert.strictEqual(repository.getCommitMessage(), templateCommitMessage); + }); + it('sets the verbatim flag when committing from the mini editor', async function() { await fs.writeFile(path.join(workdirPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); await repository.git.exec(['add', '.']); From 64f2433654ae107ca0632be8649eb33f94721a10 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 11:12:00 -0400 Subject: [PATCH 0353/4053] "position: sticky" navigation and filter banner in "Changes" tab Co-Authored-By: Katrina Uychaco --- docs/rfcs/XXX-pull-request-review.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 8489d9f2eb..3cb281e65d 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -45,10 +45,11 @@ Pull request tiles other than the current pull request display a one-line review ### IssueishPaneItem "Changes" tab -Each `IssueishPaneItem` opened on a pull request has a "Changes" tab that shows the full PR diff, annotated with comments from all reviews. +Each `IssueishPaneItem` opened on a pull request has a "Changes" tab that shows the full PR diff, annotated with comments from all reviews. A banner at the top of the diff offers navigation to individual files within the diff and to individual review comments, allows each review to be hidden or shown with a filter control, and shows a progress bar that counts "resolved" review comments. ![changes-tab](https://user-images.githubusercontent.com/378023/46287431-6e9bdf80-c5bd-11e8-99eb-f3f81ba64e81.png) +* The navigation and filter banner remains visible as the "Changes" tab is scrolled. * The up and down arrow buttons quickly scroll to center the next or previous comment within this tab. * Clicking the :hamburger: button navigates to the "Reviews" tab, expands the owning review, and scrolls to the same comment within that view. * Clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the review comment decoration there. From 0cb878eb15f30e033b7242e0a023b8ed21ca9551 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 11:15:55 -0400 Subject: [PATCH 0354/4053] Unify the "Reviews" and "Changes" tabs Co-Authored-By: Katrina Uychaco Co-Authored-By: Vanessa Yuen Co-Authored-By: simurai --- docs/rfcs/XXX-pull-request-review.md | 30 +++++----------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 3cb281e65d..1ad9e6266e 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -39,7 +39,7 @@ If a new review has been started locally, it appears at the top of the "Reviews" ### Non-current pull request tiles -Pull request tiles other than the current pull request display a one-line review summary, showing the number of accepting, comment, and change-request reviews made on each. Clicking the review summary opens the `IssueishPaneItem` for that pull request and opens the reviews tab. +Pull request tiles other than the current pull request display a one-line review summary, showing the number of accepting, comment, and change-request reviews made on each. Clicking the review summary opens the `IssueishPaneItem` for that pull request and opens the "Changes" tab. ![non-current pull request tile](https://user-images.githubusercontent.com/17565/46228625-a2aea080-c330-11e8-945b-72be7824623f.png) @@ -51,28 +51,12 @@ Each `IssueishPaneItem` opened on a pull request has a "Changes" tab that shows * The navigation and filter banner remains visible as the "Changes" tab is scrolled. * The up and down arrow buttons quickly scroll to center the next or previous comment within this tab. -* Clicking the :hamburger: button navigates to the "Reviews" tab, expands the owning review, and scrolls to the same comment within that view. -* Clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the review comment decoration there. -* Clicking within the "Reply..." text editor expands the editor to several lines and focuses it. +* Clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the review comment decoration there. If the current pull request is not checked out, the "code" button is disabled, and a tooltip prompts the user to check out the pull request to edit the source. * Clicking "mark as resolved" marks the comment as resolved with on GitHub. If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. * The "comment" button is disabled unless the "reply" editor is expanded and has non-whitespace content. * Clicking "comment" submits the response as a new stand-alone comment on that thread. -Hovering in the diff's gutter reveals a `+` icon that allows users to begin creating a new review with the same UI as described in the "In-editor decorations" section. - -### IssueishPaneItem "Reviews" tab - -Additionally, each has a "Reviews" tab that shows all reviews associated with this pull request in an accordion-style list. Unexpanded, each review is shown as its full summary comment and chosen outcome (comment, approve, or request changes). Expanded, its associated review comments are listed as well on their proximate diffs. - -![reviews-tab](https://user-images.githubusercontent.com/378023/46287432-6e9bdf80-c5bd-11e8-8290-50dd17a2c733.png) - -* The "Mark as resolved" and "comment" buttons and the "reply" text areas match their behavior in the "Changes" tab. -* The up and down arrow buttons and :hamburger: button are not present here. -* The "code" (`<>`) button also behaves as it does on the "Changes" tab. - -A local, pending review created by the user is also shown at the top of this list, with controls to edit its summary comment and choose its final state. - -> TODO: sketch here +Hovering in the diff's gutter reveals a `+` icon that allows users to begin creating a new review with the same UI as described in ["In-editor decorations"](#in-editor-decorations). ### In-editor decorations @@ -84,7 +68,7 @@ When opening a TextEditor on a file that has been annotated with review comments * The comment's position is calculated from the position acquired by the GitHub API response, modified based on the git diff of that file (following renames) between the owning review's attached commit and the current state of the working copy (including any local modifications). Once created, the associated marker will also track unsaved modifications to the file in real time. * The up and down arrow buttons navigate to the next and previous review comments within this review within their respective TextEditors. -* The "diff" button navigates to the "Reviews" tab of the corresponding pull request's `IssueishPaneItem`, expands the owning review, and scrolls to center the same comment within that view. +* The "diff" button navigates to the "Changes" tab of the corresponding pull request's `IssueishPaneItem`, expands the owning review, and scrolls to center the same comment within that view. Hovering along the gutter within a pull request diff region reveals a `+` icon, which may be clicked to begin a new review: @@ -104,11 +88,7 @@ Rendering pull request comments within TextEditors can be intrusive: if there ar ## Rationale and alternatives -One alternative may be to show review comments _only_ within the "changes" tab of an `IssueishPaneItem`. This simplifies flow considerably, because it removes the need to provide navigation among different views of the same review, and unifies the handling of current and non-current pull requests. However, I believe that the renderings of reviews in all three places each serve a unique purpose: - -* Reviews within the "Changes" tab of the `IssueishPaneItem` reveal a narrative formed by all of the reviews on a specific pull request together, as a conversation among reviewers. -* Reviews within the "Review" tab of the `IssueishPaneItem` reveal the narrative flow within each review individually. For example, review comments that refer to other comments within the same review (e.g. "same here" or "and again") become clearer here. -* Review comments within open TextEditors allow the reader to use more context within the source code to evaluate, address, or respond to each individual comment thread: consistency with functions that are not visible within the immediate diff, context within algorithms that span many lines. They also allow the receiver of a review to preserve the mental context of the review communication as they move back and forth between reading the content of a review and applying it to their source. + ## Unresolved questions From e50fa722dddc251f10d0bc74e8f070033f95143a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 11:19:07 -0400 Subject: [PATCH 0355/4053] Emphasize review authoring and finalization on the "Changes" tab Co-Authored-By: Katrina Uychaco Co-Authored-By: simurai --- docs/rfcs/XXX-pull-request-review.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 1ad9e6266e..f9afde3086 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -58,6 +58,8 @@ Each `IssueishPaneItem` opened on a pull request has a "Changes" tab that shows Hovering in the diff's gutter reveals a `+` icon that allows users to begin creating a new review with the same UI as described in ["In-editor decorations"](#in-editor-decorations). +If a pending review is present, its comments are also shown and editable here. A pending review may be finalized by submitting a form that appears at the end of the existing review summary comment list. + ### In-editor decorations When opening a TextEditor on a file that has been annotated with review comments on the current pull request, a block decoration is used to show the comment content at the corresponding position within the file content. Also, a gutter decoration is used to reveal lines that are included within the current pull requests' diff and may therefore include comments. From 884c39db337a1d0ae33ed1796d312102fac0a3c1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 11:20:00 -0400 Subject: [PATCH 0356/4053] Mention the review summary list --- docs/rfcs/XXX-pull-request-review.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index f9afde3086..495d91a407 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -45,7 +45,9 @@ Pull request tiles other than the current pull request display a one-line review ### IssueishPaneItem "Changes" tab -Each `IssueishPaneItem` opened on a pull request has a "Changes" tab that shows the full PR diff, annotated with comments from all reviews. A banner at the top of the diff offers navigation to individual files within the diff and to individual review comments, allows each review to be hidden or shown with a filter control, and shows a progress bar that counts "resolved" review comments. +Each `IssueishPaneItem` opened on a pull request has a "Changes" tab that shows the full PR diff, annotated with comments from all reviews. + +Summary comments for each existing review appear in a list before the PR diff's body. A banner at the top of the diff offers navigation to individual files within the diff and to individual review comments, allows each review to be hidden or shown with a filter control, and shows a progress bar that counts "resolved" review comments. ![changes-tab](https://user-images.githubusercontent.com/378023/46287431-6e9bdf80-c5bd-11e8-99eb-f3f81ba64e81.png) From c37fe1fe990d13b13f714e53926fd04aae90e5f3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 11:21:23 -0400 Subject: [PATCH 0357/4053] Reaction emoji ftw :cake: :tada: Co-Authored-By: Tilde Ann Thurium --- docs/rfcs/XXX-pull-request-review.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 495d91a407..239ab8c7fe 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -54,6 +54,7 @@ Summary comments for each existing review appear in a list before the PR diff's * The navigation and filter banner remains visible as the "Changes" tab is scrolled. * The up and down arrow buttons quickly scroll to center the next or previous comment within this tab. * Clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the review comment decoration there. If the current pull request is not checked out, the "code" button is disabled, and a tooltip prompts the user to check out the pull request to edit the source. +* Reaction emoji may be added to each comment with the "emoji" button. Existing emoji reaction tallies are included beneath each comment. * Clicking "mark as resolved" marks the comment as resolved with on GitHub. If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. * The "comment" button is disabled unless the "reply" editor is expanded and has non-whitespace content. * Clicking "comment" submits the response as a new stand-alone comment on that thread. From c45f9deed2246d6849003782d6a63ae7a8137c29 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 11:24:58 -0400 Subject: [PATCH 0358/4053] Explicit "single one-off comment" handling Co-Authored-By: Tilde Ann Thurium --- docs/rfcs/XXX-pull-request-review.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 239ab8c7fe..979f1b47b1 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -59,9 +59,7 @@ Summary comments for each existing review appear in a list before the PR diff's * The "comment" button is disabled unless the "reply" editor is expanded and has non-whitespace content. * Clicking "comment" submits the response as a new stand-alone comment on that thread. -Hovering in the diff's gutter reveals a `+` icon that allows users to begin creating a new review with the same UI as described in ["In-editor decorations"](#in-editor-decorations). - -If a pending review is present, its comments are also shown and editable here. A pending review may be finalized by submitting a form that appears at the end of the existing review summary comment list. +Hovering in the diff's gutter reveals a `+` icon that allows users to begin creating a new review, or making an isolated comment, using the same UI described in ["In-editor decorations"](#in-editor-decorations). If a pending review is present, its comments are also shown and editable here. A pending review may be finalized by submitting a form that appears at the end of the existing review summary comment list. ### In-editor decorations @@ -83,6 +81,10 @@ Clicking the `+` reveals a new comment box, which may be used to submit a single ![single-review](https://user-images.githubusercontent.com/378023/40351475-78a527c2-5de7-11e8-8006-72d859514ecc.png) +* If a draft review is already in progress, the "Add single comment" button is disabled and the "Start a review" button reads "Add review comment". +* Clicking "Add single comment" submits a non-review diff comment and does not create a draft review. +* Clicking "Start a review" creates a draft review and attaches the authored comment to it. + ## Drawbacks This adds a substantial amount of complexity to the UI, which is only justified for users that use GitHub pull request reviews. From 792d9d0875424a19d3b24aa0c01434123829536e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 11:35:21 -0400 Subject: [PATCH 0359/4053] Add answers to some of our questions :smile: --- docs/rfcs/XXX-pull-request-review.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 979f1b47b1..4b1f7e348d 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -105,14 +105,24 @@ Can we access "draft" reviews from the GitHub API, to unify them between Atom an How do we represent the resolution of a comment thread? Where can we reveal this progress through each review, and of all required reviews? +* _We'll show a progress bar on a sticky header at the top of the "Changes" tab within each `IssueishPaneItem`._ + Are there any design choices we can make to lessen the emotional weight of a "requests changes" review? Peer review has the most value when it discovers issues for the pull request author to address, but accepting criticism is a vulnerable moment. +* _Chosing phrasing and iconography carefully for "recommend changes"._ + Similarly, are there any ways we can encourage empathy within the review authoring process? Can we encourage reviewers to make positive comments or demonstrate humility and open-mindedness? +* _Emoji reactions on comments :cake: :tada:_ +* _Enable integration with Teletype for smoother jumping to a synchronous review_ + ### Questions I expect to resolve throughout the implementation process Review comment positioning within live TextEditors will be a tricky problem to address satisfactorily. What are the edge cases we need to handle there? +* _Review comments on deleted lines._ +* _Review comments on deleted files._ + The GraphQL API paths we need to interact with all involve multiple levels of pagination: pull requests, pull request reviews, review comments. How do we handle these within Relay? Or do we interact directly with GraphQL requests? How do we handle comment threads? From 5465f715d11b6800691455abe75183f89a1c8c38 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 11:45:29 -0400 Subject: [PATCH 0360/4053] Oh hey that works --- docs/rfcs/XXX-pull-request-review.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 4b1f7e348d..542cc20490 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -103,6 +103,8 @@ Rendering pull request comments within TextEditors can be intrusive: if there ar Can we access "draft" reviews from the GitHub API, to unify them between Atom and GitHub? +* _Yes, the `reviews` object includes it in a `PENDING` state._ + How do we represent the resolution of a comment thread? Where can we reveal this progress through each review, and of all required reviews? * _We'll show a progress bar on a sticky header at the top of the "Changes" tab within each `IssueishPaneItem`._ From ea33834c652dffa73358f2d85901d3c8668642e2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 12:01:17 -0400 Subject: [PATCH 0361/4053] Dependency graph why not --- docs/rfcs/XXX-pull-request-review.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 542cc20490..f267581e97 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -139,7 +139,4 @@ How can we notify users when new information, including reviews, is available, p ## Implementation phases - +![dependency-graph](https://user-images.githubusercontent.com/17565/46361100-d47a7c80-c63a-11e8-83de-4a548be9cb9c.png) From d4b153bbee52254e6e01582a25e95e3abf1942a8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 14:33:37 -0400 Subject: [PATCH 0362/4053] Update iconography description --- docs/rfcs/XXX-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index f267581e97..f403323571 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -27,7 +27,7 @@ Reviews on the current pull request are rendered as a list on the current pull r * The review summary bubble is elided after the first sentence or N characters if necessary. * Clicking the review summary bubble opens an `IssueishPaneItem` in the workspace center, open to the reviews tab. * Clicking a line comment opens or activates an editor on the referenced file and scrolls to center the comment's line, translated according to local changes if appropriate. -* Line comments within the review are rendered: _with a dot_ before the file has been opened and the corresponding decoration is visible; _with no icon_ after the file and decoration have been seen; and _with a checkmark_ after the comment has been marked "resolved" with the control on its decoration. +* Line comments within the review are rendered: _with a vertical blue bar_ before the file has been opened and the corresponding decoration is visible; _with a vertical grey bar_ after the file and decoration have been seen; and _with a vertical green bar_ after the comment has been marked "resolved" with the control on its decoration. If a new review has been started locally, it appears at the top of the "Reviews" section within this tile: From 6bccb4b48a6420353440b13e60b95fdd3935656d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 14:37:49 -0400 Subject: [PATCH 0363/4053] Suggestion for reducing confusion when a different PR is open Co-Authored-By: Katrina Uychaco --- docs/rfcs/XXX-pull-request-review.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index f403323571..b185916ba7 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -28,6 +28,7 @@ Reviews on the current pull request are rendered as a list on the current pull r * Clicking the review summary bubble opens an `IssueishPaneItem` in the workspace center, open to the reviews tab. * Clicking a line comment opens or activates an editor on the referenced file and scrolls to center the comment's line, translated according to local changes if appropriate. * Line comments within the review are rendered: _with a vertical blue bar_ before the file has been opened and the corresponding decoration is visible; _with a vertical grey bar_ after the file and decoration have been seen; and _with a vertical green bar_ after the comment has been marked "resolved" with the control on its decoration. +* The review summary bubble and line comment lists are greyed out if a different `IssueishPaneItem` is activated. If a new review has been started locally, it appears at the top of the "Reviews" section within this tile: From 759884aab3eb5993c035618f24f4498516391bf5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 14:41:11 -0400 Subject: [PATCH 0364/4053] Remove non-current pull request tile changes --- docs/rfcs/XXX-pull-request-review.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index b185916ba7..48ab3e97ed 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -38,12 +38,6 @@ If a new review has been started locally, it appears at the top of the "Reviews" * Choosing "Cancel" dismisses the review and any comments made. If there are local review comments that will be lost, a confirmation prompt is shown first. * Choosing "Submit review" submits the drafted review to GitHub. -### Non-current pull request tiles - -Pull request tiles other than the current pull request display a one-line review summary, showing the number of accepting, comment, and change-request reviews made on each. Clicking the review summary opens the `IssueishPaneItem` for that pull request and opens the "Changes" tab. - -![non-current pull request tile](https://user-images.githubusercontent.com/17565/46228625-a2aea080-c330-11e8-945b-72be7824623f.png) - ### IssueishPaneItem "Changes" tab Each `IssueishPaneItem` opened on a pull request has a "Changes" tab that shows the full PR diff, annotated with comments from all reviews. From af4c794b24d42ac0df970e40f1702fa60136802b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 15:15:17 -0400 Subject: [PATCH 0365/4053] IssueishDetailItem not IssueishPaneItem --- docs/rfcs/XXX-pull-request-review.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 48ab3e97ed..8f02f8c945 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -25,10 +25,10 @@ Reviews on the current pull request are rendered as a list on the current pull r ![review-list](https://user-images.githubusercontent.com/378023/46273505-b9533280-c590-11e8-840e-a8eac8023cad.png) * The review summary bubble is elided after the first sentence or N characters if necessary. -* Clicking the review summary bubble opens an `IssueishPaneItem` in the workspace center, open to the reviews tab. +* Clicking the review summary bubble opens an `IssueishDetailItem` in the workspace center. * Clicking a line comment opens or activates an editor on the referenced file and scrolls to center the comment's line, translated according to local changes if appropriate. * Line comments within the review are rendered: _with a vertical blue bar_ before the file has been opened and the corresponding decoration is visible; _with a vertical grey bar_ after the file and decoration have been seen; and _with a vertical green bar_ after the comment has been marked "resolved" with the control on its decoration. -* The review summary bubble and line comment lists are greyed out if a different `IssueishPaneItem` is activated. +* The review summary bubble and line comment lists are greyed out if a different `IssueishDetailItem` is activated. If a new review has been started locally, it appears at the top of the "Reviews" section within this tile: @@ -66,7 +66,7 @@ When opening a TextEditor on a file that has been annotated with review comments * The comment's position is calculated from the position acquired by the GitHub API response, modified based on the git diff of that file (following renames) between the owning review's attached commit and the current state of the working copy (including any local modifications). Once created, the associated marker will also track unsaved modifications to the file in real time. * The up and down arrow buttons navigate to the next and previous review comments within this review within their respective TextEditors. -* The "diff" button navigates to the "Changes" tab of the corresponding pull request's `IssueishPaneItem`, expands the owning review, and scrolls to center the same comment within that view. +* The "diff" button navigates to the corresponding pull request's `IssueishDetailItem` and scrolls to center the same comment within that view. Hovering along the gutter within a pull request diff region reveals a `+` icon, which may be clicked to begin a new review: @@ -102,7 +102,7 @@ Can we access "draft" reviews from the GitHub API, to unify them between Atom an How do we represent the resolution of a comment thread? Where can we reveal this progress through each review, and of all required reviews? -* _We'll show a progress bar on a sticky header at the top of the "Changes" tab within each `IssueishPaneItem`._ +* _We'll show a progress bar on a sticky header at the top of the `IssueishDetailItem`._ Are there any design choices we can make to lessen the emotional weight of a "requests changes" review? Peer review has the most value when it discovers issues for the pull request author to address, but accepting criticism is a vulnerable moment. @@ -128,7 +128,7 @@ How do we handle comment threads? What other pull request information can we add to the GitHub pane item? -Are there other tabs that we need within the `IssueishPaneItem`? +Are there other tabs that we need within the `IssueishDetailItem`? How can we notify users when new information, including reviews, is available, preferably without being intrusive or disruptive? From 6313744bc005e9c799de72eeccf253ec0c2f244e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 15:22:02 -0400 Subject: [PATCH 0366/4053] Include @kuychaco's IssueishDetailItem refocus idea :lightbulb: I also rolled in @sguthal's suggestion to allow you to create a new review here while I was at it. Co-Authored-By: Katrina Uychaco Co-Authored-By: Sarah Guthals --- docs/rfcs/XXX-pull-request-review.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 8f02f8c945..7b02df0b89 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -38,15 +38,27 @@ If a new review has been started locally, it appears at the top of the "Reviews" * Choosing "Cancel" dismisses the review and any comments made. If there are local review comments that will be lost, a confirmation prompt is shown first. * Choosing "Submit review" submits the drafted review to GitHub. -### IssueishPaneItem "Changes" tab +### IssueishDetailItem -Each `IssueishPaneItem` opened on a pull request has a "Changes" tab that shows the full PR diff, annotated with comments from all reviews. +Each `IssueishDetailItem` opened on a pull request displays the full, multi-file diff associated with the pull request. -Summary comments for each existing review appear in a list before the PR diff's body. A banner at the top of the diff offers navigation to individual files within the diff and to individual review comments, allows each review to be hidden or shown with a filter control, and shows a progress bar that counts "resolved" review comments. +A banner at the top of the pane offers navigation to individual files within the diff and to individual review comments, allows each review to be hidden or shown with a filter control, and shows a progress bar that counts "resolved" review comments. The banner remains visible as you scroll the pane. + +At the top of the pane is the existing summary box: + +issueish-detail-item pane + +* Clicking on the build status summary icon (green checkmark, donut chart, or X) expands an ephemeral panel beneath the summary box showing build review status. Clicking the icon again or clicking on "dismiss" dismisses it. +* Clicking on the commit count opens the log view to those commits. + +Summary comments for each existing review appear in a list below that. If a pending review is being drafted, it appears at the end of the list; otherwise, a control is present to create one. A pending review may be finalized by submitting a form that appears adjacent to it. + +After the summary comments, the diff is shown, with review comments in place: ![changes-tab](https://user-images.githubusercontent.com/378023/46287431-6e9bdf80-c5bd-11e8-99eb-f3f81ba64e81.png) -* The navigation and filter banner remains visible as the "Changes" tab is scrolled. +On each review comment decoration: + * The up and down arrow buttons quickly scroll to center the next or previous comment within this tab. * Clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the review comment decoration there. If the current pull request is not checked out, the "code" button is disabled, and a tooltip prompts the user to check out the pull request to edit the source. * Reaction emoji may be added to each comment with the "emoji" button. Existing emoji reaction tallies are included beneath each comment. @@ -54,7 +66,7 @@ Summary comments for each existing review appear in a list before the PR diff's * The "comment" button is disabled unless the "reply" editor is expanded and has non-whitespace content. * Clicking "comment" submits the response as a new stand-alone comment on that thread. -Hovering in the diff's gutter reveals a `+` icon that allows users to begin creating a new review, or making an isolated comment, using the same UI described in ["In-editor decorations"](#in-editor-decorations). If a pending review is present, its comments are also shown and editable here. A pending review may be finalized by submitting a form that appears at the end of the existing review summary comment list. +Hovering in the diff's gutter reveals a `+` icon that allows users to begin creating a new pending review, or making an isolated comment, using the same UI described in ["In-editor decorations"](#in-editor-decorations). If a pending review is present, its comments are also shown and editable here. ### In-editor decorations From 2ab74b59873c3b5bccac7ef679795eb483b335cf Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 2 Oct 2018 15:34:09 -0400 Subject: [PATCH 0367/4053] Create a pending review if one is not present Co-Authored-By: Sarah Guthals --- docs/rfcs/XXX-pull-request-review.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 7b02df0b89..95003ecec4 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -30,7 +30,7 @@ Reviews on the current pull request are rendered as a list on the current pull r * Line comments within the review are rendered: _with a vertical blue bar_ before the file has been opened and the corresponding decoration is visible; _with a vertical grey bar_ after the file and decoration have been seen; and _with a vertical green bar_ after the comment has been marked "resolved" with the control on its decoration. * The review summary bubble and line comment lists are greyed out if a different `IssueishDetailItem` is activated. -If a new review has been started locally, it appears at the top of the "Reviews" section within this tile: +If a pending review is present, it appears at the top of the "Reviews" section within this tile: ![pending-review](https://user-images.githubusercontent.com/378023/46275946-9bd69680-c599-11e8-9889-66c35458286a.png) @@ -38,6 +38,8 @@ If a new review has been started locally, it appears at the top of the "Reviews" * Choosing "Cancel" dismisses the review and any comments made. If there are local review comments that will be lost, a confirmation prompt is shown first. * Choosing "Submit review" submits the drafted review to GitHub. +If there is no pending review, the tile instead displays a control to create one. + ### IssueishDetailItem Each `IssueishDetailItem` opened on a pull request displays the full, multi-file diff associated with the pull request. From 57cf508816af11d3c564d0df261e26b404b872d9 Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Wed, 3 Oct 2018 12:19:08 +0530 Subject: [PATCH 0368/4053] Handle relative pathname in gitconfig properly --- lib/git-shell-out-strategy.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 2553aebc76..660f2f9944 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -416,10 +416,23 @@ export default class GitShellOutStrategy { } async getCommitMessageFromTemplate() { - const templatePath = await this.getConfig('commit.template'); - if (!templatePath || ! await fileExists(templatePath)) return; + let templatePath = await this.getConfig('commit.template'); + if (!templatePath) return; + + // Get absolute path from git path + // https://git-scm.com/docs/git-config#git-config-pathname + const homeDir = os.homedir(); + const regex = new RegExp('^~([^\/]*)\/'); + templatePath = templatePath.trim().replace(regex, (_, user) => { + return `${user ? path.join(path.dirname(homeDir), user) : homeDir}/` + }) + if (!path.isAbsolute(templatePath)) { + templatePath = path.join(this.workingDir, templatePath); + } + + if(!await fileExists(templatePath)) return; const message = await fs.readFile(templatePath, { encoding: 'utf8'}) - return message + return message.trim(); } unstageFiles(paths, commit = 'HEAD') { From b7b2fc132384f1bdd2d4d0fda438771649c5ff6a Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Wed, 3 Oct 2018 13:01:52 +0530 Subject: [PATCH 0369/4053] Add more tests --- test/git-strategies.test.js | 26 ++++++++++++++++++++++++++ test/helpers.js | 3 +-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 686138469d..ff7e3485ae 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1,6 +1,7 @@ import fs from 'fs-extra'; import path from 'path'; import http from 'http'; +import os from 'os'; import mkdirp from 'mkdirp'; import dedent from 'dedent-js'; @@ -85,6 +86,31 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); }); + describe('getCommitMessageFromTemplate', function() { + it('supports repository root path', async function() { + const workingDirPath = await cloneRepository('commit-template'); + const git = createTestStrategy(workingDirPath); + const absTemplatePath = path.join(workingDirPath, 'gitmessage.txt'); + const commitTemplate = fs.readFileSync(absTemplatePath, 'utf8').trim(); + const message = await git.getCommitMessageFromTemplate(); + assert.equal(message, commitTemplate); + }); + + it('supports relative path', async function() { + const workingDirPath = await cloneRepository('commit-template'); + const git = createTestStrategy(workingDirPath); + const homeDir = os.homedir(); + const absTemplatePath = path.join(homeDir, '.gitMessageSample.txt'); + await fs.writeFile(absTemplatePath, 'some commit message', {encoding: 'utf8'}); + await git.exec(['config', '--local', 'commit.template', '~/.gitMessageSample.txt']); + + const message = await git.getCommitMessageFromTemplate(); + assert.equal(message, 'some commit message'); + fs.removeSync(absTemplatePath); + }); + }) + + if (process.platform === 'win32') { describe('getStatusBundle()', function() { it('normalizes the path separator on Windows', async function() { diff --git a/test/helpers.js b/test/helpers.js index 60da235b7c..412e94d658 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -48,8 +48,7 @@ export async function cloneRepository(repoName = 'three-files') { // commit.template file(gitmessage.txt) stored in temp location let templatePath = ''; try { - const templateFile = await git.exec(['config', '--file', repoPath + '/config', 'commit.template']); - templatePath = `${cachedPath}/${templateFile}`; + templatePath = await git.exec(['config', '--file', repoPath + '/config', 'commit.template']); } catch(err) {} await git.clone(repoPath, {noLocal: true}); From 9a3df9dd4ca62a6a3da06ebb158326e195b1c2b9 Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Wed, 3 Oct 2018 13:19:23 +0530 Subject: [PATCH 0370/4053] Add one more test --- test/controllers/commit-controller.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 892ed9c192..cd696f087f 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -81,6 +81,19 @@ describe('CommitController', function() { assert.equal(wrapper.instance().getCommitMessage(), templateCommitMessage); }); + + describe('when commit.template config is set', function() { + it('pre populates the commit with the template message', async function() { + const workdirPath = await cloneRepository('commit-template'); + const repository = await buildRepository(workdirPath); + const templateCommitMessage = await repository.git.getCommitMessageFromTemplate(); + app = React.cloneElement(app, {repository: repository}); + const wrapper = shallow(app, {disableLifecycleMethods: true}); + assert.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); + }); + }); + + describe('the passed commit message', function() { let repository; From a7b7bae3004bf184ea52a9009264fd4345b2f61f Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 3 Oct 2018 00:53:53 -0700 Subject: [PATCH 0371/4053] Update RFC to reflect editor-first design Co-Authored-By: simurai --- docs/rfcs/XXX-pull-request-review.md | 157 ++++++++++++++++++++------- 1 file changed, 116 insertions(+), 41 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 95003ecec4..7fb9271b78 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -18,69 +18,126 @@ Peer review is also a critical part of the path to acceptance for pull requests ## Explanation -### Current pull request tile +### Review information in Pull Request list -Reviews on the current pull request are rendered as a list on the current pull request tile. +Review progress is indicated for open pull requests listed in the GitHub panel. The pull request corresponding to the checked out branch gets special treatment in it's own section at the top of the list. -![review-list](https://user-images.githubusercontent.com/378023/46273505-b9533280-c590-11e8-840e-a8eac8023cad.png) +slack_-_github +Note: Change "Current pull request" -* The review summary bubble is elided after the first sentence or N characters if necessary. -* Clicking the review summary bubble opens an `IssueishDetailItem` in the workspace center. -* Clicking a line comment opens or activates an editor on the referenced file and scrolls to center the comment's line, translated according to local changes if appropriate. -* Line comments within the review are rendered: _with a vertical blue bar_ before the file has been opened and the corresponding decoration is visible; _with a vertical grey bar_ after the file and decoration have been seen; and _with a vertical green bar_ after the comment has been marked "resolved" with the control on its decoration. -* The review summary bubble and line comment lists are greyed out if a different `IssueishDetailItem` is activated. +Clicking a pull request in the list opens a `PullRequestDetailItem` in the workspace center. -If a pending review is present, it appears at the top of the "Reviews" section within this tile: +For PRs that are not listed in the panel, users can use the `github:open-issue-or-pull-request` command: -![pending-review](https://user-images.githubusercontent.com/378023/46275946-9bd69680-c599-11e8-9889-66c35458286a.png) +xxx-pull-request-review_md_ ___github_github + + +### PullRequestDetailItem + +Each `PullRequestDetailItem` opened on a pull request displays the full, multi-file diff associated with the pull request. Review comments are shown within the diff. See ["Comment decorations"](#comment-decorations) for description of review comments. + +![screen shot 2018-10-03 at 1 50 18 pm](https://user-images.githubusercontent.com/7910250/46391711-1df6b600-c693-11e8-87f3-ad4cdbe8ebd8.png) + +TODO: update mock to have "Start review button" and "Add single comment" + +Diffs are editable ONLY if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. Otherwise diffs are not editable. + +A panel at the bottom of the pane offers various options for sorting and filtering the diff. It also has a "Review Changes" button. + +#### Sort Options + +slack_-_github + +The default view is sorted by files. This is akin to the "Files changed" tab on dotcom. It displays the diff for all changed files in the PR. + +Sorting by reviews is akin to the review summaries that appear on the "Conversation" tab on dotcom. The comments are displayed grouped by review along with some context lines. + +![screen shot 2018-10-03 at 1 50 08 pm](https://user-images.githubusercontent.com/7910250/46394598-6ebfdc00-c69e-11e8-84eb-39ccbcccf736.png) + +TODO: include show multiple reviews stacked + +Sorting by commits is akin to the "Commits" tab on dotcom. A list of commits is displayed in chronological order, oldest commit on top. Clicking a commit expands the diff contents below. If there is a commit message body this is displayed as well. + +TODO: include commit sort mockup + +A banner at the bottom of the pane offers navigation to individual files within the diff and to individual review comments, allows each review to be hidden or shown with a filter control, and shows a progress bar that counts "resolved" review comments. The banner remains visible as you scroll the pane. + +#### Filter Options + +slack_-_github + +The default is to show all files, all authors, and unresolved comments. + +Filtering based on file type limits the diff view to displaying only that file type. + +TODO: Consider adding a "Find" input field that allows us to filter based on search term (which could be a file name, an author, a variable name, etc). Probably out of scope for this RFC. + +Clicking an author's avatar displays only their review information. + +Clicking "unresolved" shows only resolved comments, helping users stay focused on comments that need to be addressed. + +Clicking "resolved" shows only resolved comments. This allows users to quickly see what has already been addressed. + +Checking "all comments" shows both resolved and unresolved comments. + +Clicking "none" hides all comments, in the event that users want to see diff information only. + +#### Submitting a Review + +slack_-_github + +Clicking the "Review Changes" button reveals a UI much like dotcom's + +xxx-pull-request-review_md_ ___github_github + +TODO: update Review Changes mockup * The review summary is a TextEditor that may be used to compose a summary comment. * Choosing "Cancel" dismisses the review and any comments made. If there are local review comments that will be lost, a confirmation prompt is shown first. * Choosing "Submit review" submits the drafted review to GitHub. -If there is no pending review, the tile instead displays a control to create one. +#### Summary Box -### IssueishDetailItem +At the top of the pane is the existing summary box: -Each `IssueishDetailItem` opened on a pull request displays the full, multi-file diff associated with the pull request. +issueish-detail-item pane +TODO: add conversation/timeline icon and progress bar -A banner at the top of the pane offers navigation to individual files within the diff and to individual review comments, allows each review to be hidden or shown with a filter control, and shows a progress bar that counts "resolved" review comments. The banner remains visible as you scroll the pane. +Clicking on the "22 commits" opens the commit view and changes the bottom panel to indicate sort by commits. -At the top of the pane is the existing summary box: +Clicking on the "1 changed files" opens the files view and changes the bottom panel to indicate sort by files and "all files" checked. -issueish-detail-item pane +Clicking on the build status summary icon (green checkmark, donut chart, or X) expands an ephemeral panel beneath the summary box showing build review status. Clicking the icon again or clicking on "dismiss" dismisses it. -* Clicking on the build status summary icon (green checkmark, donut chart, or X) expands an ephemeral panel beneath the summary box showing build review status. Clicking the icon again or clicking on "dismiss" dismisses it. -* Clicking on the commit count opens the log view to those commits. +slack_-_github -Summary comments for each existing review appear in a list below that. If a pending review is being drafted, it appears at the end of the list; otherwise, a control is present to create one. A pending review may be finalized by submitting a form that appears adjacent to it. +Clicking on the conversation/timeline icon expands an ephemeral panel beneath the summary box showing a very timeline view. The PR description and PR comments are displayed here. Other note-worthy timeline events are displayed in a very minimal fashion. -After the summary comments, the diff is shown, with review comments in place: +TODO: add conversation/timeline popover mockup -![changes-tab](https://user-images.githubusercontent.com/378023/46287431-6e9bdf80-c5bd-11e8-99eb-f3f81ba64e81.png) +Clicking the "expand" icon on the top right opens this information in a new pane to the right for easy side-by-side viewing with the diff (much like our current markdown preview opens in a separate pane). -On each review comment decoration: +TODO: add conversation/timeline pane item -* The up and down arrow buttons quickly scroll to center the next or previous comment within this tab. -* Clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the review comment decoration there. If the current pull request is not checked out, the "code" button is disabled, and a tooltip prompts the user to check out the pull request to edit the source. -* Reaction emoji may be added to each comment with the "emoji" button. Existing emoji reaction tallies are included beneath each comment. -* Clicking "mark as resolved" marks the comment as resolved with on GitHub. If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. -* The "comment" button is disabled unless the "reply" editor is expanded and has non-whitespace content. -* Clicking "comment" submits the response as a new stand-alone comment on that thread. +Clicking on the a commit takes you to the commit view and expands the selected commit, centering it in view. + +TODO: add commit view mockup -Hovering in the diff's gutter reveals a `+` icon that allows users to begin creating a new pending review, or making an isolated comment, using the same UI described in ["In-editor decorations"](#in-editor-decorations). If a pending review is present, its comments are also shown and editable here. +Clicking on a review reference takes you to the review view and expands the selected review, centering it in view. -### In-editor decorations +TODO: add review mockup -When opening a TextEditor on a file that has been annotated with review comments on the current pull request, a block decoration is used to show the comment content at the corresponding position within the file content. Also, a gutter decoration is used to reveal lines that are included within the current pull requests' diff and may therefore include comments. -![in-editor](https://user-images.githubusercontent.com/378023/44790482-69bcc800-abda-11e8-8a0f-922c0942b8c6.png) +### Comment decorations -> TODO: add gutter decoration? +Within the multi-file diff view, a block decoration is used to show the comment content at the corresponding position within the file content. * The comment's position is calculated from the position acquired by the GitHub API response, modified based on the git diff of that file (following renames) between the owning review's attached commit and the current state of the working copy (including any local modifications). Once created, the associated marker will also track unsaved modifications to the file in real time. -* The up and down arrow buttons navigate to the next and previous review comments within this review within their respective TextEditors. -* The "diff" button navigates to the corresponding pull request's `IssueishDetailItem` and scrolls to center the same comment within that view. +* The up and down arrow buttons navigate to the next and previous review comments. +* Clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the line corresponding to the comment. + * In the future we will implement inline review comments as decorations in the code. + * If the current pull request is not checked out, the "code" button is disabled, and a tooltip prompts the user to check out the pull request to edit the source. +* Reaction emoji may be added to each comment with the "emoji" button. Existing emoji reaction tallies are included beneath each comment. Hovering along the gutter within a pull request diff region reveals a `+` icon, which may be clicked to begin a new review: @@ -91,25 +148,38 @@ Clicking the `+` reveals a new comment box, which may be used to submit a single ![single-review](https://user-images.githubusercontent.com/378023/40351475-78a527c2-5de7-11e8-8006-72d859514ecc.png) * If a draft review is already in progress, the "Add single comment" button is disabled and the "Start a review" button reads "Add review comment". -* Clicking "Add single comment" submits a non-review diff comment and does not create a draft review. -* Clicking "Start a review" creates a draft review and attaches the authored comment to it. +* Clicking "Add single comment" submits a non-review diff comment and does not create a draft review. This button is disabled unless the "reply" editor is expanded and has non-whitespace content. +* Clicking "Start a review" creates a draft review and attaches the authored comment to it. This button is disabled unless the "reply" editor is expanded and has non-whitespace content. +* Clicking "mark as resolved" marks the comment as resolved with on GitHub. If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. ## Drawbacks This adds a substantial amount of complexity to the UI, which is only justified for users that use GitHub pull request reviews. -Showing all reviews in the current pull request tile can easily overwhelm the other pull request information included there. It also limits our ability to expand the information we provide there in the future (like associated issues, say). - Rendering pull request comments within TextEditors can be intrusive: if there are many, or if your reviewers are particularly verbose, they could easily crowd out the code that you're trying to write and obscure your context. ## Rationale and alternatives +Our original design looked and felt very dotcom-esque: + +![changes-tab](https://user-images.githubusercontent.com/378023/46287431-6e9bdf80-c5bd-11e8-99eb-f3f81ba64e81.png) + +We decided to switch to an editor-first approach and build the code review experience around an actual TextEditor item with a custom diff view. We are breaking free of the dotcom paradigm and leveraging the fact that we are in the context of the user's working directory, where we can easily update code. + +We discussed displaying review summary information in the GitHub panel in a ["Current pull request tile"](https://github.com/atom/github/blob/2ab74b59873c3b5bccac7ef679795eb483b335cf/docs/rfcs/XXX-pull-request-review.md#current-pull-request-tile). The current design encapsulates all of the PR information and functionality within a `PullRequestDetailItem`. Keeping the GitHub panel free of PR details for a specific PR rids us of the problem of having to keep it updated when the user switches active repos (which can feel jarring). This also avoids confusing the user by showing PR details for different PRs (imagine the checked out PR info in the panel and a pane item with PR info for a separate repo). We also free up space in the GitHub panel, making it less busy/overwhelming and leaving room for other information we might want to provide there in the future (like associated issues, say). + +We also discussed implementing comment decorations in regular text editors. Clicking the "code" (<>) button on a comment was originally to take you to the comment in the corresponding TextEditor file. While this is a nice feature to have, we can ship a complete code review experience without it. Let's punt on this and tackle it later on. + ## Unresolved questions ### Questions I expect to address before this is merged +When there are working directory changes, how do we clearly indicate them within the diff view? Do we need to make them visually distinct from the PR changes? Things might get confusing for the user when the diff in the editor gets out of sync with the diff on dotcom. +Example: +* Author reads comment pointing out typo in an added line. Author edits text in multi-file diff which modifies the working directory. Should this line now be orange to indicate that it has deviated from the original diff? + Can we access "draft" reviews from the GitHub API, to unify them between Atom and GitHub? * _Yes, the `reviews` object includes it in a `PENDING` state._ @@ -127,6 +197,8 @@ Similarly, are there any ways we can encourage empathy within the review authori * _Emoji reactions on comments :cake: :tada:_ * _Enable integration with Teletype for smoother jumping to a synchronous review_ +How do we clearly indicating recently added changes? That is, new changes pushed, comments, reviews, etc since the last time the users viewed the PR info. Is it enough to simply update the timeline view? Is it too easy to miss changes? + ### Questions I expect to resolve throughout the implementation process Review comment positioning within live TextEditors will be a tricky problem to address satisfactorily. What are the edge cases we need to handle there? @@ -142,10 +214,13 @@ How do we handle comment threads? What other pull request information can we add to the GitHub pane item? -Are there other tabs that we need within the `IssueishDetailItem`? - How can we notify users when new information, including reviews, is available, preferably without being intrusive or disruptive? ## Implementation phases ![dependency-graph](https://user-images.githubusercontent.com/17565/46361100-d47a7c80-c63a-11e8-83de-4a548be9cb9c.png) + +## Related features out of scope of this RFC + +* Inline review comments +* "Find" input field for filtering based on search term (which could be a file name, an author, a variable name, etc) From 3ce8ee32eba28b0dac4f9bfd3016a52516ddff89 Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Wed, 3 Oct 2018 14:02:55 +0530 Subject: [PATCH 0372/4053] Make linter happy --- lib/git-shell-out-strategy.js | 31 +++++++++++++--------- lib/models/repository-states/present.js | 4 +-- test/controllers/commit-controller.test.js | 4 +-- test/git-strategies.test.js | 2 +- test/helpers.js | 2 +- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 660f2f9944..a1bd858bb3 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -417,21 +417,28 @@ export default class GitShellOutStrategy { async getCommitMessageFromTemplate() { let templatePath = await this.getConfig('commit.template'); - if (!templatePath) return; + if (!templatePath) { + return ''; + } - // Get absolute path from git path - // https://git-scm.com/docs/git-config#git-config-pathname + /** + * Get absolute path from git path + * https://git-scm.com/docs/git-config#git-config-pathname + */ const homeDir = os.homedir(); - const regex = new RegExp('^~([^\/]*)\/'); + const regex = new RegExp('^~([^/]*)/'); templatePath = templatePath.trim().replace(regex, (_, user) => { - return `${user ? path.join(path.dirname(homeDir), user) : homeDir}/` - }) - if (!path.isAbsolute(templatePath)) { - templatePath = path.join(this.workingDir, templatePath); - } - - if(!await fileExists(templatePath)) return; - const message = await fs.readFile(templatePath, { encoding: 'utf8'}) + return `${user ? path.join(path.dirname(homeDir), user) : homeDir}/`; + }); + + if (!path.isAbsolute(templatePath)) { + templatePath = path.join(this.workingDir, templatePath); + } + + if (!await fileExists(templatePath)) { + return ''; + } + const message = await fs.readFile(templatePath, {encoding: 'utf8'}); return message.trim(); } diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index e873c39d51..5c1f7a4a92 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -61,8 +61,8 @@ export default class Present extends State { if (!message) { return; } - this.setCommitMessage(message) - this.didUpdate() + this.setCommitMessage(message); + this.didUpdate(); } async getCommitMessageFromTemplate() { diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index cd696f087f..1909580370 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -83,11 +83,11 @@ describe('CommitController', function() { describe('when commit.template config is set', function() { - it('pre populates the commit with the template message', async function() { + it('populates the commit message with the template', async function() { const workdirPath = await cloneRepository('commit-template'); const repository = await buildRepository(workdirPath); const templateCommitMessage = await repository.git.getCommitMessageFromTemplate(); - app = React.cloneElement(app, {repository: repository}); + app = React.cloneElement(app, {repository}); const wrapper = shallow(app, {disableLifecycleMethods: true}); assert.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); }); diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index ff7e3485ae..c63fe9164b 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -108,7 +108,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; assert.equal(message, 'some commit message'); fs.removeSync(absTemplatePath); }); - }) + }); if (process.platform === 'win32') { diff --git a/test/helpers.js b/test/helpers.js index 412e94d658..ba4f0b7f06 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -49,7 +49,7 @@ export async function cloneRepository(repoName = 'three-files') { let templatePath = ''; try { templatePath = await git.exec(['config', '--file', repoPath + '/config', 'commit.template']); - } catch(err) {} + } catch (err) {} await git.clone(repoPath, {noLocal: true}); await git.exec(['config', '--local', 'core.autocrlf', 'false']); From a32808167084129f460edd6defda34890dce6ce6 Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Wed, 3 Oct 2018 14:16:19 +0530 Subject: [PATCH 0373/4053] Final cleanup --- test/helpers.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/helpers.js b/test/helpers.js index ba4f0b7f06..ed2a8b3bc2 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -44,8 +44,6 @@ export async function cloneRepository(repoName = 'three-files') { const git = new GitShellOutStrategy(cachedPath); const repoPath = path.join(__dirname, 'fixtures', `repo-${repoName}`, 'dot-git'); - // templatePath is the absolute path of the - // commit.template file(gitmessage.txt) stored in temp location let templatePath = ''; try { templatePath = await git.exec(['config', '--file', repoPath + '/config', 'commit.template']); From 5042fdd3f0d3a8b73eb0bb4225e2955a2bc2289b Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Wed, 3 Oct 2018 15:33:07 +0530 Subject: [PATCH 0374/4053] Fix lint warnings --- test/controllers/commit-controller.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 1909580370..c643191fc0 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -159,18 +159,18 @@ describe('CommitController', function() { }); it('reload the commit messages from commit template', async function() { - const workdirPath = await cloneRepository('commit-template'); - const repository = await buildRepositoryWithPipeline(workdirPath, {confirm, notificationManager, workspace}); - const templateCommitMessage = await repository.git.getCommitMessageFromTemplate(); - const commit = sinon.stub().callsFake((...args) => repository.commit(...args)); - const app2 = React.cloneElement(app, {repository, commit}); + const repoPath = await cloneRepository('commit-template'); + const repo = await buildRepositoryWithPipeline(repoPath, {confirm, notificationManager, workspace}); + const templateCommitMessage = await repo.git.getCommitMessageFromTemplate(); + const commitStub = sinon.stub().callsFake((...args) => repo.commit(...args)); + const app2 = React.cloneElement(app, {repository: repo, commit: commitStub}); - await fs.writeFile(path.join(workdirPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); - await repository.git.exec(['add', '.']); + await fs.writeFile(path.join(repoPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); + await repo.git.exec(['add', '.']); const wrapper = shallow(app2, {disableLifecycleMethods: true}); await wrapper.instance().commit('some message'); - assert.strictEqual(repository.getCommitMessage(), templateCommitMessage); + assert.strictEqual(repo.getCommitMessage(), templateCommitMessage); }); it('sets the verbatim flag when committing from the mini editor', async function() { From 518910d1b0a5ac94a118a4869916fd1d2a427b4d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 10:20:57 -0400 Subject: [PATCH 0375/4053] Reintroduce the "discard-selected-lines" command handler --- lib/views/file-patch-view.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 5a70d4bd9f..79575bd5b0 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -56,7 +56,7 @@ export default class FilePatchView extends React.Component { this, 'didMouseDownOnHeader', 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', 'didConfirm', 'didToggleSelectionMode', 'selectNextHunk', 'selectPreviousHunk', 'hunkSelectDown', 'hunkSelectUp', - 'didOpenFile', 'didAddSelection', 'didChangeSelectionRange', 'didDestroySelection', + 'didDiscardSelection', 'didOpenFile', 'didAddSelection', 'didChangeSelectionRange', 'didDestroySelection', 'oldLineNumberLabel', 'newLineNumberLabel', ); @@ -174,6 +174,7 @@ export default class FilePatchView extends React.Component { return ( + @@ -671,6 +672,10 @@ export default class FilePatchView extends React.Component { return this.props.toggleRows(this.props.selectedRows, this.props.selectionMode); } + didDiscardSelection() { + return this.props.discardRows(this.props.selectedRows, this.props.selectionMode); + } + didToggleSelectionMode() { const selectedHunks = this.getSelectedHunks(); this.withSelectionMode({ From 8e3fd1b83bfc88b08ad01590956f7875d5a79ddb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 10:36:52 -0400 Subject: [PATCH 0376/4053] Default to hunk selection mode --- lib/controllers/file-patch-controller.js | 2 +- lib/views/file-patch-view.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index ec1aa9a5d2..cf85595b8f 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -34,7 +34,7 @@ export default class FilePatchController extends React.Component { this.state = { lastFilePatch: this.props.filePatch, - selectionMode: 'line', + selectionMode: 'hunk', selectedRows: new Set(), }; diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 79575bd5b0..dbda09fc64 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -80,7 +80,11 @@ export default class FilePatchView extends React.Component { componentDidMount() { window.addEventListener('mouseup', this.didMouseUp); this.refEditor.map(editor => { - editor.setSelectedBufferRange(this.props.filePatch.getFirstChangeRange()); + const [firstHunk] = this.props.filePatch.getHunks(); + if (firstHunk) { + this.nextSelectionMode = 'hunk'; + editor.setSelectedBufferRange(firstHunk.getRange()); + } return null; }); } From ad51de4c8cf010d7fdb057346059d9ed2c510dc5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 10:53:20 -0400 Subject: [PATCH 0377/4053] "marker" prop was renamed to "decorable" --- test/controllers/conflict-controller.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/controllers/conflict-controller.test.js b/test/controllers/conflict-controller.test.js index 135ea47b4d..054b61afbf 100644 --- a/test/controllers/conflict-controller.test.js +++ b/test/controllers/conflict-controller.test.js @@ -49,11 +49,11 @@ describe('ConflictController', function() { }); const textFromDecoration = function(d) { - return editor.getTextInBufferRange(d.props().marker.getBufferRange()); + return editor.getTextInBufferRange(d.prop('decorable').getBufferRange()); }; const pointFromDecoration = function(d) { - const range = d.props().marker.getBufferRange(); + const range = d.prop('decorable').getBufferRange(); assert.isTrue(range.isEmpty()); return range.start.toArray(); }; From 4c344b5fd7d6f522cb82739b49e35e79050ad98d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 10:56:01 -0400 Subject: [PATCH 0378/4053] Selection mode defaults to "hunk" now --- test/controllers/file-patch-controller.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index 7d1095708d..1672b7b15a 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -154,17 +154,17 @@ describe('FilePatchController', function() { it('captures the selected row set', function() { const wrapper = shallow(buildApp()); assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); + assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'hunk'); + wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'line'); assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); + assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); }); it('does not re-render if the row set and selection mode are unchanged', function() { const wrapper = shallow(buildApp()); assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); + assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); sinon.spy(wrapper.instance(), 'render'); From bfddaea4255bc0edbea8658039d2961ac2d6ae03 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 10:59:01 -0400 Subject: [PATCH 0379/4053] Declare "keymaps" --- test/github-package.test.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/github-package.test.js b/test/github-package.test.js index 2bbdaaa70c..8cc03f8fe1 100644 --- a/test/github-package.test.js +++ b/test/github-package.test.js @@ -8,7 +8,8 @@ import {fileExists, getTempDir} from '../lib/helpers'; import GithubPackage from '../lib/github-package'; describe('GithubPackage', function() { - let atomEnv, workspace, project, commandRegistry, notificationManager, grammars, config, confirm, tooltips, styles; + let atomEnv, workspace, project, commandRegistry, notificationManager, grammars, config, keymaps; + let confirm, tooltips, styles; let getLoadSettings, configDirPath, deserializers; let githubPackage, contextPool; @@ -75,8 +76,8 @@ describe('GithubPackage', function() { const getLoadSettings1 = () => ({initialPaths}); githubPackage1 = new GithubPackage({ - workspace, project, commandRegistry, notificationManager, tooltips, styles, grammars, config, deserializers, - confirm, getLoadSettings: getLoadSettings1, + workspace, project, commandRegistry, notificationManager, tooltips, styles, grammars, keymaps, + config, deserializers, confirm, getLoadSettings: getLoadSettings1, configDirPath, }); } From 70ab07fde3891fd8671c885e20274679bc32727b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 11:01:57 -0400 Subject: [PATCH 0380/4053] The first hunk is selected on mount --- test/views/file-patch-view.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index cf977af042..97b4765c02 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -544,7 +544,7 @@ describe('FilePatchView', function() { it('ignores right clicks', function() { instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {button: 1}}); assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ - [[1, 0], [1, 4]], + [[0, 0], [4, 4]], ]); }); @@ -552,7 +552,7 @@ describe('FilePatchView', function() { it('ignores ctrl-clicks on non-Windows platforms', function() { instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {button: 0, ctrlKey: true}}); assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ - [[1, 0], [1, 4]], + [[0, 0], [4, 4]], ]); }); } @@ -705,12 +705,12 @@ describe('FilePatchView', function() { it('does nothing on a click without a buffer row', function() { instance.didMouseDownOnLineNumber({bufferRow: NaN, domEvent: {button: 0}}); assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ - [[1, 0], [1, 4]], + [[0, 0], [4, 4]], ]); instance.didMouseDownOnLineNumber({bufferRow: undefined, domEvent: {button: 0}}); assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ - [[1, 0], [1, 4]], + [[0, 0], [4, 4]], ]); }); }); @@ -787,7 +787,7 @@ describe('FilePatchView', function() { editor.setSelectedBufferRange([[5, 1], [6, 2]]); assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [6]); - assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'line'); + assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'hunk'); }); describe('when viewing an empty patch', function() { From 5a3d1914bfbbad3e6b6684a55250f3a358c70546 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 11:20:20 -0400 Subject: [PATCH 0381/4053] Isolate the failing test --- test/integration/open.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/open.test.js b/test/integration/open.test.js index 4657b30176..89d3cd0446 100644 --- a/test/integration/open.test.js +++ b/test/integration/open.test.js @@ -24,7 +24,7 @@ describe('opening and closing tabs', function() { await teardown(context); }); - it('opens but does not focus the git tab on github:toggle-git-tab', async function() { + it.only('opens but does not focus the git tab on github:toggle-git-tab', async function() { const editor = await atomEnv.workspace.open(__filename); assert.isFalse(wrapper.find('.github-Git').exists()); From a9ba3036b5d89baf9618b582d1642f75f630975f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 11:24:13 -0400 Subject: [PATCH 0382/4053] Does it only fail under stress? --- test/integration/open.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/open.test.js b/test/integration/open.test.js index 89d3cd0446..1b034085ce 100644 --- a/test/integration/open.test.js +++ b/test/integration/open.test.js @@ -24,7 +24,7 @@ describe('opening and closing tabs', function() { await teardown(context); }); - it.only('opens but does not focus the git tab on github:toggle-git-tab', async function() { + it.stress(20, 'opens but does not focus the git tab on github:toggle-git-tab', async function() { const editor = await atomEnv.workspace.open(__filename); assert.isFalse(wrapper.find('.github-Git').exists()); From 72360e11970af6e91bb44cf4808ec0342e212ab2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 11:25:32 -0400 Subject: [PATCH 0383/4053] Pass keymaps to GitHubPackages created for integration tests --- test/integration/helpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/helpers.js b/test/integration/helpers.js index 8f071685b4..7add61d254 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -85,6 +85,7 @@ export async function setup(currentTest, options = {}) { styles: atomEnv.styles, grammars: atomEnv.grammars, config: atomEnv.config, + keymaps: atomEnv.keymaps, deserializers: atomEnv.deserializers, loginModel, confirm: atomEnv.confirm.bind(atomEnv), From c9fabfe5b986d200032ef68a9bcf9b94c5b178cd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 11:27:39 -0400 Subject: [PATCH 0384/4053] ??? --- test/integration/open.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/open.test.js b/test/integration/open.test.js index 1b034085ce..4657b30176 100644 --- a/test/integration/open.test.js +++ b/test/integration/open.test.js @@ -24,7 +24,7 @@ describe('opening and closing tabs', function() { await teardown(context); }); - it.stress(20, 'opens but does not focus the git tab on github:toggle-git-tab', async function() { + it('opens but does not focus the git tab on github:toggle-git-tab', async function() { const editor = await atomEnv.workspace.open(__filename); assert.isFalse(wrapper.find('.github-Git').exists()); From f681033f1e8b237809be34e125c3322dfe0c6877 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 11:41:57 -0400 Subject: [PATCH 0385/4053] console.logs for the console.log throne --- test/integration/open.test.js | 8 ++++++++ test/runner.js | 1 + 2 files changed, 9 insertions(+) diff --git a/test/integration/open.test.js b/test/integration/open.test.js index 4657b30176..0210742748 100644 --- a/test/integration/open.test.js +++ b/test/integration/open.test.js @@ -25,16 +25,24 @@ describe('opening and closing tabs', function() { }); it('opens but does not focus the git tab on github:toggle-git-tab', async function() { + console.log('0'); const editor = await atomEnv.workspace.open(__filename); + console.log('1'); assert.isFalse(wrapper.find('.github-Git').exists()); + console.log('2'); await commands.dispatch(workspaceElement, 'github:toggle-git-tab'); + console.log('3'); wrapper.update(); + console.log('4'); assert.isTrue(wrapper.find('.github-Git').exists()); + console.log('5'); assert.isTrue(atomEnv.workspace.getRightDock().isVisible()); + console.log('6'); await assert.async.strictEqual(atomEnv.workspace.getActivePaneItem(), editor); + console.log('7'); }); it('reveals an open but hidden git tab on github:toggle-git-tab', async function() { diff --git a/test/runner.js b/test/runner.js index ba110bc3b8..e4db4d9e02 100644 --- a/test/runner.js +++ b/test/runner.js @@ -106,6 +106,7 @@ module.exports = createRunner({ mocha.timeout(parseInt(process.env.MOCHA_TIMEOUT || '5000', 10)); if (process.env.TEST_JUNIT_XML_PATH) { + process.stderr.write(`Writing XUnit test results to path: ${process.env.TEST_JUNIT_XML_PATH}\n`); mocha.reporter(require('mocha-multi-reporters'), { reportersEnabled: 'xunit, list', xunitReporterOptions: { From 14d2bedd311b38bceaa00b41cd97d39395556954 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 11:42:40 -0400 Subject: [PATCH 0386/4053] Unify Mocha reporter configuration --- .circleci/config.yml | 3 +++ test/runner.js | 7 ------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4343e459ca..e5fc45d182 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,6 +38,7 @@ jobs: - UNTIL_TIMEOUT: "30000" - CIRCLE_BUILD_IMAGE: osx - ATOM_CHANNEL: stable + - TEST_JUNIT_XML_PATH: test-results/mocha/test-results.xml beta: <<: *defaults environment: @@ -51,6 +52,7 @@ jobs: - UNTIL_TIMEOUT: "30000" - CIRCLE_BUILD_IMAGE: osx - ATOM_CHANNEL: beta + - TEST_JUNIT_XML_PATH: test-results/mocha/test-results.xml dev: <<: *defaults environment: @@ -64,6 +66,7 @@ jobs: - UNTIL_TIMEOUT: "30000" - CIRCLE_BUILD_IMAGE: osx - ATOM_CHANNEL: dev + - TEST_JUNIT_XML_PATH: test-results/mocha/test-results.xml workflows: version: 2 diff --git a/test/runner.js b/test/runner.js index e4db4d9e02..dc9328651b 100644 --- a/test/runner.js +++ b/test/runner.js @@ -115,12 +115,5 @@ module.exports = createRunner({ }); } else if (process.env.APPVEYOR_API_URL) { mocha.reporter(require('mocha-appveyor-reporter')); - } else if (process.env.CIRCLECI === 'true') { - mocha.reporter(require('mocha-multi-reporters'), { - reportersEnabled: 'xunit, list', - xunitReporterOptions: { - output: path.join('test-results', 'mocha', 'test-results.xml'), - }, - }); } }); From 3b8fe7a25378da812096f232b48d242384323697 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 13:39:43 -0400 Subject: [PATCH 0387/4053] Oops, no "s" there --- test/runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runner.js b/test/runner.js index dc9328651b..3d2ef4be55 100644 --- a/test/runner.js +++ b/test/runner.js @@ -108,7 +108,7 @@ module.exports = createRunner({ if (process.env.TEST_JUNIT_XML_PATH) { process.stderr.write(`Writing XUnit test results to path: ${process.env.TEST_JUNIT_XML_PATH}\n`); mocha.reporter(require('mocha-multi-reporters'), { - reportersEnabled: 'xunit, list', + reporterEnabled: 'xunit, list', xunitReporterOptions: { output: process.env.TEST_JUNIT_XML_PATH, }, From 46c82402cd2b94a3e29934b13f68f4dc3967eadf Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 3 Oct 2018 11:30:05 -0700 Subject: [PATCH 0388/4053] Disable discard/undo context menu items when appropriate --- lib/views/staging-view.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 4de588a72d..52817af754 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -301,7 +301,7 @@ export default class StagingView extends React.Component { + /> ); } else { return null; @@ -611,19 +611,17 @@ export default class StagingView extends React.Component { const selectedItemCount = this.getSelectedItemFilePaths().length; const pluralization = selectedItemCount > 1 ? 's' : ''; - if (this.props.unstagedChanges.length && selectedItemCount) { - menu.append(new MenuItem({ - label: 'Discard Changes in Selected File' + pluralization, - click: () => this.discardChanges(), - })); - } + menu.append(new MenuItem({ + label: 'Discard Changes in Selected File' + pluralization, + click: () => this.discardChanges(), + enabled: !!(this.props.unstagedChanges.length && selectedItemCount), + })); - if (this.props.hasUndoHistory) { - menu.append(new MenuItem({ - label: 'Undo Last Discard', - click: () => this.undoLastDiscard(), - })); - } + menu.append(new MenuItem({ + label: 'Undo Last Discard', + click: () => this.undoLastDiscard(), + enabled: this.props.hasUndoHistory, + })); menu.popup(remote.getCurrentWindow()); } From dca63d6aa2e44729a7078ddc734aabd70f556472 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 3 Oct 2018 11:37:53 -0700 Subject: [PATCH 0389/4053] Drop context menu items on StagingView headers --- menus/git.cson | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/menus/git.cson b/menus/git.cson index fa126d32d1..1e166db943 100644 --- a/menus/git.cson +++ b/menus/git.cson @@ -77,25 +77,6 @@ 'command': 'github:open-link-in-browser' } ] - '.github-UnstagedChanges .github-StagingView-header': [ - { - 'label': 'Stage All Changes' - 'command': 'github:stage-all-changes' - } - { - 'type': 'separator' - } - { - 'label': 'Discard All Changes' - 'command': 'github:discard-all-changes' - } - ] - '.github-StagedChanges .github-StagingView-header': [ - { - 'label': 'Unstage All Changes' - 'command': 'github:unstage-all-changes' - } - ] '.github-CommitView': [ { 'type': 'separator' From 9581681a42ae5eea434d6458ae2e17230ea93c40 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 14:40:42 -0400 Subject: [PATCH 0390/4053] Using .strictEquals() on Editors turns out poorly --- test/integration/open.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/integration/open.test.js b/test/integration/open.test.js index 0210742748..7c57282ed4 100644 --- a/test/integration/open.test.js +++ b/test/integration/open.test.js @@ -41,7 +41,8 @@ describe('opening and closing tabs', function() { console.log('5'); assert.isTrue(atomEnv.workspace.getRightDock().isVisible()); console.log('6'); - await assert.async.strictEqual(atomEnv.workspace.getActivePaneItem(), editor); + // Don't use assert.async.strictEqual() because it times out Mocha's failure diffing <_< + await assert.async.isTrue(atomEnv.workspace.getActivePaneItem() === editor); console.log('7'); }); From 08900af0190aa6f85b4cd2564c7bbd400cf7ff72 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 3 Oct 2018 11:38:13 -0700 Subject: [PATCH 0391/4053] Make Stage/Unstage All buttons always visible. Disable when no changes Co-Authored-By: David Wilson --- lib/views/staging-view.js | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 52817af754..51753158ac 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -195,7 +195,10 @@ export default class StagingView extends React.Component { Unstaged Changes {this.renderActionsMenu()} - {this.props.unstagedChanges.length ? this.renderStageAllButton() : null} +
{ @@ -222,7 +225,9 @@ export default class StagingView extends React.Component { Staged Changes - { this.props.stagedChanges.length ? this.renderUnstageAllButton() : null } +
{ @@ -280,21 +285,6 @@ export default class StagingView extends React.Component { ); } - renderStageAllButton() { - return ( - - ); - } - - renderUnstageAllButton() { - return ( - - ); - } - renderActionsMenu() { if (this.props.unstagedChanges.length || this.props.hasUndoHistory) { return ( From 8336eeea8da87b137e3611dd13cff8e61e6f143d Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 3 Oct 2018 11:46:41 -0700 Subject: [PATCH 0392/4053] Add "Discard All Changes" to kebab menu Co-Authored-By: David Wilson --- lib/views/staging-view.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 51753158ac..49ceb46af2 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -601,6 +601,12 @@ export default class StagingView extends React.Component { const selectedItemCount = this.getSelectedItemFilePaths().length; const pluralization = selectedItemCount > 1 ? 's' : ''; + menu.append(new MenuItem({ + label: 'Discard All Changes', + click: () => this.discardAll(), + enabled: this.props.unstagedChanges.length > 0, + })); + menu.append(new MenuItem({ label: 'Discard Changes in Selected File' + pluralization, click: () => this.discardChanges(), From 22ec1fe6b81f00e47064bc34a0fff8accf894a2a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 3 Oct 2018 12:40:10 -0700 Subject: [PATCH 0393/4053] Instrument discard/undo discard actions in StagingView --- lib/views/staging-view.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 49ceb46af2..2ad8e02b5d 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -16,6 +16,7 @@ import RefHolder from '../models/ref-holder'; import FilePatchController from '../controllers/file-patch-controller'; import Commands, {Command} from '../atom/commands'; import {autobind} from '../helpers'; +import {addEvent} from '../reporter-proxy'; const debounce = (fn, wait) => { let timeout; @@ -398,6 +399,12 @@ export default class StagingView extends React.Component { discardChanges() { const filePaths = this.getSelectedItemFilePaths(); + addEvent('discard-unstaged-changes', { + package: 'github', + component: 'StagingView', + fileCount: filePaths.length, + type: 'selected', + }); return this.props.discardWorkDirChangesForPaths(filePaths); } @@ -467,6 +474,12 @@ export default class StagingView extends React.Component { discardAll() { if (this.props.unstagedChanges.length === 0) { return null; } const filePaths = this.props.unstagedChanges.map(filePatch => filePatch.filePath); + addEvent('discard-unstaged-changes', { + package: 'github', + component: 'StagingView', + fileCount: filePaths.length, + type: 'all', + }); return this.props.discardWorkDirChangesForPaths(filePaths); } @@ -833,6 +846,11 @@ export default class StagingView extends React.Component { return; } + addEvent('undo-last-discard', { + package: 'github', + component: 'StagingView', + }); + this.props.undoLastDiscard(); } From 636ccf037debcd0edf732021264e4e3efd895a1f Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 3 Oct 2018 12:40:27 -0700 Subject: [PATCH 0394/4053] Instrument discard/undo discard actions in FilePatchController --- lib/controllers/file-patch-controller.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 49bf2f099c..5097e14e8c 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -8,6 +8,7 @@ import Switchboard from '../switchboard'; import FilePatchView from '../views/file-patch-view'; import ModelObserver from '../models/model-observer'; import {autobind} from '../helpers'; +import {addEvent} from '../reporter-proxy'; export default class FilePatchController extends React.Component { static propTypes = { @@ -530,10 +531,19 @@ export default class FilePatchController extends React.Component { } discardLines(lines) { + addEvent('discard-unstaged-changes', { + package: 'github', + component: 'FilePatchController', + lineCount: lines.length, + }); return this.props.discardLines(this.state.filePatch, lines, this.repositoryObserver.getActiveModel()); } undoLastDiscard() { + addEvent('undo-last-discard', { + package: 'github', + component: 'FilePatchController', + }); return this.props.undoLastDiscard(this.props.filePath, this.repositoryObserver.getActiveModel()); } From c2049c18fbada4ddae33a382534c9012911b9b9c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 15:48:06 -0400 Subject: [PATCH 0395/4053] Don't rely on document.activeElement and atomEnv.workspace being in sync --- test/integration/open.test.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/test/integration/open.test.js b/test/integration/open.test.js index 7c57282ed4..bdd29f04cf 100644 --- a/test/integration/open.test.js +++ b/test/integration/open.test.js @@ -25,25 +25,20 @@ describe('opening and closing tabs', function() { }); it('opens but does not focus the git tab on github:toggle-git-tab', async function() { - console.log('0'); + atomEnv.workspace.getCenter().activate(); const editor = await atomEnv.workspace.open(__filename); - console.log('1'); assert.isFalse(wrapper.find('.github-Git').exists()); - console.log('2'); + const previousFocus = document.activeElement; + await commands.dispatch(workspaceElement, 'github:toggle-git-tab'); - console.log('3'); wrapper.update(); - console.log('4'); assert.isTrue(wrapper.find('.github-Git').exists()); - console.log('5'); - assert.isTrue(atomEnv.workspace.getRightDock().isVisible()); - console.log('6'); // Don't use assert.async.strictEqual() because it times out Mocha's failure diffing <_< - await assert.async.isTrue(atomEnv.workspace.getActivePaneItem() === editor); - console.log('7'); + await assert.async.isTrue(atomEnv.workspace.getRightDock().isVisible()); + await assert.async.isTrue(previousFocus === document.activeElement); }); it('reveals an open but hidden git tab on github:toggle-git-tab', async function() { From efc40ebda30531754da7db30de8707b95dc40574 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 15:51:02 -0400 Subject: [PATCH 0396/4053] :fire: stderr output --- test/runner.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/runner.js b/test/runner.js index 3d2ef4be55..0aace08b01 100644 --- a/test/runner.js +++ b/test/runner.js @@ -106,7 +106,6 @@ module.exports = createRunner({ mocha.timeout(parseInt(process.env.MOCHA_TIMEOUT || '5000', 10)); if (process.env.TEST_JUNIT_XML_PATH) { - process.stderr.write(`Writing XUnit test results to path: ${process.env.TEST_JUNIT_XML_PATH}\n`); mocha.reporter(require('mocha-multi-reporters'), { reporterEnabled: 'xunit, list', xunitReporterOptions: { From 8cba2f813e794d69147c509efbb7381cd908aa87 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 3 Oct 2018 12:54:30 -0700 Subject: [PATCH 0397/4053] Add StagingView tests for event recording for discarding/undoing discards --- test/views/staging-view.test.js | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/views/staging-view.test.js b/test/views/staging-view.test.js index b268a44093..e046123b7c 100644 --- a/test/views/staging-view.test.js +++ b/test/views/staging-view.test.js @@ -3,6 +3,7 @@ import React from 'react'; import {mount} from 'enzyme'; import StagingView from '../../lib/views/staging-view'; import ResolutionProgress from '../../lib/models/conflicts/resolution-progress'; +import * as reporterProxy from '../../lib/reporter-proxy'; import {assertEqualSets} from '../helpers'; @@ -785,4 +786,52 @@ describe('StagingView', function() { wrapper.instance().refRoot.setter(null); assert.isFalse(wrapper.instance().hasFocus()); }); + + describe('discardAll()', function() { + it('records an event', function() { + const filePatches = [ + {filePath: 'a.txt', status: 'modified'}, + {filePath: 'b.txt', status: 'deleted'}, + ]; + const wrapper = mount(React.cloneElement(app, {unstagedChanges: filePatches})); + sinon.stub(reporterProxy, 'addEvent'); + wrapper.instance().discardAll(); + assert.isTrue(reporterProxy.addEvent.calledWith('discard-unstaged-changes', { + package: 'github', + component: 'StagingView', + fileCount: 2, + type: 'all', + })); + }); + }); + + describe('discardChanges()', function() { + it('records an event', function() { + const wrapper = mount(app); + sinon.stub(reporterProxy, 'addEvent'); + sinon.stub(wrapper.instance(), 'getSelectedItemFilePaths').returns(['a.txt', 'b.txt']); + wrapper.instance().discardChanges(); + assert.isTrue(reporterProxy.addEvent.calledWith('discard-unstaged-changes', { + package: 'github', + component: 'StagingView', + fileCount: 2, + type: 'selected', + })); + }); + }); + + describe('undoLastDiscard()', function() { + it('records an event', function() { + const wrapper = mount(app); + sinon.stub(reporterProxy, 'addEvent'); + sinon.stub(wrapper.instance(), 'getSelectedItemFilePaths').returns(['a.txt', 'b.txt']); + wrapper.instance().discardChanges(); + assert.isTrue(reporterProxy.addEvent.calledWith('discard-unstaged-changes', { + package: 'github', + component: 'StagingView', + fileCount: 2, + type: 'selected', + })); + }); + }); }); From 5dc113a368004586233d9fd4f138b3cf17f4c42f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 15:57:42 -0400 Subject: [PATCH 0398/4053] :shirt: :shirt: :shirt: --- lib/atom/decoration.js | 2 +- test/atom/keystroke.test.js | 2 -- test/integration/open.test.js | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/atom/decoration.js b/lib/atom/decoration.js index 15fe3ceacb..841b8bd22e 100644 --- a/lib/atom/decoration.js +++ b/lib/atom/decoration.js @@ -177,7 +177,7 @@ export default class Decoration extends React.Component { if (editorChanged) { nextState.editorHolder = RefHolder.on(props.editor); } - if (markableChanged) { + if (decorableChanged) { nextState.decorableHolder = RefHolder.on(props.decorable); } return nextState; diff --git a/test/atom/keystroke.test.js b/test/atom/keystroke.test.js index 21c11f9605..b81abf654f 100644 --- a/test/atom/keystroke.test.js +++ b/test/atom/keystroke.test.js @@ -48,8 +48,6 @@ describe('Keystroke', function() { , ); - console.log(wrapper.debug()); - assert.isFalse(wrapper.find('span.keystroke').exists()); }); diff --git a/test/integration/open.test.js b/test/integration/open.test.js index bdd29f04cf..d573e0b8f3 100644 --- a/test/integration/open.test.js +++ b/test/integration/open.test.js @@ -26,7 +26,7 @@ describe('opening and closing tabs', function() { it('opens but does not focus the git tab on github:toggle-git-tab', async function() { atomEnv.workspace.getCenter().activate(); - const editor = await atomEnv.workspace.open(__filename); + await atomEnv.workspace.open(__filename); assert.isFalse(wrapper.find('.github-Git').exists()); const previousFocus = document.activeElement; From 3fabe7c6c90b289f035f5c394c364a37947feb37 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 3 Oct 2018 13:03:21 -0700 Subject: [PATCH 0399/4053] Add FilePatchController event recording tests for discard/undo discard --- .../controllers/file-patch-controller.test.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index 2026f34079..7618cac6f2 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -12,6 +12,7 @@ import Hunk from '../../lib/models/hunk'; import HunkLine from '../../lib/models/hunk-line'; import ResolutionProgress from '../../lib/models/conflicts/resolution-progress'; import Switchboard from '../../lib/switchboard'; +import * as reporterProxy from '../../lib/reporter-proxy'; function createFilePatch(oldFilePath, newFilePath, status, hunks) { const oldFile = new FilePatch.File({path: oldFilePath}); @@ -298,6 +299,31 @@ describe('FilePatchController', function() { assert.isTrue(cursorCall.args[0].isEqual([4, 0])); }); }); + + describe('discardLines()', function() { + it('records an event', function() { + const wrapper = mount(component); + sinon.stub(reporterProxy, 'addEvent'); + wrapper.instance().discardLines(['once upon a time', 'there was an octocat named mona lisa']); + assert.isTrue(reporterProxy.addEvent.calledWith('discard-unstaged-changes', { + package: 'github', + component: 'FilePatchController', + lineCount: 2, + })); + }); + }); + + describe('undoLastDiscard()', function() { + it('records an event', function() { + const wrapper = mount(component); + sinon.stub(reporterProxy, 'addEvent'); + wrapper.instance().undoLastDiscard(); + assert.isTrue(reporterProxy.addEvent.calledWith('undo-last-discard', { + package: 'github', + component: 'FilePatchController', + })); + }); + }); }); describe('integration tests', function() { From 2db0e8ca97802b842941ed2208d4f6dd7451b46e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 3 Oct 2018 16:30:53 -0400 Subject: [PATCH 0400/4053] Prepare 0.19.1-4 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 015c340d38..a99b406a7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.19.1-3", + "version": "0.19.1-4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index df329533d1..c36d3aa61e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.19.1-3", + "version": "0.19.1-4", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From cd41db432dd84aba4b2557590c58c66e3dbdab38 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 3 Oct 2018 17:14:59 -0700 Subject: [PATCH 0401/4053] Incorporate feedback Co-Authored-By: Ash Wilson Co-Authored-By: Tilde Ann Thurium --- docs/rfcs/XXX-pull-request-review.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 7fb9271b78..83ba5d69cb 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -23,7 +23,7 @@ Peer review is also a critical part of the path to acceptance for pull requests Review progress is indicated for open pull requests listed in the GitHub panel. The pull request corresponding to the checked out branch gets special treatment in it's own section at the top of the list. slack_-_github -Note: Change "Current pull request" +Note: Change "Current pull request" to "Checked out pull request" Clicking a pull request in the list opens a `PullRequestDetailItem` in the workspace center. @@ -40,7 +40,7 @@ Each `PullRequestDetailItem` opened on a pull request displays the full, multi-f TODO: update mock to have "Start review button" and "Add single comment" -Diffs are editable ONLY if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. Otherwise diffs are not editable. +Diffs are editable ONLY if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. Otherwise diffs are not editable. Details of this will be fleshed out in a separate RFC specifically for editable diffs. A panel at the bottom of the pane offers various options for sorting and filtering the diff. It also has a "Review Changes" button. @@ -48,6 +48,8 @@ A panel at the bottom of the pane offers various options for sorting and filteri slack_-_github +Note: We probably want to find better verbiage than "sort". Let's also consider a dropdown menu UX to select different views of the data. + The default view is sorted by files. This is akin to the "Files changed" tab on dotcom. It displays the diff for all changed files in the PR. Sorting by reviews is akin to the review summaries that appear on the "Conversation" tab on dotcom. The comments are displayed grouped by review along with some context lines. @@ -56,7 +58,7 @@ Sorting by reviews is akin to the review summaries that appear on the "Conversat TODO: include show multiple reviews stacked -Sorting by commits is akin to the "Commits" tab on dotcom. A list of commits is displayed in chronological order, oldest commit on top. Clicking a commit expands the diff contents below. If there is a commit message body this is displayed as well. +Sorting by commits is akin to the "Commits" tab on dotcom. A list of commits is displayed in chronological order, oldest commit on top. Clicking a commit expands the diff contents below. If there is a commit message body this is displayed as well. Commit diffs are not editable. TODO: include commit sort mockup @@ -111,7 +113,7 @@ Clicking on the build status summary icon (green checkmark, donut chart, or X) e slack_-_github -Clicking on the conversation/timeline icon expands an ephemeral panel beneath the summary box showing a very timeline view. The PR description and PR comments are displayed here. Other note-worthy timeline events are displayed in a very minimal fashion. +Clicking on the conversation/timeline icon expands an ephemeral panel beneath the summary box showing a very timeline view. The PR description and PR comments are displayed here. Other note-worthy timeline events are displayed in a very minimal fashion. At the bottom is an input field to add a new PR comment. TODO: add conversation/timeline popover mockup @@ -178,7 +180,7 @@ We also discussed implementing comment decorations in regular text editors. Clic When there are working directory changes, how do we clearly indicate them within the diff view? Do we need to make them visually distinct from the PR changes? Things might get confusing for the user when the diff in the editor gets out of sync with the diff on dotcom. Example: -* Author reads comment pointing out typo in an added line. Author edits text in multi-file diff which modifies the working directory. Should this line now be orange to indicate that it has deviated from the original diff? +* Author reads comment pointing out typo in an added line. Author edits text in multi-file diff which modifies the working directory. Should this line now be styled differently to indicate that it has deviated from the original diff? Can we access "draft" reviews from the GitHub API, to unify them between Atom and GitHub? @@ -197,8 +199,6 @@ Similarly, are there any ways we can encourage empathy within the review authori * _Emoji reactions on comments :cake: :tada:_ * _Enable integration with Teletype for smoother jumping to a synchronous review_ -How do we clearly indicating recently added changes? That is, new changes pushed, comments, reviews, etc since the last time the users viewed the PR info. Is it enough to simply update the timeline view? Is it too easy to miss changes? - ### Questions I expect to resolve throughout the implementation process Review comment positioning within live TextEditors will be a tricky problem to address satisfactorily. What are the edge cases we need to handle there? From 29c6a6e25e1e25a846386870a5a793e528f15d7b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 3 Oct 2018 17:19:07 -0700 Subject: [PATCH 0402/4053] Add in-line comments back Co-Authored-By: Ash Wilson --- docs/rfcs/XXX-pull-request-review.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 83ba5d69cb..f00b165089 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -136,8 +136,7 @@ Within the multi-file diff view, a block decoration is used to show the comment * The comment's position is calculated from the position acquired by the GitHub API response, modified based on the git diff of that file (following renames) between the owning review's attached commit and the current state of the working copy (including any local modifications). Once created, the associated marker will also track unsaved modifications to the file in real time. * The up and down arrow buttons navigate to the next and previous review comments. -* Clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the line corresponding to the comment. - * In the future we will implement inline review comments as decorations in the code. +* For comment decorations in the `PullRequestDetailItem`, clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the review comment decoration there. * If the current pull request is not checked out, the "code" button is disabled, and a tooltip prompts the user to check out the pull request to edit the source. * Reaction emoji may be added to each comment with the "emoji" button. Existing emoji reaction tallies are included beneath each comment. @@ -170,8 +169,6 @@ We decided to switch to an editor-first approach and build the code review exper We discussed displaying review summary information in the GitHub panel in a ["Current pull request tile"](https://github.com/atom/github/blob/2ab74b59873c3b5bccac7ef679795eb483b335cf/docs/rfcs/XXX-pull-request-review.md#current-pull-request-tile). The current design encapsulates all of the PR information and functionality within a `PullRequestDetailItem`. Keeping the GitHub panel free of PR details for a specific PR rids us of the problem of having to keep it updated when the user switches active repos (which can feel jarring). This also avoids confusing the user by showing PR details for different PRs (imagine the checked out PR info in the panel and a pane item with PR info for a separate repo). We also free up space in the GitHub panel, making it less busy/overwhelming and leaving room for other information we might want to provide there in the future (like associated issues, say). -We also discussed implementing comment decorations in regular text editors. Clicking the "code" (<>) button on a comment was originally to take you to the comment in the corresponding TextEditor file. While this is a nice feature to have, we can ship a complete code review experience without it. Let's punt on this and tackle it later on. - ## Unresolved questions From 2ca379989299b888fb30a8f638c3334f8616691f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 07:46:24 -0400 Subject: [PATCH 0403/4053] Prepare 0.19.1-5 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a99b406a7b..aad8bb2bb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.19.1-4", + "version": "0.19.1-5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c36d3aa61e..7ac89edf88 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.19.1-4", + "version": "0.19.1-5", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From ea3973bdac7202f69433f5c8ca7faae4cad80911 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 08:25:21 -0400 Subject: [PATCH 0404/4053] Mark remaining TODOs as :construction: --- docs/rfcs/XXX-pull-request-review.md | 54 +++++++++++++--------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index f00b165089..f5c41a4dd0 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -22,23 +22,21 @@ Peer review is also a critical part of the path to acceptance for pull requests Review progress is indicated for open pull requests listed in the GitHub panel. The pull request corresponding to the checked out branch gets special treatment in it's own section at the top of the list. -slack_-_github -Note: Change "Current pull request" to "Checked out pull request" +pull request list with review progress bars Clicking a pull request in the list opens a `PullRequestDetailItem` in the workspace center. For PRs that are not listed in the panel, users can use the `github:open-issue-or-pull-request` command: -xxx-pull-request-review_md_ ___github_github - +opening a pull request by URL ### PullRequestDetailItem Each `PullRequestDetailItem` opened on a pull request displays the full, multi-file diff associated with the pull request. Review comments are shown within the diff. See ["Comment decorations"](#comment-decorations) for description of review comments. -![screen shot 2018-10-03 at 1 50 18 pm](https://user-images.githubusercontent.com/7910250/46391711-1df6b600-c693-11e8-87f3-ad4cdbe8ebd8.png) +![pull request detail item](https://user-images.githubusercontent.com/7910250/46391711-1df6b600-c693-11e8-87f3-ad4cdbe8ebd8.png) -TODO: update mock to have "Start review button" and "Add single comment" +> :construction: Update mock to have "Start review button" and "Add single comment" Diffs are editable ONLY if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. Otherwise diffs are not editable. Details of this will be fleshed out in a separate RFC specifically for editable diffs. @@ -46,33 +44,33 @@ A panel at the bottom of the pane offers various options for sorting and filteri #### Sort Options -slack_-_github +sort by -Note: We probably want to find better verbiage than "sort". Let's also consider a dropdown menu UX to select different views of the data. +> :construction: We probably want to find better verbiage than "sort". Let's also consider a dropdown menu UX to select different views of the data. The default view is sorted by files. This is akin to the "Files changed" tab on dotcom. It displays the diff for all changed files in the PR. Sorting by reviews is akin to the review summaries that appear on the "Conversation" tab on dotcom. The comments are displayed grouped by review along with some context lines. -![screen shot 2018-10-03 at 1 50 08 pm](https://user-images.githubusercontent.com/7910250/46394598-6ebfdc00-c69e-11e8-84eb-39ccbcccf736.png) +![sorted by reviews](https://user-images.githubusercontent.com/7910250/46394598-6ebfdc00-c69e-11e8-84eb-39ccbcccf736.png) -TODO: include show multiple reviews stacked +> :construction: Show multiple reviews stacked Sorting by commits is akin to the "Commits" tab on dotcom. A list of commits is displayed in chronological order, oldest commit on top. Clicking a commit expands the diff contents below. If there is a commit message body this is displayed as well. Commit diffs are not editable. -TODO: include commit sort mockup +> :construction: Include commit sort mockup A banner at the bottom of the pane offers navigation to individual files within the diff and to individual review comments, allows each review to be hidden or shown with a filter control, and shows a progress bar that counts "resolved" review comments. The banner remains visible as you scroll the pane. #### Filter Options -slack_-_github +filter options The default is to show all files, all authors, and unresolved comments. Filtering based on file type limits the diff view to displaying only that file type. -TODO: Consider adding a "Find" input field that allows us to filter based on search term (which could be a file name, an author, a variable name, etc). Probably out of scope for this RFC. +> :construction: Consider adding a "Find" input field that allows us to filter based on search term (which could be a file name, an author, a variable name, etc). Clicking an author's avatar displays only their review information. @@ -86,13 +84,13 @@ Clicking "none" hides all comments, in the event that users want to see diff inf #### Submitting a Review -slack_-_github +review changes button -Clicking the "Review Changes" button reveals a UI much like dotcom's +Clicking the "Review Changes" button reveals a UI much like dotcom's: -xxx-pull-request-review_md_ ___github_github +review changes panel -TODO: update Review Changes mockup +> :construction: Update the Review Changes mockup * The review summary is a TextEditor that may be used to compose a summary comment. * Choosing "Cancel" dismisses the review and any comments made. If there are local review comments that will be lost, a confirmation prompt is shown first. @@ -102,8 +100,9 @@ TODO: update Review Changes mockup At the top of the pane is the existing summary box: -issueish-detail-item pane -TODO: add conversation/timeline icon and progress bar +pull request details pane summary box + +> :construction: Add conversation/timeline icon and progress bar Clicking on the "22 commits" opens the commit view and changes the bottom panel to indicate sort by commits. @@ -111,24 +110,23 @@ Clicking on the "1 changed files" opens the files view and changes the bottom pa Clicking on the build status summary icon (green checkmark, donut chart, or X) expands an ephemeral panel beneath the summary box showing build review status. Clicking the icon again or clicking on "dismiss" dismisses it. -slack_-_github +emphemeral checks panel Clicking on the conversation/timeline icon expands an ephemeral panel beneath the summary box showing a very timeline view. The PR description and PR comments are displayed here. Other note-worthy timeline events are displayed in a very minimal fashion. At the bottom is an input field to add a new PR comment. -TODO: add conversation/timeline popover mockup +> :construction: Add conversation/timeline popover mockup Clicking the "expand" icon on the top right opens this information in a new pane to the right for easy side-by-side viewing with the diff (much like our current markdown preview opens in a separate pane). -TODO: add conversation/timeline pane item +> :construction: Add conversation/timeline pane item Clicking on the a commit takes you to the commit view and expands the selected commit, centering it in view. -TODO: add commit view mockup +> :construction: Add commit view mockup Clicking on a review reference takes you to the review view and expands the selected review, centering it in view. -TODO: add review mockup - +> :construction: Add review mockup ### Comment decorations @@ -169,8 +167,6 @@ We decided to switch to an editor-first approach and build the code review exper We discussed displaying review summary information in the GitHub panel in a ["Current pull request tile"](https://github.com/atom/github/blob/2ab74b59873c3b5bccac7ef679795eb483b335cf/docs/rfcs/XXX-pull-request-review.md#current-pull-request-tile). The current design encapsulates all of the PR information and functionality within a `PullRequestDetailItem`. Keeping the GitHub panel free of PR details for a specific PR rids us of the problem of having to keep it updated when the user switches active repos (which can feel jarring). This also avoids confusing the user by showing PR details for different PRs (imagine the checked out PR info in the panel and a pane item with PR info for a separate repo). We also free up space in the GitHub panel, making it less busy/overwhelming and leaving room for other information we might want to provide there in the future (like associated issues, say). - - ## Unresolved questions ### Questions I expect to address before this is merged @@ -185,11 +181,11 @@ Can we access "draft" reviews from the GitHub API, to unify them between Atom an How do we represent the resolution of a comment thread? Where can we reveal this progress through each review, and of all required reviews? -* _We'll show a progress bar on a sticky header at the top of the `IssueishDetailItem`._ +* _We'll show a progress bar on a sticky header at the top of the `PullRequestDetailItem`._ Are there any design choices we can make to lessen the emotional weight of a "requests changes" review? Peer review has the most value when it discovers issues for the pull request author to address, but accepting criticism is a vulnerable moment. -* _Chosing phrasing and iconography carefully for "recommend changes"._ +* _Choosing phrasing and iconography carefully for "recommend changes"._ Similarly, are there any ways we can encourage empathy within the review authoring process? Can we encourage reviewers to make positive comments or demonstrate humility and open-mindedness? From 2750281c934e88f21a38443b40a2979533804d8d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 08:37:38 -0400 Subject: [PATCH 0405/4053] Add an Explanation section for in-editor comment rendering --- docs/rfcs/XXX-pull-request-review.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index f5c41a4dd0..248d18418c 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -128,17 +128,32 @@ Clicking on a review reference takes you to the review view and expands the sele > :construction: Add review mockup +### In-editor decorations + +When opening a TextEditor on a file that has been annotated with review comments on the current pull request, a block decoration is used to show the comment content at the corresponding position within the file content. Also, a gutter decoration is used to reveal lines that are included within the current pull requests' diff and may therefore include comments. + +![in-editor review comment decoration](https://user-images.githubusercontent.com/378023/44790482-69bcc800-abda-11e8-8a0f-922c0942b8c6.png) + +> :construction: Add gutter decoration? + +* The comment's position is calculated from the position acquired by the GitHub API response, modified based on the git diff of that file (following renames) between the owning review's attached commit and the current state of the working copy (including any local modifications). Once created, the associated marker will also track unsaved modifications to the file in real time. +* The up and down arrow buttons navigate to the next and previous review comments within this review within their respective TextEditors. +* The "diff" button navigates to the corresponding pull request's detail item and scrolls to center the same comment within that view. + ### Comment decorations -Within the multi-file diff view, a block decoration is used to show the comment content at the corresponding position within the file content. +Within the multi-file diff view or a TextEditor, a block decoration is used to show the comment content at the corresponding position within the file content. * The comment's position is calculated from the position acquired by the GitHub API response, modified based on the git diff of that file (following renames) between the owning review's attached commit and the current state of the working copy (including any local modifications). Once created, the associated marker will also track unsaved modifications to the file in real time. * The up and down arrow buttons navigate to the next and previous review comments. * For comment decorations in the `PullRequestDetailItem`, clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the review comment decoration there. * If the current pull request is not checked out, the "code" button is disabled, and a tooltip prompts the user to check out the pull request to edit the source. +* For comment decorations within a `TextEditor`, clicking the "diff" button opens the corresponding `PullRequestDetailItem` and scrolls to focus the equivalent comment. * Reaction emoji may be added to each comment with the "emoji" button. Existing emoji reaction tallies are included beneath each comment. -Hovering along the gutter within a pull request diff region reveals a `+` icon, which may be clicked to begin a new review: +### Line comment creation + +Hovering along the gutter within a pull request diff region in a `TextEditor` or a `PullRequestDetailItem` reveals a `+` icon, which may be clicked to begin a new review: ![plus-icon](https://user-images.githubusercontent.com/378023/40348708-6698b2ea-5ddf-11e8-8eaa-9d95bc483fb1.png) From c44566882bb37e7dcbd3eb91527637f48826b05a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 08:45:39 -0400 Subject: [PATCH 0406/4053] Move diff editability questions to "answered during implementation" --- docs/rfcs/XXX-pull-request-review.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 248d18418c..11ca6574a0 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -38,7 +38,7 @@ Each `PullRequestDetailItem` opened on a pull request displays the full, multi-f > :construction: Update mock to have "Start review button" and "Add single comment" -Diffs are editable ONLY if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. Otherwise diffs are not editable. Details of this will be fleshed out in a separate RFC specifically for editable diffs. +Diffs are editable _only_ if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. A panel at the bottom of the pane offers various options for sorting and filtering the diff. It also has a "Review Changes" button. @@ -218,6 +218,11 @@ The GraphQL API paths we need to interact with all involve multiple levels of pa How do we handle comment threads? +When editing diffs: + +* Do we edit the underlying buffer or file directly, or do we mark the `PullRequestDetailItem` as "modified" and require a "save" action to persist changes? +* Do we disallow edits of removed lines, or do we re-introduce the removed line as an addition on modification? + ### Questions I consider out of scope of this RFC What other pull request information can we add to the GitHub pane item? @@ -230,5 +235,4 @@ How can we notify users when new information, including reviews, is available, p ## Related features out of scope of this RFC -* Inline review comments * "Find" input field for filtering based on search term (which could be a file name, an author, a variable name, etc) From 17ec9370cde9baa7437fd655f6af54c5d2af160e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 08:49:29 -0400 Subject: [PATCH 0407/4053] Move this question from "before merge" to "during implementation" aka "cheating" --- docs/rfcs/XXX-pull-request-review.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 11ca6574a0..36b1a95ddd 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -186,10 +186,6 @@ We discussed displaying review summary information in the GitHub panel in a ["Cu ### Questions I expect to address before this is merged -When there are working directory changes, how do we clearly indicate them within the diff view? Do we need to make them visually distinct from the PR changes? Things might get confusing for the user when the diff in the editor gets out of sync with the diff on dotcom. -Example: -* Author reads comment pointing out typo in an added line. Author edits text in multi-file diff which modifies the working directory. Should this line now be styled differently to indicate that it has deviated from the original diff? - Can we access "draft" reviews from the GitHub API, to unify them between Atom and GitHub? * _Yes, the `reviews` object includes it in a `PENDING` state._ @@ -209,6 +205,8 @@ Similarly, are there any ways we can encourage empathy within the review authori ### Questions I expect to resolve throughout the implementation process +When there are working directory changes or local commits on the PR branch, how do we clearly indicate them within the diff view? Do we need to make them visually distinct from the PR changes? Things might get confusing for the user when the diff in the editor gets out of sync with the diff on dotcom. For example: a pull request author reads a comment pointing out a typo in an added line. The author edits text within the multi-file diff which modifies the working directory. Should this line now be styled differently to indicate that it has deviated from the original diff? + Review comment positioning within live TextEditors will be a tricky problem to address satisfactorily. What are the edge cases we need to handle there? * _Review comments on deleted lines._ From e6c34abfaa8907aa8da00c250a78c33311b834a7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 09:01:17 -0400 Subject: [PATCH 0408/4053] Dependency graph bump --- docs/rfcs/XXX-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/XXX-pull-request-review.md index 36b1a95ddd..ff981eae1e 100644 --- a/docs/rfcs/XXX-pull-request-review.md +++ b/docs/rfcs/XXX-pull-request-review.md @@ -229,7 +229,7 @@ How can we notify users when new information, including reviews, is available, p ## Implementation phases -![dependency-graph](https://user-images.githubusercontent.com/17565/46361100-d47a7c80-c63a-11e8-83de-4a548be9cb9c.png) +![dependency-graph](https://user-images.githubusercontent.com/17565/46475622-019e6a80-c7b4-11e8-9bf5-8223d5c6631f.png) ## Related features out of scope of this RFC From bad57704f9f9b86e1599088f3211bc68aef02a3e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 09:21:54 -0400 Subject: [PATCH 0409/4053] Give it a number :tada: --- .../{XXX-pull-request-review.md => 003-pull-request-review.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/rfcs/{XXX-pull-request-review.md => 003-pull-request-review.md} (100%) diff --git a/docs/rfcs/XXX-pull-request-review.md b/docs/rfcs/003-pull-request-review.md similarity index 100% rename from docs/rfcs/XXX-pull-request-review.md rename to docs/rfcs/003-pull-request-review.md From 5ad24e6037e94b2c976fbefa418691df856c478f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 09:22:55 -0400 Subject: [PATCH 0410/4053] Status: accepted :hammer: --- docs/rfcs/003-pull-request-review.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index ff981eae1e..06eb3bd368 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -1,10 +1,8 @@ -# Feature title - -Pull Request Review +# Pull Request Review ## Status -Proposed +Accepted ## Summary From 5055785fc07494558575636a331056bdaee7d80d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 10:28:51 -0400 Subject: [PATCH 0411/4053] Drill them props --- lib/containers/file-patch-container.js | 1 + lib/controllers/file-patch-controller.js | 1 + lib/controllers/root-controller.js | 1 + lib/items/file-patch-item.js | 1 + lib/views/file-patch-view.js | 1 + test/containers/file-patch-container.test.js | 1 + test/controllers/file-patch-controller.test.js | 1 + test/items/file-patch-item.test.js | 1 + test/views/file-patch-view.test.js | 1 + 9 files changed, 9 insertions(+) diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index 6c41e9de1d..eb85a11d7e 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -17,6 +17,7 @@ export default class FilePatchContainer extends React.Component { commands: PropTypes.object.isRequired, keymaps: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, + config: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, undoLastDiscard: PropTypes.func.isRequired, diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index cf85595b8f..1fa11bc485 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -17,6 +17,7 @@ export default class FilePatchController extends React.Component { commands: PropTypes.object.isRequired, keymaps: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, + config: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, discardLines: PropTypes.func.isRequired, diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 9eeeaf3e44..d4e320bacd 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -319,6 +319,7 @@ export default class RootController extends React.Component { commands={this.props.commandRegistry} keymaps={this.props.keymaps} workspace={this.props.workspace} + config={this.props.config} discardLines={this.discardLines} undoLastDiscard={this.undoLastDiscard} diff --git a/lib/items/file-patch-item.js b/lib/items/file-patch-item.js index 4e408cb56a..86ed8ad9c8 100644 --- a/lib/items/file-patch-item.js +++ b/lib/items/file-patch-item.js @@ -18,6 +18,7 @@ export default class FilePatchItem extends React.Component { commands: PropTypes.object.isRequired, keymaps: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, + config: PropTypes.object.isRequired, discardLines: PropTypes.func.isRequired, undoLastDiscard: PropTypes.func.isRequired, diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index dbda09fc64..8b4acc4655 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -37,6 +37,7 @@ export default class FilePatchView extends React.Component { commands: PropTypes.object.isRequired, keymaps: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, + config: PropTypes.object.isRequired, selectedRowsChanged: PropTypes.func.isRequired, diff --git a/test/containers/file-patch-container.test.js b/test/containers/file-patch-container.test.js index c06cfa43d8..81e744117c 100644 --- a/test/containers/file-patch-container.test.js +++ b/test/containers/file-patch-container.test.js @@ -38,6 +38,7 @@ describe('FilePatchContainer', function() { commands: atomEnv.commands, keymaps: atomEnv.keymaps, tooltips: atomEnv.tooltips, + config: atomEnv.config, discardLines: () => {}, undoLastDiscard: () => {}, destroy: () => {}, diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index 1672b7b15a..c20d8020c3 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -36,6 +36,7 @@ describe('FilePatchController', function() { commands: atomEnv.commands, keymaps: atomEnv.keymaps, tooltips: atomEnv.tooltips, + config: atomEnv.config, destroy: () => {}, discardLines: () => {}, undoLastDiscard: () => {}, diff --git a/test/items/file-patch-item.test.js b/test/items/file-patch-item.test.js index 8c95387a1e..c94a1c0964 100644 --- a/test/items/file-patch-item.test.js +++ b/test/items/file-patch-item.test.js @@ -33,6 +33,7 @@ describe('FilePatchItem', function() { commands: atomEnv.commands, keymaps: atomEnv.keymaps, tooltips: atomEnv.tooltips, + config: atomEnv.config, discardLines: () => {}, undoLastDiscard: () => {}, ...overrideProps, diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 97b4765c02..2affc25946 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -54,6 +54,7 @@ describe('FilePatchView', function() { repository, workspace, + config: atomEnv.config, commands: atomEnv.commands, keymaps: atomEnv.keymaps, tooltips: atomEnv.tooltips, From 5ec75153f389984370c2e78f8fb7ea68cff63243 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 4 Oct 2018 18:23:11 +0300 Subject: [PATCH 0412/4053] chamge 'current pr' to 'checked out pr' --- docs/rfcs/002-issueish-list.md | 4 +- package-lock.json | 2804 ++++++++++++++++---------------- 2 files changed, 1404 insertions(+), 1404 deletions(-) diff --git a/docs/rfcs/002-issueish-list.md b/docs/rfcs/002-issueish-list.md index a1be89d236..4b6a90d5bf 100644 --- a/docs/rfcs/002-issueish-list.md +++ b/docs/rfcs/002-issueish-list.md @@ -22,7 +22,7 @@ As an initial building block toward a pull request review workflow. Within the GitHub panel, render a vertical stack of two collapsible lists of _issueish_ (pull request or issue) items: -_First list: current pull request_. If the active branch is associated with one or more open pull requests on a GitHub repository, render an item for each. "Associated with" means that the pull request's head ref and head repository matches the upstream remote ref for the current branch in the active git repository. +_First list: checked out request_. If the active branch is associated with one or more open pull requests on a GitHub repository, render an item for each. "Associated with" means that the pull request's head ref and head repository matches the upstream remote ref for the current branch in the active git repository. _Second list: all open pull requests_. List all open pull requests on the GitHub repository, ordered by decreasing creation date. @@ -53,7 +53,7 @@ For a pull request, the issueish pane shows: * Author avatar * Title * Branches -> `master` < `aw/rfc-pr-list` -* "Checkout" button to fetch (if necessary) and check out the pull request. Only enabled if the current pull request is not the current one. +* "Checkout" button to fetch (if necessary) and check out the pull request. Only enabled if the checked out pull request is not the current one. * `Commits` with count, links to .com (for now), optional with avatars * `Checks` with count, links to .com (for now) * CI status, each item links to the detail page diff --git a/package-lock.json b/package-lock.json index 9ab8b46e39..ea382f85b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,10 +26,10 @@ "dev": true, "requires": { "@babel/types": "7.0.0-beta.49", - "jsesc": "2.5.1", - "lodash": "4.17.5", - "source-map": "0.5.7", - "trim-right": "1.0.1" + "jsesc": "^2.5.1", + "lodash": "^4.17.5", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" }, "dependencies": { "jsesc": { @@ -75,9 +75,9 @@ "integrity": "sha1-lr3GtD4TSCASumaRsQGEktOWIsw=", "dev": true, "requires": { - "chalk": "2.4.1", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^3.0.0" }, "dependencies": { "ansi-styles": { @@ -86,7 +86,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, "chalk": { @@ -95,9 +95,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "supports-color": { @@ -106,7 +106,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -126,7 +126,7 @@ "@babel/code-frame": "7.0.0-beta.49", "@babel/parser": "7.0.0-beta.49", "@babel/types": "7.0.0-beta.49", - "lodash": "4.17.5" + "lodash": "^4.17.5" } }, "@babel/traverse": { @@ -141,10 +141,10 @@ "@babel/helper-split-export-declaration": "7.0.0-beta.49", "@babel/parser": "7.0.0-beta.49", "@babel/types": "7.0.0-beta.49", - "debug": "3.1.0", - "globals": "11.7.0", - "invariant": "2.2.4", - "lodash": "4.17.5" + "debug": "^3.1.0", + "globals": "^11.1.0", + "invariant": "^2.2.0", + "lodash": "^4.17.5" }, "dependencies": { "debug": { @@ -170,9 +170,9 @@ "integrity": "sha1-t+Oxw/TUz+Eb34yJ8e/V4WF7h6Y=", "dev": true, "requires": { - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "2.0.0" + "esutils": "^2.0.2", + "lodash": "^4.17.5", + "to-fast-properties": "^2.0.0" } }, "@mrmlnc/readdir-enhanced": { @@ -181,8 +181,8 @@ "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", "dev": true, "requires": { - "call-me-maybe": "1.0.1", - "glob-to-regexp": "0.3.0" + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" } }, "@nodelib/fs.stat": { @@ -207,10 +207,10 @@ "dev": true, "requires": { "diff": "3.5.0", - "etch": "0.8.0", - "grim": "2.0.2", - "less": "3.8.1", - "mocha": "5.2.0", + "etch": "^0.8.0", + "grim": "^2.0.1", + "less": "^3.7.1", + "mocha": "^5.2.0", "tmp": "0.0.31" } }, @@ -220,13 +220,13 @@ "integrity": "sha512-YGotyPAPOwi9vbRzvTutDgqYbBo5gX8mELNHPICcAXilV9XMa0yxqCQ6OY4ItIO5dPiRkc9RkjSYBr2M1fFOZw==", "dev": true, "requires": { - "@smashwilson/enzyme-adapter-utils": "1.0.0", - "lodash": "4.17.5", - "object.assign": "4.1.0", - "object.values": "1.0.4", - "prop-types": "15.6.2", - "react-reconciler": "0.7.0", - "react-test-renderer": "16.4.1" + "@smashwilson/enzyme-adapter-utils": "^1.0.0", + "lodash": "^4.17.4", + "object.assign": "^4.1.0", + "object.values": "^1.0.4", + "prop-types": "^15.6.0", + "react-reconciler": "^0.7.0", + "react-test-renderer": "^16.0.0-0" } }, "@smashwilson/enzyme-adapter-utils": { @@ -235,9 +235,9 @@ "integrity": "sha512-/kQoTFU5bdbZbh6C9Ohxz1BgoHW+n2BX/kGUcS3DucGZfVNl4Em9lJqzd3aelyZSJz+cRX/FuE6ccnO6MnZc0Q==", "dev": true, "requires": { - "lodash": "4.17.5", - "object.assign": "4.1.0", - "prop-types": "15.6.2" + "lodash": "^4.17.4", + "object.assign": "^4.1.0", + "prop-types": "^15.6.0" } }, "@types/node": { @@ -258,7 +258,7 @@ "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", "dev": true, "requires": { - "acorn": "5.7.1" + "acorn": "^5.0.3" } }, "ajv": { @@ -266,10 +266,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "ajv-keywords": { @@ -283,9 +283,9 @@ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" }, "dependencies": { "kind-of": { @@ -293,7 +293,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -326,7 +326,7 @@ "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", "dev": true, "requires": { - "default-require-extensions": "2.0.0" + "default-require-extensions": "^2.0.0" } }, "aproba": { @@ -345,8 +345,8 @@ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.3" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "argparse": { @@ -355,7 +355,7 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "aria-query": { @@ -365,7 +365,7 @@ "dev": true, "requires": { "ast-types-flow": "0.0.7", - "commander": "2.15.1" + "commander": "^2.11.0" } }, "arr-diff": { @@ -392,8 +392,8 @@ "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, "array-union": { @@ -402,7 +402,7 @@ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { - "array-uniq": "1.0.3" + "array-uniq": "^1.0.1" } }, "array-uniq": { @@ -423,8 +423,8 @@ "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, "array.prototype.flat": { @@ -433,9 +433,9 @@ "integrity": "sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0", - "function-bind": "1.1.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.10.0", + "function-bind": "^1.1.1" } }, "arrify": { @@ -499,7 +499,7 @@ "resolved": "https://registry.npmjs.org/atom-babel6-transpiler/-/atom-babel6-transpiler-1.2.0.tgz", "integrity": "sha512-lZucrjVyRtPAPPJxvICCEBsAC1qn48wUHaIlieriWCXTXLqtLC2PvkQU7vNvU2w1eZ7tw9m0lojZ8PbpVyWTvg==", "requires": { - "babel-core": "6.26.3" + "babel-core": "6.x" } }, "aws-sign2": { @@ -526,9 +526,9 @@ "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" } }, "babel-core": { @@ -536,25 +536,25 @@ "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", "integrity": "sha1-suLwnjQtDwyI4vAuBneUEl51wgc=", "requires": { - "babel-code-frame": "6.26.0", - "babel-generator": "6.26.1", - "babel-helpers": "6.24.1", - "babel-messages": "6.23.0", - "babel-register": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "convert-source-map": "1.5.1", - "debug": "2.6.9", - "json5": "0.5.1", - "lodash": "4.17.5", - "minimatch": "3.0.4", - "path-is-absolute": "1.0.1", - "private": "0.1.8", - "slash": "1.0.0", - "source-map": "0.5.7" + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" }, "dependencies": { "babel-runtime": { @@ -562,8 +562,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-template": { @@ -571,11 +571,11 @@ "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.5" + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" } }, "babel-traverse": { @@ -583,15 +583,15 @@ "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.5" + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" } }, "babel-types": { @@ -599,10 +599,10 @@ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "babylon": { @@ -628,10 +628,10 @@ "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0", - "babylon": "6.17.4" + "babel-code-frame": "^6.22.0", + "babel-traverse": "^6.23.1", + "babel-types": "^6.23.0", + "babylon": "^6.17.0" } }, "babel-generator": { @@ -639,14 +639,14 @@ "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", "integrity": "sha1-GERAjTuPDTWkBOp6wYDwh6YBvZA=", "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.5", - "source-map": "0.5.7", - "trim-right": "1.0.1" + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" }, "dependencies": { "babel-runtime": { @@ -654,8 +654,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-types": { @@ -663,10 +663,10 @@ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "regenerator-runtime": { @@ -686,9 +686,9 @@ "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", "requires": { - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "esutils": "2.0.2" + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "esutils": "^2.0.2" }, "dependencies": { "babel-runtime": { @@ -696,8 +696,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-types": { @@ -705,10 +705,10 @@ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "regenerator-runtime": { @@ -729,10 +729,10 @@ "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", "dev": true, "requires": { - "babel-helper-hoist-variables": "6.24.1", - "babel-runtime": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-define-map": { @@ -741,10 +741,10 @@ "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.5" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" }, "dependencies": { "babel-runtime": { @@ -753,8 +753,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-types": { @@ -763,10 +763,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "regenerator-runtime": { @@ -788,11 +788,11 @@ "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", "requires": { - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helper-get-function-arity": { @@ -800,8 +800,8 @@ "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-hoist-variables": { @@ -810,8 +810,8 @@ "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-optimise-call-expression": { @@ -820,8 +820,8 @@ "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-helper-replace-supers": { @@ -830,12 +830,12 @@ "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", "dev": true, "requires": { - "babel-helper-optimise-call-expression": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-helpers": { @@ -843,8 +843,8 @@ "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", "requires": { - "babel-runtime": "6.25.0", - "babel-template": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-messages": { @@ -852,7 +852,7 @@ "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-chai-assert-async": { @@ -866,7 +866,7 @@ "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-istanbul": { @@ -875,10 +875,10 @@ "integrity": "sha1-NsWbIZLvzoHFs3gyG3QXWt0cmkU=", "dev": true, "requires": { - "babel-plugin-syntax-object-rest-spread": "6.13.0", - "find-up": "2.1.0", - "istanbul-lib-instrument": "1.10.1", - "test-exclude": "4.2.1" + "babel-plugin-syntax-object-rest-spread": "^6.13.0", + "find-up": "^2.1.0", + "istanbul-lib-instrument": "^1.10.1", + "test-exclude": "^4.2.1" }, "dependencies": { "babylon": { @@ -899,13 +899,13 @@ "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", "dev": true, "requires": { - "babel-generator": "6.26.1", - "babel-template": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0", - "babylon": "6.18.0", - "istanbul-lib-coverage": "1.2.0", - "semver": "5.5.1" + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.0", + "semver": "^5.3.0" } } } @@ -915,8 +915,8 @@ "resolved": "https://registry.npmjs.org/babel-plugin-relay/-/babel-plugin-relay-1.6.0.tgz", "integrity": "sha1-oiTaUkNi1pA6UkIUobhAUw/fvSg=", "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.23.0", + "babel-types": "^6.24.1" } }, "babel-plugin-syntax-class-properties": { @@ -950,10 +950,10 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", "requires": { - "babel-helper-function-name": "6.24.1", - "babel-plugin-syntax-class-properties": "6.13.0", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0" + "babel-helper-function-name": "^6.24.1", + "babel-plugin-syntax-class-properties": "^6.8.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-arrow-functions": { @@ -962,7 +962,7 @@ "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-block-scoped-functions": { @@ -971,7 +971,7 @@ "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-block-scoping": { @@ -980,11 +980,11 @@ "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "lodash": "4.17.5" + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" }, "dependencies": { "babel-runtime": { @@ -993,8 +993,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-template": { @@ -1003,11 +1003,11 @@ "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.5" + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" } }, "babel-traverse": { @@ -1016,15 +1016,15 @@ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.5" + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" } }, "babel-types": { @@ -1033,10 +1033,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "babylon": { @@ -1065,15 +1065,15 @@ "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", "dev": true, "requires": { - "babel-helper-define-map": "6.26.0", - "babel-helper-function-name": "6.24.1", - "babel-helper-optimise-call-expression": "6.24.1", - "babel-helper-replace-supers": "6.24.1", - "babel-messages": "6.23.0", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-computed-properties": { @@ -1082,8 +1082,8 @@ "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-template": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-plugin-transform-es2015-destructuring": { @@ -1092,7 +1092,7 @@ "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-for-of": { @@ -1101,7 +1101,7 @@ "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-function-name": { @@ -1110,9 +1110,9 @@ "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", "dev": true, "requires": { - "babel-helper-function-name": "6.24.1", - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-literals": { @@ -1121,7 +1121,7 @@ "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-modules-commonjs": { @@ -1129,10 +1129,10 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", "integrity": "sha1-WKeThjqefKhwvcWogRF/+sJ9tvM=", "requires": { - "babel-plugin-transform-strict-mode": "6.24.1", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-types": "6.26.0" + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" }, "dependencies": { "babel-runtime": { @@ -1140,8 +1140,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "babel-template": { @@ -1149,11 +1149,11 @@ "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.5" + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" } }, "babel-traverse": { @@ -1161,15 +1161,15 @@ "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.5" + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" } }, "babel-types": { @@ -1177,10 +1177,10 @@ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "babylon": { @@ -1206,8 +1206,8 @@ "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", "dev": true, "requires": { - "babel-helper-replace-supers": "6.24.1", - "babel-runtime": "6.25.0" + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-parameters": { @@ -1216,12 +1216,12 @@ "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", "dev": true, "requires": { - "babel-helper-call-delegate": "6.24.1", - "babel-helper-get-function-arity": "6.24.1", - "babel-runtime": "6.25.0", - "babel-template": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0" + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-shorthand-properties": { @@ -1230,8 +1230,8 @@ "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", "dev": true, "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-plugin-transform-es2015-spread": { @@ -1240,7 +1240,7 @@ "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es2015-template-literals": { @@ -1249,7 +1249,7 @@ "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es3-member-expression-literals": { @@ -1258,7 +1258,7 @@ "integrity": "sha1-cz00RPPsxBvvjtGmpOCWV7iWnrs=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-es3-property-literals": { @@ -1267,7 +1267,7 @@ "integrity": "sha1-sgeNWELiKr9A9z6M3pzTcRq9V1g=", "dev": true, "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-flow-strip-types": { @@ -1275,8 +1275,8 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", "requires": { - "babel-plugin-syntax-flow": "6.18.0", - "babel-runtime": "6.25.0" + "babel-plugin-syntax-flow": "^6.18.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-object-rest-spread": { @@ -1284,8 +1284,8 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", "requires": { - "babel-plugin-syntax-object-rest-spread": "6.13.0", - "babel-runtime": "6.26.0" + "babel-plugin-syntax-object-rest-spread": "^6.8.0", + "babel-runtime": "^6.26.0" }, "dependencies": { "babel-runtime": { @@ -1293,8 +1293,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "regenerator-runtime": { @@ -1309,7 +1309,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", "requires": { - "babel-runtime": "6.25.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-react-jsx": { @@ -1317,9 +1317,9 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", "requires": { - "babel-helper-builder-react-jsx": "6.26.0", - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.25.0" + "babel-helper-builder-react-jsx": "^6.24.1", + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-react-jsx-self": { @@ -1327,8 +1327,8 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.25.0" + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-react-jsx-source": { @@ -1336,8 +1336,8 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-runtime": "6.25.0" + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" } }, "babel-plugin-transform-strict-mode": { @@ -1345,8 +1345,8 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", "requires": { - "babel-runtime": "6.25.0", - "babel-types": "6.25.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, "babel-polyfill": { @@ -1355,9 +1355,9 @@ "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "core-js": "2.5.0", - "regenerator-runtime": "0.10.5" + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" }, "dependencies": { "babel-runtime": { @@ -1366,8 +1366,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" }, "dependencies": { "regenerator-runtime": { @@ -1386,34 +1386,34 @@ "integrity": "sha512-jj0KFJDioYZMtPtZf77dQuU+Ad/1BtN0UnAYlHDa8J8f4tGXr3YrPoJImD5MdueaOPeN/jUdrCgu330EfXr0XQ==", "dev": true, "requires": { - "babel-plugin-check-es2015-constants": "6.22.0", - "babel-plugin-syntax-class-properties": "6.13.0", - "babel-plugin-syntax-flow": "6.18.0", - "babel-plugin-syntax-jsx": "6.18.0", - "babel-plugin-syntax-object-rest-spread": "6.13.0", - "babel-plugin-syntax-trailing-function-commas": "6.22.0", - "babel-plugin-transform-class-properties": "6.24.1", - "babel-plugin-transform-es2015-arrow-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", - "babel-plugin-transform-es2015-block-scoping": "6.26.0", - "babel-plugin-transform-es2015-classes": "6.24.1", - "babel-plugin-transform-es2015-computed-properties": "6.24.1", - "babel-plugin-transform-es2015-destructuring": "6.23.0", - "babel-plugin-transform-es2015-for-of": "6.23.0", - "babel-plugin-transform-es2015-function-name": "6.24.1", - "babel-plugin-transform-es2015-literals": "6.22.0", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", - "babel-plugin-transform-es2015-object-super": "6.24.1", - "babel-plugin-transform-es2015-parameters": "6.24.1", - "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", - "babel-plugin-transform-es2015-spread": "6.22.0", - "babel-plugin-transform-es2015-template-literals": "6.22.0", - "babel-plugin-transform-es3-member-expression-literals": "6.22.0", - "babel-plugin-transform-es3-property-literals": "6.22.0", - "babel-plugin-transform-flow-strip-types": "6.22.0", - "babel-plugin-transform-object-rest-spread": "6.26.0", - "babel-plugin-transform-react-display-name": "6.25.0", - "babel-plugin-transform-react-jsx": "6.24.1" + "babel-plugin-check-es2015-constants": "^6.8.0", + "babel-plugin-syntax-class-properties": "^6.8.0", + "babel-plugin-syntax-flow": "^6.8.0", + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-plugin-syntax-object-rest-spread": "^6.8.0", + "babel-plugin-syntax-trailing-function-commas": "^6.8.0", + "babel-plugin-transform-class-properties": "^6.8.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.8.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.8.0", + "babel-plugin-transform-es2015-block-scoping": "^6.8.0", + "babel-plugin-transform-es2015-classes": "^6.8.0", + "babel-plugin-transform-es2015-computed-properties": "^6.8.0", + "babel-plugin-transform-es2015-destructuring": "^6.8.0", + "babel-plugin-transform-es2015-for-of": "^6.8.0", + "babel-plugin-transform-es2015-function-name": "^6.8.0", + "babel-plugin-transform-es2015-literals": "^6.8.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.8.0", + "babel-plugin-transform-es2015-object-super": "^6.8.0", + "babel-plugin-transform-es2015-parameters": "^6.8.0", + "babel-plugin-transform-es2015-shorthand-properties": "^6.8.0", + "babel-plugin-transform-es2015-spread": "^6.8.0", + "babel-plugin-transform-es2015-template-literals": "^6.8.0", + "babel-plugin-transform-es3-member-expression-literals": "^6.8.0", + "babel-plugin-transform-es3-property-literals": "^6.8.0", + "babel-plugin-transform-flow-strip-types": "^6.8.0", + "babel-plugin-transform-object-rest-spread": "^6.8.0", + "babel-plugin-transform-react-display-name": "^6.8.0", + "babel-plugin-transform-react-jsx": "^6.8.0" } }, "babel-preset-flow": { @@ -1421,7 +1421,7 @@ "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", "requires": { - "babel-plugin-transform-flow-strip-types": "6.22.0" + "babel-plugin-transform-flow-strip-types": "^6.22.0" } }, "babel-preset-react": { @@ -1429,12 +1429,12 @@ "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", "requires": { - "babel-plugin-syntax-jsx": "6.18.0", - "babel-plugin-transform-react-display-name": "6.25.0", - "babel-plugin-transform-react-jsx": "6.24.1", - "babel-plugin-transform-react-jsx-self": "6.22.0", - "babel-plugin-transform-react-jsx-source": "6.22.0", - "babel-preset-flow": "6.23.0" + "babel-plugin-syntax-jsx": "^6.3.13", + "babel-plugin-transform-react-display-name": "^6.23.0", + "babel-plugin-transform-react-jsx": "^6.24.1", + "babel-plugin-transform-react-jsx-self": "^6.22.0", + "babel-plugin-transform-react-jsx-source": "^6.22.0", + "babel-preset-flow": "^6.23.0" } }, "babel-register": { @@ -1442,13 +1442,13 @@ "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "requires": { - "babel-core": "6.26.3", - "babel-runtime": "6.26.0", - "core-js": "2.5.0", - "home-or-tmp": "2.0.0", - "lodash": "4.17.5", - "mkdirp": "0.5.1", - "source-map-support": "0.4.18" + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" }, "dependencies": { "babel-runtime": { @@ -1456,8 +1456,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "regenerator-runtime": { @@ -1472,8 +1472,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=", "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.10.5" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.10.0" } }, "babel-template": { @@ -1481,11 +1481,11 @@ "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=", "requires": { - "babel-runtime": "6.25.0", - "babel-traverse": "6.25.0", - "babel-types": "6.25.0", - "babylon": "6.17.4", - "lodash": "4.17.5" + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.25.0", + "babel-types": "^6.25.0", + "babylon": "^6.17.2", + "lodash": "^4.2.0" } }, "babel-traverse": { @@ -1493,15 +1493,15 @@ "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=", "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.25.0", - "babel-types": "6.25.0", - "babylon": "6.17.4", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.5" + "babel-code-frame": "^6.22.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-types": "^6.25.0", + "babylon": "^6.17.2", + "debug": "^2.2.0", + "globals": "^9.0.0", + "invariant": "^2.2.0", + "lodash": "^4.2.0" } }, "babel-types": { @@ -1509,10 +1509,10 @@ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=", "requires": { - "babel-runtime": "6.25.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.22.0", + "esutils": "^2.0.2", + "lodash": "^4.2.0", + "to-fast-properties": "^1.0.1" }, "dependencies": { "to-fast-properties": { @@ -1538,13 +1538,13 @@ "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", "dev": true, "requires": { - "cache-base": "1.0.1", - "class-utils": "0.3.6", - "component-emitter": "1.2.1", - "define-property": "1.0.0", - "isobject": "3.0.1", - "mixin-deep": "1.3.1", - "pascalcase": "0.1.1" + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" }, "dependencies": { "define-property": { @@ -1553,7 +1553,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -1562,7 +1562,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -1571,7 +1571,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -1580,9 +1580,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -1593,7 +1593,7 @@ "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "bl": { @@ -1601,8 +1601,8 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha1-oWCRFxcQPAdBDO9j71Gzl8Alr5w=", "requires": { - "readable-stream": "2.3.6", - "safe-buffer": "5.1.1" + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" }, "dependencies": { "isarray": { @@ -1615,13 +1615,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -1629,7 +1629,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } } } @@ -1646,7 +1646,7 @@ "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } }, "brace-expansion": { @@ -1654,7 +1654,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -1664,16 +1664,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -1682,7 +1682,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -1705,7 +1705,7 @@ "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", "dev": true, "requires": { - "node-int64": "0.4.0" + "node-int64": "^0.4.0" } }, "buffer-alloc": { @@ -1713,8 +1713,8 @@ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.1.0.tgz", "integrity": "sha1-BVFNM78WVtNUDGhPZbEgLpDsowM=", "requires": { - "buffer-alloc-unsafe": "0.1.1", - "buffer-fill": "0.1.1" + "buffer-alloc-unsafe": "^0.1.0", + "buffer-fill": "^0.1.0" } }, "buffer-alloc-unsafe": { @@ -1744,15 +1744,15 @@ "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", "dev": true, "requires": { - "collection-visit": "1.0.0", - "component-emitter": "1.2.1", - "get-value": "2.0.6", - "has-value": "1.0.0", - "isobject": "3.0.1", - "set-value": "2.0.0", - "to-object-path": "0.3.0", - "union-value": "1.0.0", - "unset-value": "1.0.0" + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" } }, "caching-transform": { @@ -1761,9 +1761,9 @@ "integrity": "sha1-bb2y8g+Nj7znnz6U6dF0Lc31wKE=", "dev": true, "requires": { - "md5-hex": "1.3.0", - "mkdirp": "0.5.1", - "write-file-atomic": "1.3.4" + "md5-hex": "^1.2.0", + "mkdirp": "^0.5.1", + "write-file-atomic": "^1.1.4" } }, "call-me-maybe": { @@ -1778,7 +1778,7 @@ "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, "requires": { - "callsites": "0.2.0" + "callsites": "^0.2.0" } }, "callsites": { @@ -1809,8 +1809,8 @@ "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" } }, "chai": { @@ -1819,12 +1819,12 @@ "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", "dev": true, "requires": { - "assertion-error": "1.1.0", - "check-error": "1.0.2", - "deep-eql": "3.0.1", - "get-func-name": "2.0.0", - "pathval": "1.1.0", - "type-detect": "4.0.8" + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^3.0.0", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "type-detect": "^4.0.0" } }, "chai-as-promised": { @@ -1833,7 +1833,7 @@ "integrity": "sha1-CGRdgl3rhpbuYXJdv1kMAS6wDKA=", "dev": true, "requires": { - "check-error": "1.0.2" + "check-error": "^1.0.2" } }, "chalk": { @@ -1841,11 +1841,11 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "chardet": { @@ -1865,7 +1865,7 @@ "resolved": "https://registry.npmjs.org/checksum/-/checksum-0.1.1.tgz", "integrity": "sha1-3GUn1MkL6FYNvR7Uzs8yl9Uo6ek=", "requires": { - "optimist": "0.3.7" + "optimist": "~0.3.5" } }, "cheerio": { @@ -1874,12 +1874,12 @@ "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", "dev": true, "requires": { - "css-select": "1.2.0", - "dom-serializer": "0.1.0", - "entities": "1.1.1", - "htmlparser2": "3.9.2", - "lodash": "4.17.5", - "parse5": "3.0.3" + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" } }, "chownr": { @@ -1899,10 +1899,10 @@ "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", "dev": true, "requires": { - "arr-union": "3.1.0", - "define-property": "0.2.5", - "isobject": "3.0.1", - "static-extend": "0.1.2" + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" }, "dependencies": { "define-property": { @@ -1911,7 +1911,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -1927,7 +1927,7 @@ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "2.0.0" + "restore-cursor": "^2.0.0" } }, "cli-width": { @@ -1942,9 +1942,9 @@ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" }, "dependencies": { "string-width": { @@ -1953,9 +1953,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -1982,8 +1982,8 @@ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "map-visit": "1.0.0", - "object-visit": "1.0.1" + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" } }, "color-convert": { @@ -1991,7 +1991,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", "integrity": "sha1-wSYRB66y8pTr/+ye2eytUppgl+0=", "requires": { - "color-name": "1.1.3" + "color-name": "^1.1.1" } }, "color-name": { @@ -2010,7 +2010,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { @@ -2073,11 +2073,11 @@ "integrity": "sha1-EuFZFOqikgTlaGml7Oe54UktKuI=", "dev": true, "requires": { - "js-yaml": "3.11.0", - "lcov-parse": "0.0.10", - "log-driver": "1.2.7", - "minimist": "1.2.0", - "request": "2.87.0" + "js-yaml": "^3.6.1", + "lcov-parse": "^0.0.10", + "log-driver": "^1.2.5", + "minimist": "^1.2.0", + "request": "^2.79.0" } }, "cross-env": { @@ -2086,8 +2086,8 @@ "integrity": "sha1-bs1MAV1Xc+YUA57lKQdmabnRJvI=", "dev": true, "requires": { - "cross-spawn": "6.0.5", - "is-windows": "1.0.2" + "cross-spawn": "^6.0.5", + "is-windows": "^1.0.0" }, "dependencies": { "cross-spawn": { @@ -2096,11 +2096,11 @@ "integrity": "sha1-Sl7Hxk364iw6FBJNus3uhG2Ay8Q=", "dev": true, "requires": { - "nice-try": "1.0.4", - "path-key": "2.0.1", - "semver": "5.5.1", - "shebang-command": "1.2.0", - "which": "1.3.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } } } @@ -2111,9 +2111,9 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "4.1.2", - "shebang-command": "1.2.0", - "which": "1.3.0" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" }, "dependencies": { "yallist": { @@ -2134,7 +2134,7 @@ "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", "dev": true, "requires": { - "boom": "5.2.0" + "boom": "5.x.x" }, "dependencies": { "boom": { @@ -2143,7 +2143,7 @@ "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } } } @@ -2154,10 +2154,10 @@ "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { - "boolbase": "1.0.0", - "css-what": "2.1.0", + "boolbase": "~1.0.0", + "css-what": "2.1", "domutils": "1.5.1", - "nth-check": "1.0.1" + "nth-check": "~1.0.1" } }, "css-what": { @@ -2177,7 +2177,7 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "debug": { @@ -2211,7 +2211,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", "requires": { - "mimic-response": "1.0.0" + "mimic-response": "^1.0.0" } }, "dedent-js": { @@ -2226,7 +2226,7 @@ "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=", "dev": true, "requires": { - "type-detect": "4.0.8" + "type-detect": "^4.0.0" } }, "deep-equal": { @@ -2252,7 +2252,7 @@ "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", "dev": true, "requires": { - "strip-bom": "3.0.0" + "strip-bom": "^3.0.0" } }, "define-properties": { @@ -2261,8 +2261,8 @@ "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", "dev": true, "requires": { - "foreach": "2.0.5", - "object-keys": "1.0.11" + "foreach": "^2.0.5", + "object-keys": "^1.0.8" } }, "define-property": { @@ -2271,8 +2271,8 @@ "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", "dev": true, "requires": { - "is-descriptor": "1.0.2", - "isobject": "3.0.1" + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" }, "dependencies": { "is-accessor-descriptor": { @@ -2281,7 +2281,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -2290,7 +2290,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -2299,9 +2299,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -2312,13 +2312,13 @@ "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "dev": true, "requires": { - "globby": "5.0.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.1", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "rimraf": "2.6.2" + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" } }, "delayed-stream": { @@ -2342,7 +2342,7 @@ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, "detect-libc": { @@ -2368,8 +2368,8 @@ "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" + "esutils": "^2.0.2", + "isarray": "^1.0.0" }, "dependencies": { "isarray": { @@ -2386,8 +2386,8 @@ "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", "dev": true, "requires": { - "domelementtype": "1.1.3", - "entities": "1.1.1" + "domelementtype": "~1.1.1", + "entities": "~1.1.1" }, "dependencies": { "domelementtype": { @@ -2416,7 +2416,7 @@ "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", "dev": true, "requires": { - "domelementtype": "1.3.0" + "domelementtype": "1" } }, "domutils": { @@ -2425,8 +2425,8 @@ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", "dev": true, "requires": { - "dom-serializer": "0.1.0", - "domelementtype": "1.3.0" + "dom-serializer": "0", + "domelementtype": "1" } }, "dugite": { @@ -2434,12 +2434,12 @@ "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.66.0.tgz", "integrity": "sha1-X9q2aDwLU4p5vb7Emenz0+pyEPk=", "requires": { - "checksum": "0.1.1", - "mkdirp": "0.5.1", - "progress": "2.0.0", - "request": "2.87.0", - "rimraf": "2.6.2", - "tar": "4.4.4" + "checksum": "^0.1.1", + "mkdirp": "^0.5.1", + "progress": "^2.0.0", + "request": "^2.86.0", + "rimraf": "^2.5.4", + "tar": "^4.0.2" } }, "ecc-jsbn": { @@ -2448,7 +2448,7 @@ "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "electron-devtools-installer": { @@ -2459,8 +2459,8 @@ "requires": { "7zip": "0.0.6", "cross-unzip": "0.0.2", - "rimraf": "2.6.2", - "semver": "5.5.1" + "rimraf": "^2.5.2", + "semver": "^5.3.0" } }, "emoji-regex": { @@ -2474,7 +2474,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { - "iconv-lite": "0.4.18" + "iconv-lite": "~0.4.13" } }, "end-of-stream": { @@ -2482,7 +2482,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", "requires": { - "once": "1.4.0" + "once": "^1.4.0" } }, "entities": { @@ -2497,23 +2497,23 @@ "integrity": "sha512-XBZbyUy36WipNSBVZKIR1sg9iF6zXfkfDEzwTc10T9zhB61UPnMo+c3WE17T/jyhfmPJOz6X073NXXsR7G/1rA==", "dev": true, "requires": { - "array.prototype.flat": "1.2.1", - "cheerio": "1.0.0-rc.2", - "function.prototype.name": "1.1.0", - "has": "1.0.3", - "is-boolean-object": "1.0.0", - "is-callable": "1.1.4", - "is-number-object": "1.0.3", - "is-string": "1.0.4", - "is-subset": "0.1.1", - "lodash": "4.17.5", - "object-inspect": "1.6.0", - "object-is": "1.0.1", - "object.assign": "4.1.0", - "object.entries": "1.0.4", - "object.values": "1.0.4", - "raf": "3.4.0", - "rst-selector-parser": "2.2.3" + "array.prototype.flat": "^1.2.1", + "cheerio": "^1.0.0-rc.2", + "function.prototype.name": "^1.1.0", + "has": "^1.0.3", + "is-boolean-object": "^1.0.0", + "is-callable": "^1.1.4", + "is-number-object": "^1.0.3", + "is-string": "^1.0.4", + "is-subset": "^0.1.1", + "lodash": "^4.17.4", + "object-inspect": "^1.6.0", + "object-is": "^1.0.1", + "object.assign": "^4.1.0", + "object.entries": "^1.0.4", + "object.values": "^1.0.4", + "raf": "^3.4.0", + "rst-selector-parser": "^2.2.3" }, "dependencies": { "has": { @@ -2522,7 +2522,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, "is-callable": { @@ -2540,7 +2540,7 @@ "dev": true, "optional": true, "requires": { - "prr": "1.0.1" + "prr": "~1.0.1" } }, "error": { @@ -2549,9 +2549,9 @@ "integrity": "sha1-v2n/JR+0onnBmtzNqmth6Q2b8So=", "dev": true, "requires": { - "camelize": "1.0.0", - "string-template": "0.2.1", - "xtend": "4.0.1" + "camelize": "^1.0.0", + "string-template": "~0.2.0", + "xtend": "~4.0.0" } }, "error-ex": { @@ -2560,7 +2560,7 @@ "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "es-abstract": { @@ -2569,11 +2569,11 @@ "integrity": "sha1-zOh9UY8Elok7GjDNhGGDVTVIBoE=", "dev": true, "requires": { - "es-to-primitive": "1.1.1", - "function-bind": "1.1.1", - "has": "1.0.1", - "is-callable": "1.1.3", - "is-regex": "1.0.4" + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" } }, "es-to-primitive": { @@ -2582,9 +2582,9 @@ "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", "dev": true, "requires": { - "is-callable": "1.1.3", - "is-date-object": "1.0.1", - "is-symbol": "1.0.1" + "is-callable": "^1.1.1", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.1" } }, "escape-string-regexp": { @@ -2598,44 +2598,44 @@ "integrity": "sha512-D5nG2rErquLUstgUaxJlWB5+gu+U/3VDY0fk/Iuq8y9CUFy/7Y6oF4N2cR1tV8knzQvciIbfqfohd359xTLIKQ==", "dev": true, "requires": { - "ajv": "6.5.2", - "babel-code-frame": "6.26.0", - "chalk": "2.4.1", - "cross-spawn": "6.0.5", - "debug": "3.1.0", - "doctrine": "2.1.0", - "eslint-scope": "4.0.0", - "eslint-visitor-keys": "1.0.0", - "espree": "4.0.0", - "esquery": "1.0.1", - "esutils": "2.0.2", - "file-entry-cache": "2.0.0", - "functional-red-black-tree": "1.0.1", - "glob": "7.1.2", - "globals": "11.7.0", - "ignore": "3.3.10", - "imurmurhash": "0.1.4", - "inquirer": "5.2.0", - "is-resolvable": "1.1.0", - "js-yaml": "3.11.0", - "json-stable-stringify-without-jsonify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.5", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "natural-compare": "1.4.0", - "optionator": "0.8.2", - "path-is-inside": "1.0.2", - "pluralize": "7.0.0", - "progress": "2.0.0", - "regexpp": "1.1.0", - "require-uncached": "1.0.3", - "semver": "5.5.1", - "string.prototype.matchall": "2.0.0", - "strip-ansi": "4.0.0", - "strip-json-comments": "2.0.1", - "table": "4.0.3", - "text-table": "0.2.0" + "ajv": "^6.5.0", + "babel-code-frame": "^6.26.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-visitor-keys": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.5.0", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^5.2.0", + "is-resolvable": "^1.1.0", + "js-yaml": "^3.11.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.1.0", + "require-uncached": "^1.0.3", + "semver": "^5.5.0", + "string.prototype.matchall": "^2.0.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^4.0.3", + "text-table": "^0.2.0" }, "dependencies": { "ajv": { @@ -2644,10 +2644,10 @@ "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", "dev": true, "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.1" } }, "ansi-regex": { @@ -2662,7 +2662,7 @@ "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, "chalk": { @@ -2671,9 +2671,9 @@ "integrity": "sha1-GMSasWoDe26wFSzIPjRxM4IVtm4=", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "cross-spawn": { @@ -2682,11 +2682,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "1.0.4", - "path-key": "2.0.1", - "semver": "5.5.1", - "shebang-command": "1.2.0", - "which": "1.3.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "debug": { @@ -2704,7 +2704,7 @@ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "2.0.2" + "esutils": "^2.0.2" } }, "fast-deep-equal": { @@ -2731,7 +2731,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { @@ -2740,7 +2740,7 @@ "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -2751,13 +2751,13 @@ "integrity": "sha1-n2yIvtUXwGDuqcQ0BqBXMw2whJc=", "dev": true, "requires": { - "babel-eslint": "7.2.3", - "eslint-plugin-babel": "4.1.2", - "eslint-plugin-flowtype": "2.46.3", - "eslint-plugin-jasmine": "2.9.3", - "eslint-plugin-prefer-object-spread": "1.2.1", - "eslint-plugin-react": "6.10.3", - "fbjs-eslint-utils": "1.0.0" + "babel-eslint": "^7.1.1", + "eslint-plugin-babel": "^4.0.1", + "eslint-plugin-flowtype": "^2.30.0", + "eslint-plugin-jasmine": "^2.2.0", + "eslint-plugin-prefer-object-spread": "^1.1.0", + "eslint-plugin-react": "^6.9.0", + "fbjs-eslint-utils": "^1.0.0" } }, "eslint-plugin-babel": { @@ -2772,7 +2772,7 @@ "integrity": "sha1-foQTHYfvGLSWsYEESFkzdIYLTo4=", "dev": true, "requires": { - "lodash": "4.17.5" + "lodash": "^4.15.0" } }, "eslint-plugin-jasmine": { @@ -2787,14 +2787,14 @@ "integrity": "sha512-JsxNKqa3TwmPypeXNnI75FntkUktGzI1wSa1LgNZdSOMI+B4sxnr1lSF8m8lPiz4mKiC+14ysZQM4scewUrP7A==", "dev": true, "requires": { - "aria-query": "3.0.0", - "array-includes": "3.0.3", - "ast-types-flow": "0.0.7", - "axobject-query": "2.0.1", - "damerau-levenshtein": "1.0.4", - "emoji-regex": "6.5.1", - "has": "1.0.3", - "jsx-ast-utils": "2.0.1" + "aria-query": "^3.0.0", + "array-includes": "^3.0.3", + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.1", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^6.5.1", + "has": "^1.0.3", + "jsx-ast-utils": "^2.0.1" }, "dependencies": { "has": { @@ -2803,7 +2803,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, "jsx-ast-utils": { @@ -2812,7 +2812,7 @@ "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", "dev": true, "requires": { - "array-includes": "3.0.3" + "array-includes": "^3.0.3" } } } @@ -2829,11 +2829,11 @@ "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=", "dev": true, "requires": { - "array.prototype.find": "2.0.4", - "doctrine": "1.5.0", - "has": "1.0.1", - "jsx-ast-utils": "1.4.1", - "object.assign": "4.1.0" + "array.prototype.find": "^2.0.1", + "doctrine": "^1.2.2", + "has": "^1.0.1", + "jsx-ast-utils": "^1.3.4", + "object.assign": "^4.0.4" } }, "eslint-scope": { @@ -2842,8 +2842,8 @@ "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { - "esrecurse": "4.2.1", - "estraverse": "4.2.0" + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, "eslint-visitor-keys": { @@ -2858,8 +2858,8 @@ "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", "dev": true, "requires": { - "acorn": "5.7.1", - "acorn-jsx": "4.1.1" + "acorn": "^5.6.0", + "acorn-jsx": "^4.1.1" } }, "esprima": { @@ -2874,7 +2874,7 @@ "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.0.0" } }, "esrecurse": { @@ -2883,7 +2883,7 @@ "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.1.0" } }, "estraverse": { @@ -2903,7 +2903,7 @@ "integrity": "sha1-VPYZV0NG+KPueXP1T7vQG1YnItY=", "dev": true, "requires": { - "virtual-dom": "2.1.1" + "virtual-dom": "^2.0.1" } }, "ev-store": { @@ -2912,7 +2912,7 @@ "integrity": "sha1-GrDH+CE2UF3XSzHRdwHLK+bSZVg=", "dev": true, "requires": { - "individual": "3.0.0" + "individual": "^3.0.0" } }, "event-kit": { @@ -2926,13 +2926,13 @@ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, "expand-brackets": { @@ -2941,13 +2941,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -2956,7 +2956,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -2965,7 +2965,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -2986,8 +2986,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "1.0.0", - "is-extendable": "1.0.1" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -2996,7 +2996,7 @@ "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "dev": true, "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -3007,9 +3007,9 @@ "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { - "chardet": "0.4.2", - "iconv-lite": "0.4.18", - "tmp": "0.0.33" + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" }, "dependencies": { "tmp": { @@ -3018,7 +3018,7 @@ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.2" } } } @@ -3029,14 +3029,14 @@ "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", "dev": true, "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "define-property": { @@ -3045,7 +3045,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "extend-shallow": { @@ -3054,7 +3054,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "is-accessor-descriptor": { @@ -3063,7 +3063,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -3072,7 +3072,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -3081,9 +3081,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -3104,12 +3104,12 @@ "integrity": "sha512-TR6zxCKftDQnUAPvkrCWdBgDq/gbqx8A3ApnBrR5rMvpp6+KMJI0Igw7fkWPgeVK0uhRXTXdvO3O+YP0CaUX2g==", "dev": true, "requires": { - "@mrmlnc/readdir-enhanced": "2.2.1", - "@nodelib/fs.stat": "1.1.1", - "glob-parent": "3.1.0", - "is-glob": "4.0.0", - "merge2": "1.2.2", - "micromatch": "3.1.10" + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.0.1", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.1", + "micromatch": "^3.1.10" } }, "fast-json-stable-stringify": { @@ -3129,7 +3129,7 @@ "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", "dev": true, "requires": { - "bser": "2.0.0" + "bser": "^2.0.0" } }, "fbjs": { @@ -3137,13 +3137,13 @@ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", "requires": { - "core-js": "1.2.7", - "isomorphic-fetch": "2.2.1", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "promise": "7.3.1", - "setimmediate": "1.0.5", - "ua-parser-js": "0.7.14" + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.9" }, "dependencies": { "core-js": { @@ -3156,8 +3156,8 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" + "encoding": "^0.1.11", + "is-stream": "^1.0.1" } } } @@ -3174,7 +3174,7 @@ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { - "escape-string-regexp": "1.0.5" + "escape-string-regexp": "^1.0.5" } }, "file-entry-cache": { @@ -3183,8 +3183,8 @@ "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, "requires": { - "flat-cache": "1.3.0", - "object-assign": "4.1.1" + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" } }, "fill-range": { @@ -3193,10 +3193,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" }, "dependencies": { "extend-shallow": { @@ -3205,7 +3205,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -3216,9 +3216,9 @@ "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", "dev": true, "requires": { - "commondir": "1.0.1", - "make-dir": "1.3.0", - "pkg-dir": "2.0.0" + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" } }, "find-up": { @@ -3227,7 +3227,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } }, "flat-cache": { @@ -3236,10 +3236,10 @@ "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", "dev": true, "requires": { - "circular-json": "0.3.3", - "del": "2.2.2", - "graceful-fs": "4.1.11", - "write": "0.2.1" + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" } }, "for-in": { @@ -3260,8 +3260,8 @@ "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", "dev": true, "requires": { - "cross-spawn": "4.0.2", - "signal-exit": "3.0.2" + "cross-spawn": "^4", + "signal-exit": "^3.0.0" }, "dependencies": { "cross-spawn": { @@ -3270,8 +3270,8 @@ "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", "dev": true, "requires": { - "lru-cache": "4.1.2", - "which": "1.3.0" + "lru-cache": "^4.0.1", + "which": "^1.2.9" } }, "yallist": { @@ -3290,9 +3290,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { - "asynckit": "0.4.0", + "asynckit": "^0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.16" + "mime-types": "^2.1.12" } }, "fragment-cache": { @@ -3301,7 +3301,7 @@ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "map-cache": "0.2.2" + "map-cache": "^0.2.2" } }, "fs-constants": { @@ -3314,9 +3314,9 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "fs-minipass": { @@ -3324,7 +3324,7 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", "integrity": "sha1-BsJ3IYRU7CiN93raVKA7hwKqy50=", "requires": { - "minipass": "2.3.3" + "minipass": "^2.2.1" } }, "fs.realpath": { @@ -3344,9 +3344,9 @@ "integrity": "sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==", "dev": true, "requires": { - "define-properties": "1.1.2", - "function-bind": "1.1.1", - "is-callable": "1.1.3" + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "is-callable": "^1.1.3" } }, "functional-red-black-tree": { @@ -3360,14 +3360,14 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" }, "dependencies": { "string-width": { @@ -3375,9 +3375,9 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -3411,7 +3411,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "github-from-package": { @@ -3424,12 +3424,12 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "glob-parent": { @@ -3438,8 +3438,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { "is-glob": { @@ -3448,7 +3448,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.0" } } } @@ -3465,8 +3465,8 @@ "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", "dev": true, "requires": { - "min-document": "2.19.0", - "process": "0.5.2" + "min-document": "^2.19.0", + "process": "~0.5.1" } }, "globals": { @@ -3480,12 +3480,12 @@ "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { - "array-union": "1.0.2", - "arrify": "1.0.1", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "graceful-fs": { @@ -3498,7 +3498,7 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.13.2.tgz", "integrity": "sha1-THQK48Iigj5wBAlvgy57k7IQgnA=", "requires": { - "iterall": "1.2.2" + "iterall": "^1.2.1" } }, "graphql-compiler": { @@ -3507,9 +3507,9 @@ "integrity": "sha512-ZpnZm6ijwfphsnJpUvWd/M4taRR0xy5wYf1JR9LC29IGobORjAhXtEng41hYL4hAiSIpqep8zcac1yGCknaVMg==", "dev": true, "requires": { - "chalk": "1.1.3", - "fb-watchman": "2.0.0", - "immutable": "3.7.6" + "chalk": "^1.1.1", + "fb-watchman": "^2.0.0", + "immutable": "~3.7.6" } }, "grim": { @@ -3518,7 +3518,7 @@ "integrity": "sha1-52CinKe4NDsMH/r2ziDyGkbuiu0=", "dev": true, "requires": { - "event-kit": "2.5.1" + "event-kit": "^2.0.0" } }, "growl": { @@ -3533,10 +3533,10 @@ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", "dev": true, "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" }, "dependencies": { "minimist": { @@ -3551,8 +3551,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "0.0.10", - "wordwrap": "0.0.3" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" } }, "source-map": { @@ -3561,7 +3561,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } @@ -3576,8 +3576,8 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.1.0", + "har-schema": "^2.0.0" } }, "has": { @@ -3586,7 +3586,7 @@ "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.0.2" } }, "has-ansi": { @@ -3594,7 +3594,7 @@ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "has-flag": { @@ -3619,9 +3619,9 @@ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { - "get-value": "2.0.6", - "has-values": "1.0.0", - "isobject": "3.0.1" + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" } }, "has-values": { @@ -3630,8 +3630,8 @@ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { "kind-of": { @@ -3640,7 +3640,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -3651,10 +3651,10 @@ "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", "dev": true, "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.1", - "sntp": "2.1.0" + "boom": "4.x.x", + "cryptiles": "3.x.x", + "hoek": "4.x.x", + "sntp": "2.x.x" } }, "he": { @@ -3684,8 +3684,8 @@ "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" } }, "hosted-git-info": { @@ -3700,12 +3700,12 @@ "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", "dev": true, "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.4.2", - "domutils": "1.5.1", - "entities": "1.1.1", - "inherits": "2.0.3", - "readable-stream": "2.3.3" + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" } }, "http-signature": { @@ -3713,9 +3713,9 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "iconv-lite": { @@ -3759,8 +3759,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -3779,19 +3779,19 @@ "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", "dev": true, "requires": { - "ansi-escapes": "3.1.0", - "chalk": "2.4.1", - "cli-cursor": "2.1.0", - "cli-width": "2.2.0", - "external-editor": "2.2.0", - "figures": "2.0.0", - "lodash": "4.17.5", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.1.0", + "figures": "^2.0.0", + "lodash": "^4.3.0", "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rxjs": "5.5.11", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" + "run-async": "^2.2.0", + "rxjs": "^5.5.2", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" }, "dependencies": { "ansi-regex": { @@ -3806,7 +3806,7 @@ "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, "chalk": { @@ -3815,9 +3815,9 @@ "integrity": "sha1-GMSasWoDe26wFSzIPjRxM4IVtm4=", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "strip-ansi": { @@ -3826,7 +3826,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { @@ -3835,7 +3835,7 @@ "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -3845,7 +3845,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } }, "invert-kv": { @@ -3860,7 +3860,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -3869,7 +3869,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -3897,7 +3897,7 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" } }, "is-callable": { @@ -3912,7 +3912,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -3921,7 +3921,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -3938,9 +3938,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" }, "dependencies": { "kind-of": { @@ -3968,7 +3968,7 @@ "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-fullwidth-code-point": { @@ -3976,7 +3976,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-glob": { @@ -3985,7 +3985,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "2.1.1" + "is-extglob": "^2.1.1" } }, "is-number": { @@ -3994,7 +3994,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -4003,7 +4003,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -4026,7 +4026,7 @@ "integrity": "sha1-dkZiRnH9fqVYzNmieVGC8pWPGyQ=", "dev": true, "requires": { - "is-number": "4.0.0" + "is-number": "^4.0.0" }, "dependencies": { "is-number": { @@ -4049,7 +4049,7 @@ "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { - "is-path-inside": "1.0.1" + "is-path-inside": "^1.0.0" } }, "is-path-inside": { @@ -4058,7 +4058,7 @@ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { - "path-is-inside": "1.0.2" + "path-is-inside": "^1.0.1" } }, "is-plain-object": { @@ -4066,7 +4066,7 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" } }, "is-promise": { @@ -4081,7 +4081,7 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "1.0.1" + "has": "^1.0.1" } }, "is-resolvable": { @@ -4151,8 +4151,8 @@ "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", "requires": { - "node-fetch": "1.7.3", - "whatwg-fetch": "2.0.4" + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" }, "dependencies": { "node-fetch": { @@ -4160,8 +4160,8 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" + "encoding": "^0.1.11", + "is-stream": "^1.0.1" } } } @@ -4183,7 +4183,7 @@ "integrity": "sha512-qm3dt628HKpCVtIjbdZLuQyXn0+LO8qz+YHQDfkeXuSk5D+p299SEV5DrnUUnPi2SXvdMmWapMYWiuE75o2rUQ==", "dev": true, "requires": { - "append-transform": "1.0.0" + "append-transform": "^1.0.0" } }, "istanbul-lib-instrument": { @@ -4197,8 +4197,8 @@ "@babel/template": "7.0.0-beta.49", "@babel/traverse": "7.0.0-beta.49", "@babel/types": "7.0.0-beta.49", - "istanbul-lib-coverage": "2.0.0", - "semver": "5.5.1" + "istanbul-lib-coverage": "^2.0.0", + "semver": "^5.5.0" }, "dependencies": { "ansi-styles": { @@ -4206,7 +4206,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, "chalk": { @@ -4214,9 +4214,9 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "debug": { @@ -4242,7 +4242,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -4253,9 +4253,9 @@ "integrity": "sha512-RiELmy9oIRYUv36ITOAhVum9PUvuj6bjyXVEKEHNiD1me6qXtxfx7vSEJWnjOGk2QmYw/GRFjLXWJv3qHpLceQ==", "dev": true, "requires": { - "istanbul-lib-coverage": "2.0.0", - "make-dir": "1.3.0", - "supports-color": "5.4.0" + "istanbul-lib-coverage": "^2.0.0", + "make-dir": "^1.3.0", + "supports-color": "^5.4.0" }, "dependencies": { "supports-color": { @@ -4264,7 +4264,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -4275,11 +4275,11 @@ "integrity": "sha512-jenUeC0gMSSMGkvqD9xuNfs3nD7XWeXLhqaIkqHsNZ3DJBWPdlKEydE7Ya5aTgdWjrEQhrCYTv+J606cGC2vuQ==", "dev": true, "requires": { - "debug": "3.1.0", - "istanbul-lib-coverage": "2.0.0", - "make-dir": "1.3.0", - "rimraf": "2.6.2", - "source-map": "0.6.1" + "debug": "^3.1.0", + "istanbul-lib-coverage": "^2.0.0", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" }, "dependencies": { "debug": { @@ -4305,7 +4305,7 @@ "integrity": "sha512-HeZG0WHretI9FXBni5wZ9DOgNziqDCEwetxnme5k1Vv5e81uTqcsy3fMH99gXGDGKr1ea87TyGseDMa2h4HEUA==", "dev": true, "requires": { - "handlebars": "4.0.11" + "handlebars": "^4.0.11" } }, "iterall": { @@ -4324,8 +4324,8 @@ "integrity": "sha1-WXwai9VxUvJtYizkEXhRpR9euu8=", "dev": true, "requires": { - "argparse": "1.0.10", - "esprima": "4.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "jsbn": { @@ -4370,7 +4370,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } }, "jsprim": { @@ -4402,7 +4402,7 @@ "integrity": "sha1-igamV3/fY3PgqmsRInfmPex3/RI=", "requires": { "nan": "2.8.0", - "prebuild-install": "2.5.3" + "prebuild-install": "^2.4.1" } }, "kind-of": { @@ -4422,7 +4422,7 @@ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "lcov-parse": { @@ -4437,15 +4437,15 @@ "integrity": "sha1-8xdYWY71oZMN1MrvqeQ0BkHnHh0=", "dev": true, "requires": { - "clone": "2.1.2", - "errno": "0.1.7", - "graceful-fs": "4.1.11", - "image-size": "0.5.5", - "mime": "1.6.0", - "mkdirp": "0.5.1", - "promise": "7.3.1", - "request": "2.87.0", - "source-map": "0.6.1" + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.4.1", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0" }, "dependencies": { "source-map": { @@ -4463,8 +4463,8 @@ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, "load-json-file": { @@ -4473,10 +4473,10 @@ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" } }, "locate-path": { @@ -4485,8 +4485,8 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" } }, "lodash": { @@ -4544,7 +4544,7 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "requires": { - "js-tokens": "3.0.2" + "js-tokens": "^3.0.0" } }, "lru-cache": { @@ -4552,8 +4552,8 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" }, "dependencies": { "yallist": { @@ -4569,7 +4569,7 @@ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" }, "dependencies": { "pify": { @@ -4592,7 +4592,7 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "1.0.1" + "object-visit": "^1.0.0" } }, "md5-hex": { @@ -4601,7 +4601,7 @@ "integrity": "sha1-0sSv6YPENwZiF5uMrRRSGRNQRsQ=", "dev": true, "requires": { - "md5-o-matic": "0.1.1" + "md5-o-matic": "^0.1.1" } }, "md5-o-matic": { @@ -4616,7 +4616,7 @@ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "merge-source-map": { @@ -4625,7 +4625,7 @@ "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", "dev": true, "requires": { - "source-map": "0.6.1" + "source-map": "^0.6.1" }, "dependencies": { "source-map": { @@ -4648,19 +4648,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.9", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "mime": { @@ -4680,7 +4680,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz", "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", "requires": { - "mime-db": "1.29.0" + "mime-db": "~1.29.0" } }, "mimic-fn": { @@ -4700,7 +4700,7 @@ "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", "dev": true, "requires": { - "dom-walk": "0.1.1" + "dom-walk": "^0.1.0" } }, "minimatch": { @@ -4708,7 +4708,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { - "brace-expansion": "1.1.8" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -4721,8 +4721,8 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.3.tgz", "integrity": "sha512-/jAn9/tEX4gnpyRATxgHEOV6xbcyxgT7iUnxo9Y3+OB0zX00TgKIv/2FZCf5brBbICcwbLqVv2ImjvWWrQMSYw==", "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.2" + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" }, "dependencies": { "safe-buffer": { @@ -4737,7 +4737,7 @@ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", "integrity": "sha1-EeE2WM5GvDpwomeqxYNZ0eDCnOs=", "requires": { - "minipass": "2.3.3" + "minipass": "^2.2.1" } }, "mixin-deep": { @@ -4746,8 +4746,8 @@ "integrity": "sha1-pJ5yaNzhoNlpjkUybFYm3zVD0P4=", "dev": true, "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -4756,7 +4756,7 @@ "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "dev": true, "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -4810,7 +4810,7 @@ "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -4821,7 +4821,7 @@ "integrity": "sha512-3FOlmBP3DemyvmboFK2vMoPRAJ/7Co8gyvcdxctYMynofouRQAVFwpgO/f/mRCLCMn7GLEq7/sOyuj2HXave/g==", "dev": true, "requires": { - "request-json": "0.6.3" + "request-json": "^0.6.3" } }, "mocha-multi-reporters": { @@ -4830,8 +4830,8 @@ "integrity": "sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI=", "dev": true, "requires": { - "debug": "3.1.0", - "lodash": "4.17.5" + "debug": "^3.1.0", + "lodash": "^4.16.4" }, "dependencies": { "debug": { @@ -4884,18 +4884,18 @@ "integrity": "sha1-h59xUMstq3pHElkGbBBO7m4Pp8I=", "dev": true, "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "fragment-cache": "0.2.1", - "is-odd": "2.0.0", - "is-windows": "1.0.2", - "kind-of": "6.0.2", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-odd": "^2.0.0", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { "is-extendable": { @@ -4903,7 +4903,7 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -4920,11 +4920,11 @@ "integrity": "sha512-8IUY/rUrKz2mIynUGh8k+tul1awMKEjeHHC5G3FHvvyAW6oq4mQfNp2c0BMea+sYZJvYcrrM6GmZVIle/GRXGw==", "dev": true, "requires": { - "moo": "0.4.3", - "nomnom": "1.6.2", - "railroad-diagrams": "1.0.0", + "moo": "^0.4.3", + "nomnom": "~1.6.2", + "railroad-diagrams": "^1.0.0", "randexp": "0.4.6", - "semver": "5.5.1" + "semver": "^5.4.1" } }, "next-tick": { @@ -4945,11 +4945,11 @@ "integrity": "sha512-BxH/DxoQYYdhKgVAfqVy4pzXRZELHOIewzoesxpjYvpU+7YOalQhGNPf7wAx8pLrTNPrHRDlLOkAl8UI0ZpXjw==", "dev": true, "requires": { - "@sinonjs/formatio": "2.0.0", - "just-extend": "1.1.27", - "lolex": "2.7.1", - "path-to-regexp": "1.7.0", - "text-encoding": "0.6.4" + "@sinonjs/formatio": "^2.0.0", + "just-extend": "^1.1.27", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0", + "text-encoding": "^0.6.4" } }, "node-abi": { @@ -4957,7 +4957,7 @@ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.0.tgz", "integrity": "sha1-PCdRXLhC9bvBMqMSVPnx4cVce4M=", "requires": { - "semver": "5.5.1" + "semver": "^5.4.1" } }, "node-emoji": { @@ -4965,7 +4965,7 @@ "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.8.1.tgz", "integrity": "sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg==", "requires": { - "lodash.toarray": "4.4.0" + "lodash.toarray": "^4.4.0" } }, "node-fetch": { @@ -4986,8 +4986,8 @@ "integrity": "sha1-hKZqJgF0QI/Ft3oY+IjszET7aXE=", "dev": true, "requires": { - "colors": "0.5.1", - "underscore": "1.4.4" + "colors": "0.5.x", + "underscore": "~1.4.4" } }, "noop-logger": { @@ -5001,10 +5001,10 @@ "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", "dev": true, "requires": { - "hosted-git-info": "2.6.0", - "is-builtin-module": "1.0.0", - "semver": "5.5.1", - "validate-npm-package-license": "3.0.3" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "npm-run-path": { @@ -5013,7 +5013,7 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "2.0.1" + "path-key": "^2.0.0" } }, "npmlog": { @@ -5021,10 +5021,10 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "nth-check": { @@ -5033,7 +5033,7 @@ "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", "dev": true, "requires": { - "boolbase": "1.0.0" + "boolbase": "~1.0.0" } }, "number-is-nan": { @@ -5047,31 +5047,31 @@ "integrity": "sha1-4Awm6b0zq16B7emSu+STCEhYmbY=", "dev": true, "requires": { - "archy": "1.0.0", - "arrify": "1.0.1", - "caching-transform": "1.0.1", - "convert-source-map": "1.5.1", - "debug-log": "1.0.1", - "find-cache-dir": "1.0.0", - "find-up": "2.1.0", - "foreground-child": "1.5.6", - "glob": "7.1.2", - "istanbul-lib-coverage": "2.0.0", - "istanbul-lib-hook": "2.0.0", - "istanbul-lib-instrument": "2.2.0", - "istanbul-lib-report": "2.0.0", - "istanbul-lib-source-maps": "2.0.0", - "istanbul-reports": "1.5.0", - "make-dir": "1.3.0", - "md5-hex": "2.0.0", - "merge-source-map": "1.1.0", - "resolve-from": "4.0.0", - "rimraf": "2.6.2", - "signal-exit": "3.0.2", - "spawn-wrap": "1.4.2", - "test-exclude": "4.2.2", + "archy": "^1.0.0", + "arrify": "^1.0.1", + "caching-transform": "^1.0.1", + "convert-source-map": "^1.5.1", + "debug-log": "^1.0.1", + "find-cache-dir": "^1.0.0", + "find-up": "^2.1.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.2", + "istanbul-lib-coverage": "^2.0.0", + "istanbul-lib-hook": "^2.0.0", + "istanbul-lib-instrument": "^2.2.0", + "istanbul-lib-report": "^2.0.0", + "istanbul-lib-source-maps": "^2.0.0", + "istanbul-reports": "^1.5.0", + "make-dir": "^1.3.0", + "md5-hex": "^2.0.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^4.2.2", "yargs": "11.1.0", - "yargs-parser": "9.0.2" + "yargs-parser": "^9.0.2" }, "dependencies": { "ansi-regex": { @@ -5090,8 +5090,8 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", + "center-align": "^0.1.1", + "right-align": "^0.1.1", "wordwrap": "0.0.2" }, "dependencies": { @@ -5107,8 +5107,8 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", "requires": { - "lru-cache": "4.1.2", - "which": "1.3.0" + "lru-cache": "^4.0.1", + "which": "^1.2.9" } }, "debug": { @@ -5129,7 +5129,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } }, "md5-hex": { @@ -5138,7 +5138,7 @@ "integrity": "sha1-0FiOnxx0lUSS7NJKwKxs6ZfZLjM=", "dev": true, "requires": { - "md5-o-matic": "0.1.1" + "md5-o-matic": "^0.1.1" } }, "minimist": { @@ -5151,8 +5151,8 @@ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "requires": { - "minimist": "0.0.8", - "wordwrap": "0.0.3" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" } }, "resolve-from": { @@ -5172,7 +5172,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { @@ -5180,7 +5180,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } }, "test-exclude": { @@ -5189,10 +5189,10 @@ "integrity": "sha512-2kTGf+3tykCfrWVREgyTR0bmVO0afE6i7zVXi/m+bZZ8ujV89Aulxdcdv32yH+unVFg3Y5o6GA8IzsHnGQuFgQ==", "dev": true, "requires": { - "arrify": "1.0.1", - "minimatch": "3.0.4", - "read-pkg-up": "3.0.0", - "require-main-filename": "1.0.1" + "arrify": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^3.0.0", + "require-main-filename": "^1.0.1" }, "dependencies": { "load-json-file": { @@ -5201,10 +5201,10 @@ "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "4.0.0", - "pify": "3.0.0", - "strip-bom": "3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" } }, "parse-json": { @@ -5213,8 +5213,8 @@ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "error-ex": "1.3.1", - "json-parse-better-errors": "1.0.2" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" }, "dependencies": { "json-parse-better-errors": { @@ -5231,7 +5231,7 @@ "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" } }, "pify": { @@ -5246,9 +5246,9 @@ "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "load-json-file": "4.0.0", - "normalize-package-data": "2.4.0", - "path-type": "3.0.0" + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" } }, "read-pkg-up": { @@ -5257,8 +5257,8 @@ "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", "dev": true, "requires": { - "find-up": "2.1.0", - "read-pkg": "3.0.0" + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" } }, "strip-bom": { @@ -5280,18 +5280,18 @@ "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, "requires": { - "cliui": "4.1.0", - "decamelize": "1.2.0", - "find-up": "2.1.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "9.0.2" + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" }, "dependencies": { "camelcase": { @@ -5306,9 +5306,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "wrap-ansi": "2.1.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } }, "yargs-parser": { @@ -5317,7 +5317,7 @@ "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" } } } @@ -5328,7 +5328,7 @@ "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" }, "dependencies": { "camelcase": { @@ -5357,9 +5357,9 @@ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "copy-descriptor": "0.1.1", - "define-property": "0.2.5", - "kind-of": "3.2.2" + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" }, "dependencies": { "define-property": { @@ -5368,7 +5368,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "kind-of": { @@ -5377,7 +5377,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -5406,7 +5406,7 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.0" } }, "object.assign": { @@ -5415,10 +5415,10 @@ "integrity": "sha1-lovxEA15Vrs8oIbwBvhGs7xACNo=", "dev": true, "requires": { - "define-properties": "1.1.2", - "function-bind": "1.1.1", - "has-symbols": "1.0.0", - "object-keys": "1.0.11" + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" } }, "object.entries": { @@ -5427,10 +5427,10 @@ "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0", - "function-bind": "1.1.1", - "has": "1.0.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.6.1", + "function-bind": "^1.1.0", + "has": "^1.0.1" } }, "object.pick": { @@ -5439,7 +5439,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "3.0.1" + "isobject": "^3.0.1" } }, "object.values": { @@ -5448,10 +5448,10 @@ "integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0", - "function-bind": "1.1.1", - "has": "1.0.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.6.1", + "function-bind": "^1.1.0", + "has": "^1.0.1" } }, "once": { @@ -5459,7 +5459,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "onetime": { @@ -5468,7 +5468,7 @@ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "1.2.0" + "mimic-fn": "^1.0.0" } }, "optimist": { @@ -5476,7 +5476,7 @@ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz", "integrity": "sha1-yQlBrVnkJzMokjB00s8ufLxuwNk=", "requires": { - "wordwrap": "0.0.3" + "wordwrap": "~0.0.2" } }, "optionator": { @@ -5485,12 +5485,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" }, "dependencies": { "wordwrap": { @@ -5512,9 +5512,9 @@ "integrity": "sha1-QrwpAKa1uL0XN2yOiCtlr8zyS/I=", "dev": true, "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" } }, "os-tmpdir": { @@ -5534,7 +5534,7 @@ "integrity": "sha1-DpK2vty1nwIsE9DxlJ3ILRWQnxw=", "dev": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { @@ -5543,7 +5543,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.2.0" + "p-limit": "^1.1.0" } }, "p-try": { @@ -5558,7 +5558,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.1" + "error-ex": "^1.2.0" } }, "parse5": { @@ -5567,7 +5567,7 @@ "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", "dev": true, "requires": { - "@types/node": "10.7.1" + "@types/node": "*" } }, "pascalcase": { @@ -5620,7 +5620,7 @@ "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { - "pify": "2.3.0" + "pify": "^2.0.0" } }, "pathval": { @@ -5652,7 +5652,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "pkg-dir": { @@ -5661,7 +5661,7 @@ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "find-up": "2.1.0" + "find-up": "^2.1.0" } }, "pluralize": { @@ -5681,21 +5681,21 @@ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.3.tgz", "integrity": "sha1-n2XyQngtNwKWNTcQ6byENJDBn2k=", "requires": { - "detect-libc": "1.0.3", - "expand-template": "1.1.0", + "detect-libc": "^1.0.3", + "expand-template": "^1.0.2", "github-from-package": "0.0.0", - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "node-abi": "2.4.0", - "noop-logger": "0.1.1", - "npmlog": "4.1.2", - "os-homedir": "1.0.2", - "pump": "2.0.1", - "rc": "1.2.7", - "simple-get": "2.8.1", - "tar-fs": "1.16.2", - "tunnel-agent": "0.6.0", - "which-pm-runs": "1.0.0" + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "node-abi": "^2.2.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "os-homedir": "^1.0.1", + "pump": "^2.0.1", + "rc": "^1.1.6", + "simple-get": "^2.7.0", + "tar-fs": "^1.13.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" } }, "prelude-ls": { @@ -5730,7 +5730,7 @@ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", "requires": { - "asap": "2.0.6" + "asap": "~2.0.3" } }, "prop-types": { @@ -5738,8 +5738,8 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", "requires": { - "loose-envify": "1.3.1", - "object-assign": "4.1.1" + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" } }, "prr": { @@ -5759,8 +5759,8 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", "integrity": "sha1-Ejma3W5M91Jtlzy8i1zi4pCLOQk=", "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "punycode": { @@ -5779,7 +5779,7 @@ "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==", "dev": true, "requires": { - "performance-now": "2.1.0" + "performance-now": "^2.1.0" } }, "railroad-diagrams": { @@ -5795,7 +5795,7 @@ "dev": true, "requires": { "discontinuous-range": "1.0.0", - "ret": "0.1.15" + "ret": "~0.1.10" } }, "rc": { @@ -5803,10 +5803,10 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.7.tgz", "integrity": "sha1-ihDKMNWI0ARkNgNyuJDQbazQIpc=", "requires": { - "deep-extend": "0.5.1", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" } }, "react": { @@ -5814,10 +5814,10 @@ "resolved": "https://registry.npmjs.org/react/-/react-16.4.0.tgz", "integrity": "sha512-K0UrkLXSAekf5nJu89obKUM7o2vc6MMN9LYoKnCa+c+8MJRAT120xzPLENcWSRc7GYKIg0LlgJRDorrufdglQQ==", "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.2" + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" } }, "react-dom": { @@ -5825,10 +5825,10 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.4.0.tgz", "integrity": "sha512-bbLd+HYpBEnYoNyxDe9XpSG2t9wypMohwQPvKw8Hov3nF7SJiJIgK56b46zHpBUpHb06a1iEuw7G3rbrsnNL6w==", "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.2" + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" } }, "react-input-autosize": { @@ -5836,7 +5836,7 @@ "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.1.tgz", "integrity": "sha1-7EKPoVsVkplPtfmqFbsetrr0IPg=", "requires": { - "prop-types": "15.6.2" + "prop-types": "^15.5.8" }, "dependencies": { "core-js": { @@ -5849,8 +5849,8 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" + "encoding": "^0.1.11", + "is-stream": "^1.0.1" } } } @@ -5867,10 +5867,10 @@ "integrity": "sha512-50JwZ3yNyMS8fchN+jjWEJOH3Oze7UmhxeoJLn2j6f3NjpfCRbcmih83XTWmzqtar/ivd5f7tvQhvvhism2fgg==", "dev": true, "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.2" + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" } }, "react-relay": { @@ -5878,9 +5878,9 @@ "resolved": "https://registry.npmjs.org/react-relay/-/react-relay-1.6.0.tgz", "integrity": "sha512-8clmRHXNo96pcdkA8ZeiqF7xGjE+mjSbdX/INj5upRm2M8AprSrFk2Oz5nH084O+0hvXQhZtFyraXJWQO9ld3A==", "requires": { - "babel-runtime": "6.25.0", - "fbjs": "0.8.16", - "prop-types": "15.6.2", + "babel-runtime": "^6.23.0", + "fbjs": "^0.8.14", + "prop-types": "^15.5.8", "relay-runtime": "1.6.0" } }, @@ -5889,9 +5889,9 @@ "resolved": "https://registry.npmjs.org/react-select/-/react-select-1.2.1.tgz", "integrity": "sha1-ov5YpWnrFNyqZUOBYmC5flOBINE=", "requires": { - "classnames": "2.2.6", - "prop-types": "15.6.2", - "react-input-autosize": "2.2.1" + "classnames": "^2.2.4", + "prop-types": "^15.5.8", + "react-input-autosize": "^2.1.2" }, "dependencies": { "core-js": { @@ -5904,8 +5904,8 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" + "encoding": "^0.1.11", + "is-stream": "^1.0.1" } } } @@ -5915,8 +5915,8 @@ "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-2.3.0.tgz", "integrity": "sha512-pYaefgVy76/36AMEP+B8YuVVzDHa3C5UFZ3REU78zolk0qMxEhKvUFofvDCXyLZwf0RZjxIfiwok1BEb18nHyA==", "requires": { - "classnames": "2.2.6", - "prop-types": "15.6.2" + "classnames": "^2.2.0", + "prop-types": "^15.5.0" } }, "react-test-renderer": { @@ -5925,10 +5925,10 @@ "integrity": "sha512-wyyiPxRZOTpKnNIgUBOB6xPLTpIzwcQMIURhZvzUqZzezvHjaGNsDPBhMac5fIY3Jf5NuKxoGvV64zDSOECPPQ==", "dev": true, "requires": { - "fbjs": "0.8.16", - "object-assign": "4.1.1", - "prop-types": "15.6.2", - "react-is": "16.4.1" + "fbjs": "^0.8.16", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0", + "react-is": "^16.4.1" } }, "read-pkg": { @@ -5937,9 +5937,9 @@ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" } }, "read-pkg-up": { @@ -5948,8 +5948,8 @@ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" } }, "readable-stream": { @@ -5957,13 +5957,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" }, "dependencies": { "isarray": { @@ -5989,8 +5989,8 @@ "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", "dev": true, "requires": { - "extend-shallow": "3.0.2", - "safe-regex": "1.1.0" + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" }, "dependencies": { "is-extendable": { @@ -5998,7 +5998,7 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -6009,7 +6009,7 @@ "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", "dev": true, "requires": { - "define-properties": "1.1.2" + "define-properties": "^1.1.2" } }, "regexpp": { @@ -6027,19 +6027,19 @@ "@babel/generator": "7.0.0-beta.54", "@babel/parser": "7.0.0-beta.54", "@babel/types": "7.0.0-beta.54", - "babel-polyfill": "6.26.0", + "babel-polyfill": "^6.20.0", "babel-preset-fbjs": "2.2.0", - "babel-runtime": "6.25.0", - "babel-traverse": "6.26.0", - "chalk": "1.1.3", - "fast-glob": "2.2.2", - "fb-watchman": "2.0.0", + "babel-runtime": "^6.23.0", + "babel-traverse": "^6.26.0", + "chalk": "^1.1.1", + "fast-glob": "^2.2.2", + "fb-watchman": "^2.0.0", "fbjs": "0.8.17", "graphql-compiler": "1.6.2", - "immutable": "3.7.6", + "immutable": "~3.7.6", "relay-runtime": "1.6.2", - "signedsource": "1.0.0", - "yargs": "9.0.1" + "signedsource": "^1.0.0", + "yargs": "^9.0.0" }, "dependencies": { "@babel/generator": { @@ -6049,10 +6049,10 @@ "dev": true, "requires": { "@babel/types": "7.0.0-beta.54", - "jsesc": "2.5.1", - "lodash": "4.17.5", - "source-map": "0.5.7", - "trim-right": "1.0.1" + "jsesc": "^2.5.1", + "lodash": "^4.17.5", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" } }, "@babel/parser": { @@ -6067,9 +6067,9 @@ "integrity": "sha1-AlrWhJL+1ULBPxTFeaRMhI5TEGM=", "dev": true, "requires": { - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "2.0.0" + "esutils": "^2.0.2", + "lodash": "^4.17.5", + "to-fast-properties": "^2.0.0" } }, "babel-traverse": { @@ -6078,15 +6078,15 @@ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.4", - "lodash": "4.17.5" + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" }, "dependencies": { "babel-runtime": { @@ -6095,8 +6095,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } } } @@ -6107,10 +6107,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.5", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" }, "dependencies": { "babel-runtime": { @@ -6119,8 +6119,8 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.0", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" } }, "to-fast-properties": { @@ -6143,13 +6143,13 @@ "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", "dev": true, "requires": { - "core-js": "1.2.7", - "isomorphic-fetch": "2.2.1", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "promise": "7.3.1", - "setimmediate": "1.0.5", - "ua-parser-js": "0.7.18" + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.18" }, "dependencies": { "core-js": { @@ -6178,7 +6178,7 @@ "integrity": "sha512-eWjdlK+OvzW35fbYcFrO+S4dMyxb1LqrCU9HBh1OEEK465Te+l+l2Rmg9RCdcvaPuq7zGC6+Anec2/2aNXtiUA==", "dev": true, "requires": { - "babel-runtime": "6.25.0", + "babel-runtime": "^6.23.0", "fbjs": "0.8.17" } }, @@ -6195,8 +6195,8 @@ "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-1.6.0.tgz", "integrity": "sha512-UJiEHp8CX2uFxXdM0nVLTCQ6yAT0GLmyMceXLISuW/l2a9jrS9a4MdZgdr/9UkkYno7Sj1hU/EUIQ0GaVkou8g==", "requires": { - "babel-runtime": "6.25.0", - "fbjs": "0.8.16" + "babel-runtime": "^6.23.0", + "fbjs": "^0.8.14" } }, "repeat-element": { @@ -6215,7 +6215,7 @@ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "requires": { - "is-finite": "1.0.2" + "is-finite": "^1.0.0" } }, "request": { @@ -6223,26 +6223,26 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.1.0" + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" }, "dependencies": { "mime-db": { @@ -6255,7 +6255,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "integrity": "sha1-bzI/YKg9ERRvgx/xH9ZuL+VQO7g=", "requires": { - "mime-db": "1.33.0" + "mime-db": "~1.33.0" } } } @@ -6282,7 +6282,7 @@ "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "dev": true, "requires": { - "mime-db": "1.35.0" + "mime-db": "~1.35.0" } }, "request": { @@ -6291,28 +6291,28 @@ "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", "dev": true, "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.19", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.6", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.1.0" + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "hawk": "~6.0.2", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "stringstream": "~0.0.5", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" } } } @@ -6335,8 +6335,8 @@ "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" } }, "resolve-from": { @@ -6357,8 +6357,8 @@ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" } }, "ret": { @@ -6372,7 +6372,7 @@ "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", "requires": { - "align-text": "0.1.4" + "align-text": "^0.1.1" } }, "rimraf": { @@ -6380,7 +6380,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "rst-selector-parser": { @@ -6389,8 +6389,8 @@ "integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=", "dev": true, "requires": { - "lodash.flattendeep": "4.4.0", - "nearley": "2.15.1" + "lodash.flattendeep": "^4.4.0", + "nearley": "^2.7.10" } }, "run-async": { @@ -6399,7 +6399,7 @@ "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "dev": true, "requires": { - "is-promise": "2.1.0" + "is-promise": "^2.1.0" } }, "rxjs": { @@ -6422,7 +6422,7 @@ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { - "ret": "0.1.15" + "ret": "~0.1.10" } }, "samsam": { @@ -6447,10 +6447,10 @@ "integrity": "sha1-ca5KiPD+77v1LR6mBPP7MV67YnQ=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "split-string": "3.1.0" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" }, "dependencies": { "extend-shallow": { @@ -6459,7 +6459,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -6475,7 +6475,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "1.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { @@ -6505,9 +6505,9 @@ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", "integrity": "sha1-DiLpHUV12HYgYgvJEwjVenf0S10=", "requires": { - "decompress-response": "3.3.0", - "once": "1.4.0", - "simple-concat": "1.0.0" + "decompress-response": "^3.3.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" } }, "sinon": { @@ -6516,13 +6516,13 @@ "integrity": "sha512-rfszhNcfamK2+ofIPi9XqeH89pH7KGDcAtM+F9CsjHXOK3jzWG99vyhyD2V+r7s4IipmWcWUFYq4ftZ9/Eu2Wg==", "dev": true, "requires": { - "@sinonjs/formatio": "2.0.0", - "diff": "3.5.0", - "lodash.get": "4.4.2", - "lolex": "2.7.1", - "nise": "1.4.2", - "supports-color": "5.4.0", - "type-detect": "4.0.8" + "@sinonjs/formatio": "^2.0.0", + "diff": "^3.5.0", + "lodash.get": "^4.4.2", + "lolex": "^2.4.2", + "nise": "^1.3.3", + "supports-color": "^5.4.0", + "type-detect": "^4.0.8" }, "dependencies": { "supports-color": { @@ -6531,7 +6531,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -6547,7 +6547,7 @@ "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0" + "is-fullwidth-code-point": "^2.0.0" }, "dependencies": { "is-fullwidth-code-point": { @@ -6570,14 +6570,14 @@ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "base": "0.11.2", - "debug": "2.6.9", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "map-cache": "0.2.2", - "source-map": "0.5.7", - "source-map-resolve": "0.5.1", - "use": "3.1.0" + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" }, "dependencies": { "define-property": { @@ -6586,7 +6586,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } }, "extend-shallow": { @@ -6595,7 +6595,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } } } @@ -6606,9 +6606,9 @@ "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", "dev": true, "requires": { - "define-property": "1.0.0", - "isobject": "3.0.1", - "snapdragon-util": "3.0.1" + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" }, "dependencies": { "define-property": { @@ -6617,7 +6617,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "1.0.2" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { @@ -6626,7 +6626,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-data-descriptor": { @@ -6635,7 +6635,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.0" } }, "is-descriptor": { @@ -6644,9 +6644,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" } } } @@ -6657,7 +6657,7 @@ "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.2.0" }, "dependencies": { "kind-of": { @@ -6666,7 +6666,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -6677,7 +6677,7 @@ "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "dev": true, "requires": { - "hoek": "4.2.1" + "hoek": "4.x.x" } }, "source-map": { @@ -6691,11 +6691,11 @@ "integrity": "sha1-etD1k/IoFZjoVN+A8ZquS5LXoRo=", "dev": true, "requires": { - "atob": "2.1.1", - "decode-uri-component": "0.2.0", - "resolve-url": "0.2.1", - "source-map-url": "0.4.0", - "urix": "0.1.0" + "atob": "^2.0.0", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" } }, "source-map-support": { @@ -6703,7 +6703,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", "integrity": "sha1-Aoam3ovkJkEzhZTpfM6nXwosWF8=", "requires": { - "source-map": "0.5.7" + "source-map": "^0.5.6" } }, "source-map-url": { @@ -6718,12 +6718,12 @@ "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", "dev": true, "requires": { - "foreground-child": "1.5.6", - "mkdirp": "0.5.1", - "os-homedir": "1.0.2", - "rimraf": "2.6.2", - "signal-exit": "3.0.2", - "which": "1.3.0" + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" } }, "spdx-correct": { @@ -6732,8 +6732,8 @@ "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { @@ -6748,8 +6748,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { @@ -6763,7 +6763,7 @@ "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", "integrity": "sha1-YFvZvjA6pZ+zX5Ip++oN3snqB9k=", "requires": { - "through": "2.3.8" + "through": "2" } }, "split-string": { @@ -6772,7 +6772,7 @@ "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", "dev": true, "requires": { - "extend-shallow": "3.0.2" + "extend-shallow": "^3.0.0" }, "dependencies": { "is-extendable": { @@ -6780,7 +6780,7 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -6796,14 +6796,14 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" } }, "static-extend": { @@ -6812,8 +6812,8 @@ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "define-property": "0.2.5", - "object-copy": "0.1.0" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" }, "dependencies": { "define-property": { @@ -6822,7 +6822,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "0.1.6" + "is-descriptor": "^0.1.0" } } } @@ -6839,8 +6839,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" }, "dependencies": { "ansi-regex": { @@ -6861,7 +6861,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -6872,11 +6872,11 @@ "integrity": "sha512-WoZ+B2ypng1dp4iFLF2kmZlwwlE19gmjgKuhL1FJfDgCREWb3ye3SDVHSzLH6bxfnvYmkCxbzkmWcQZHA4P//Q==", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0", - "function-bind": "1.1.1", - "has-symbols": "1.0.0", - "regexp.prototype.flags": "1.2.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.10.0", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "regexp.prototype.flags": "^1.2.0" } }, "string_decoder": { @@ -6884,7 +6884,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } }, "stringstream": { @@ -6898,7 +6898,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-bom": { @@ -6935,12 +6935,12 @@ "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", "dev": true, "requires": { - "ajv": "6.5.2", - "ajv-keywords": "3.2.0", - "chalk": "2.4.1", - "lodash": "4.17.5", + "ajv": "^6.0.1", + "ajv-keywords": "^3.0.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", "slice-ansi": "1.0.0", - "string-width": "2.1.1" + "string-width": "^2.1.1" }, "dependencies": { "ajv": { @@ -6949,10 +6949,10 @@ "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", "dev": true, "requires": { - "fast-deep-equal": "2.0.1", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.4.1", - "uri-js": "4.2.2" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.1" } }, "ansi-styles": { @@ -6961,7 +6961,7 @@ "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, "chalk": { @@ -6970,9 +6970,9 @@ "integrity": "sha1-GMSasWoDe26wFSzIPjRxM4IVtm4=", "dev": true, "requires": { - "ansi-styles": "3.2.1", - "escape-string-regexp": "1.0.5", - "supports-color": "5.4.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "fast-deep-equal": { @@ -6993,7 +6993,7 @@ "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", "dev": true, "requires": { - "has-flag": "3.0.0" + "has-flag": "^3.0.0" } } } @@ -7003,13 +7003,13 @@ "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.4.tgz", "integrity": "sha1-7IQJ+un2ZaQ1XMO0CH0IICMruM0=", "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.3.3", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.2", - "yallist": "3.0.2" + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.3", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" }, "dependencies": { "safe-buffer": { @@ -7024,10 +7024,10 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.2.tgz", "integrity": "sha1-F+Ujl0fjmffnc0T19TNl8Er1NXc=", "requires": { - "chownr": "1.0.1", - "mkdirp": "0.5.1", - "pump": "1.0.3", - "tar-stream": "1.6.0" + "chownr": "^1.0.1", + "mkdirp": "^0.5.1", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" }, "dependencies": { "pump": { @@ -7035,8 +7035,8 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", "integrity": "sha1-Xf6DEcM7v2/BgmH580cCxHwIqVQ=", "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } } } @@ -7046,13 +7046,13 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.0.tgz", "integrity": "sha1-pQ76p7F3YLgsJ7PK5KMBqCVKVxU=", "requires": { - "bl": "1.2.2", - "buffer-alloc": "1.1.0", - "end-of-stream": "1.4.1", - "fs-constants": "1.0.0", - "readable-stream": "2.3.3", - "to-buffer": "1.1.1", - "xtend": "4.0.1" + "bl": "^1.0.0", + "buffer-alloc": "^1.1.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.0.0", + "to-buffer": "^1.1.0", + "xtend": "^4.0.0" } }, "temp": { @@ -7060,8 +7060,8 @@ "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", "requires": { - "os-tmpdir": "1.0.2", - "rimraf": "2.2.8" + "os-tmpdir": "^1.0.0", + "rimraf": "~2.2.6" }, "dependencies": { "rimraf": { @@ -7077,11 +7077,11 @@ "integrity": "sha1-36Ii8DSAvKaSB8pyizfXS0X3JPo=", "dev": true, "requires": { - "arrify": "1.0.1", - "micromatch": "3.1.10", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "require-main-filename": "1.0.1" + "arrify": "^1.0.1", + "micromatch": "^3.1.8", + "object-assign": "^4.1.0", + "read-pkg-up": "^1.0.1", + "require-main-filename": "^1.0.1" }, "dependencies": { "find-up": { @@ -7090,8 +7090,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "load-json-file": { @@ -7100,11 +7100,11 @@ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" } }, "path-exists": { @@ -7113,7 +7113,7 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } }, "path-type": { @@ -7122,9 +7122,9 @@ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "read-pkg": { @@ -7133,9 +7133,9 @@ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" } }, "read-pkg-up": { @@ -7144,8 +7144,8 @@ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" } }, "strip-bom": { @@ -7154,7 +7154,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "0.2.1" + "is-utf8": "^0.2.0" } } } @@ -7193,7 +7193,7 @@ "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.1" } }, "to-buffer": { @@ -7213,7 +7213,7 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { @@ -7222,7 +7222,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -7233,10 +7233,10 @@ "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", "dev": true, "requires": { - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "regex-not": "1.0.2", - "safe-regex": "1.1.0" + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" }, "dependencies": { "is-extendable": { @@ -7244,7 +7244,7 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -7255,8 +7255,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "3.0.0", - "repeat-string": "1.6.1" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } }, "tough-cookie": { @@ -7264,7 +7264,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha1-7GDO44rGdQY//JelwYlwV47oNlU=", "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" } }, "tree-kill": { @@ -7282,7 +7282,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -7297,7 +7297,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "type-detect": { @@ -7318,9 +7318,9 @@ "dev": true, "optional": true, "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" }, "dependencies": { "camelcase": { @@ -7337,8 +7337,8 @@ "dev": true, "optional": true, "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", + "center-align": "^0.1.1", + "right-align": "^0.1.1", "wordwrap": "0.0.2" } }, @@ -7356,9 +7356,9 @@ "dev": true, "optional": true, "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", "window-size": "0.1.0" } } @@ -7383,10 +7383,10 @@ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, "requires": { - "arr-union": "3.1.0", - "get-value": "2.0.6", - "is-extendable": "0.1.1", - "set-value": "0.4.3" + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" }, "dependencies": { "extend-shallow": { @@ -7395,7 +7395,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "is-extendable": "^0.1.0" } }, "set-value": { @@ -7404,10 +7404,10 @@ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, "requires": { - "extend-shallow": "2.0.1", - "is-extendable": "0.1.1", - "is-plain-object": "2.0.4", - "to-object-path": "0.3.0" + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" } } } @@ -7423,8 +7423,8 @@ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "has-value": "0.3.1", - "isobject": "3.0.1" + "has-value": "^0.3.1", + "isobject": "^3.0.0" }, "dependencies": { "has-value": { @@ -7433,9 +7433,9 @@ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "get-value": "2.0.6", - "has-values": "0.1.4", - "isobject": "2.1.0" + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" }, "dependencies": { "isobject": { @@ -7469,7 +7469,7 @@ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { - "punycode": "2.1.1" + "punycode": "^2.1.0" }, "dependencies": { "punycode": { @@ -7492,7 +7492,7 @@ "integrity": "sha1-IjeVIL/gfSa1kIAEkIruEuhNBf8=", "dev": true, "requires": { - "deep-equal": "1.0.1" + "deep-equal": "~1.0.1" }, "dependencies": { "deep-equal": { @@ -7509,7 +7509,7 @@ "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", "dev": true, "requires": { - "kind-of": "6.0.2" + "kind-of": "^6.0.2" } }, "util-deprecate": { @@ -7528,8 +7528,8 @@ "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", "dev": true, "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, "verror": { @@ -7537,9 +7537,9 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "virtual-dom": { @@ -7549,11 +7549,11 @@ "dev": true, "requires": { "browser-split": "0.0.1", - "error": "4.4.0", - "ev-store": "7.0.0", - "global": "4.3.2", - "is-object": "1.0.1", - "next-tick": "0.2.2", + "error": "^4.3.0", + "ev-store": "^7.0.0", + "global": "^4.3.0", + "is-object": "^1.0.1", + "next-tick": "^0.2.2", "x-is-array": "0.1.0", "x-is-string": "0.1.0" } @@ -7568,7 +7568,7 @@ "resolved": "https://registry.npmjs.org/what-the-status/-/what-the-status-1.0.3.tgz", "integrity": "sha1-lP3NAR/7U6Ijnnb6+NrL78mHdRA=", "requires": { - "split": "1.0.1" + "split": "^1.0.0" } }, "whatwg-fetch": { @@ -7581,7 +7581,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { @@ -7600,7 +7600,7 @@ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=", "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" }, "dependencies": { "string-width": { @@ -7608,9 +7608,9 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -7633,8 +7633,8 @@ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" }, "dependencies": { "string-width": { @@ -7643,9 +7643,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -7661,7 +7661,7 @@ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, "requires": { - "mkdirp": "0.5.1" + "mkdirp": "^0.5.1" } }, "write-file-atomic": { @@ -7670,9 +7670,9 @@ "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "imurmurhash": "0.1.4", - "slide": "1.1.6" + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" } }, "x-is-array": { @@ -7709,19 +7709,19 @@ "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", "dev": true, "requires": { - "camelcase": "4.1.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "read-pkg-up": "2.0.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "7.0.0" + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" } }, "yargs-parser": { @@ -7730,7 +7730,7 @@ "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", "dev": true, "requires": { - "camelcase": "4.1.0" + "camelcase": "^4.1.0" } }, "yubikiri": { From daea2a14eee5452bd95e74b1d6f8399604e43168 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 4 Oct 2018 18:24:33 +0300 Subject: [PATCH 0413/4053] fix typo --- docs/rfcs/002-issueish-list.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/002-issueish-list.md b/docs/rfcs/002-issueish-list.md index 4b6a90d5bf..99ebc70ff1 100644 --- a/docs/rfcs/002-issueish-list.md +++ b/docs/rfcs/002-issueish-list.md @@ -22,7 +22,7 @@ As an initial building block toward a pull request review workflow. Within the GitHub panel, render a vertical stack of two collapsible lists of _issueish_ (pull request or issue) items: -_First list: checked out request_. If the active branch is associated with one or more open pull requests on a GitHub repository, render an item for each. "Associated with" means that the pull request's head ref and head repository matches the upstream remote ref for the current branch in the active git repository. +_First list: checked out pull request_. If the active branch is associated with one or more open pull requests on a GitHub repository, render an item for each. "Associated with" means that the pull request's head ref and head repository matches the upstream remote ref for the current branch in the active git repository. _Second list: all open pull requests_. List all open pull requests on the GitHub repository, ordered by decreasing creation date. From 8340b366dd7dacd4e5298124aec770b13da89598 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 4 Oct 2018 18:25:33 +0300 Subject: [PATCH 0414/4053] change pr name in schema --- graphql/schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql/schema.graphql b/graphql/schema.graphql index dfadf986b3..e148c6aef6 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -5284,7 +5284,7 @@ type PullRequestReview implements Node & Comment & Deletable & Updatable & Updat """The body of this review rendered as plain text.""" bodyText: String! - """A list of review comments for the current pull request review.""" + """A list of review comments for the checked out pull request review.""" comments( """Returns the first _n_ elements from the list.""" first: Int From 39988788dc61b2fe9c4e674db1b775b9898a71ee Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 11:31:53 -0400 Subject: [PATCH 0415/4053] Add a config setting to toggle the diff icon gutter --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index 7ac89edf88..63f3b2b36f 100644 --- a/package.json +++ b/package.json @@ -173,6 +173,11 @@ "default": true, "description": "Resolve merge conflicts with in-editor controls" }, + "showDiffIconGutter": { + "type": "boolean", + "default": false, + "description": "Show change regions within a file patch with icons in addition to color" + }, "excludedUsers": { "type": "string", "default": "", From 2774dc8e91b893574f25b59c1eb78e3bf106ab71 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 11:43:58 -0400 Subject: [PATCH 0416/4053] Unit test for diff icon gutter behavior --- test/views/file-patch-view.test.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 2affc25946..7bfcfc797b 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -526,6 +526,26 @@ describe('FilePatchView', function() { assert.strictEqual(instance.newLineNumberLabel({bufferRow: 999, softWrapped: false}), '\u00a0\u00a0'); }); + it('renders diff region scope characters when the config option is enabled', function() { + atomEnv.config.set('github.showDiffIconGutter', true); + + wrapper.update(); + const gutter = wrapper.find('Gutter[name="diff-icons"]'); + assert.isTrue(gutter.exists()); + + const assertLayerDecorated = layer => { + const layerWrapper = wrapper.find('MarkerLayer').filterWhere(each => each.prop('external') === layer); + const decorations = layerWrapper.find('Decoration[type="line-number"][gutterName="diff-icons"]'); + assert.isTrue(decorations.exists()); + }; + assertLayerDecorated(filePatch.getAdditionLayer()); + assertLayerDecorated(filePatch.getDeletionLayer()); + + atomEnv.config.set('github.showDiffIconGutter', false); + wrapper.update(); + assert.isFalse(wrapper.find('Gutter[name="diff-icons"]').exists()); + }); + it('selects a single line on click', function() { instance.didMouseDownOnLineNumber({bufferRow: 2, domEvent: {button: 0}}); assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ From 784b190e1682a2de8ca7f518f5c149a2f1a1c871 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 11:44:21 -0400 Subject: [PATCH 0417/4053] Render and decorate a gutter for diff icons --- lib/views/file-patch-view.js | 43 ++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 8b4acc4655..f4230ba52d 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -23,6 +23,8 @@ const executableText = { const NBSP_CHARACTER = '\u00a0'; +const BLANK_LABEL = () => NBSP_CHARACTER; + export default class FilePatchView extends React.Component { static propTypes = { relPath: PropTypes.string.isRequired, @@ -88,6 +90,10 @@ export default class FilePatchView extends React.Component { } return null; }); + + this.subs.add( + this.props.config.onDidChange('github.showDiffIconGutter', ({newValue}) => this.forceUpdate()), + ); } getSnapshotBeforeUpdate(prevProps) { @@ -227,6 +233,17 @@ export default class FilePatchView extends React.Component { onMouseDown={this.didMouseDownOnLineNumber} onMouseMove={this.didMouseMoveOnLineNumber} /> + {this.props.config.get('github.showDiffIconGutter') && ( + + )} @@ -242,23 +259,23 @@ export default class FilePatchView extends React.Component { {this.renderLineDecorations( Array.from(this.props.selectedRows, row => Range.fromObject([[row, 0], [row, Infinity]])), 'github-FilePatchView-line--selected', - {gutter: true, line: true}, + {gutter: true, icon: true, line: true}, )} {this.renderDecorationsOnLayer( this.props.filePatch.getAdditionLayer(), 'github-FilePatchView-line--added', - {line: true}, + {icon: true, line: true}, )} {this.renderDecorationsOnLayer( this.props.filePatch.getDeletionLayer(), 'github-FilePatchView-line--deleted', - {line: true}, + {icon: true, line: true}, )} {this.renderDecorationsOnLayer( this.props.filePatch.getNoNewlineLayer(), 'github-FilePatchView-line--nonewline', - {line: true}, + {icon: true, line: true}, )} @@ -439,7 +456,7 @@ export default class FilePatchView extends React.Component { ); } - renderLineDecorations(ranges, lineClass, {line, gutter, refHolder}) { + renderLineDecorations(ranges, lineClass, {line, gutter, icon, refHolder}) { if (ranges.length === 0) { return null; } @@ -456,24 +473,24 @@ export default class FilePatchView extends React.Component { /> ); })} - {this.renderDecorations(lineClass, {line, gutter})} + {this.renderDecorations(lineClass, {line, gutter, icon})} ); } - renderDecorationsOnLayer(layer, lineClass, {line, gutter}) { + renderDecorationsOnLayer(layer, lineClass, {line, gutter, icon}) { if (layer.getMarkerCount() === 0) { return null; } return ( - {this.renderDecorations(lineClass, {line, gutter})} + {this.renderDecorations(lineClass, {line, gutter, icon})} ); } - renderDecorations(lineClass, {line, gutter}) { + renderDecorations(lineClass, {line, gutter, icon}) { return ( {line && ( @@ -499,6 +516,14 @@ export default class FilePatchView extends React.Component { /> )} + {icon && ( + + )} ); } From 4b03503c35b862373f7c4f16a22a1d0b30f92187 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 11:44:55 -0400 Subject: [PATCH 0418/4053] CSS fumblings to add + and - glyphs to the diff icon gutter --- styles/file-patch-view.less | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 1e5fdbf2f2..c04a3445b0 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -1,4 +1,6 @@ @import "variables"; +@import "octicon-utf-codes"; +@import "octicon-mixins"; @hunk-fg-color: @text-color-subtle; @hunk-bg-color: @pane-item-background-color; @@ -183,7 +185,7 @@ display: none; // remove fold icon } - .line-number { + &.old .line-number, &.new .line-number { min-width: 6ch; // Fit up to 4 characters (+1 padding on each side) opacity: 1; padding: 0 1ch 0 0; @@ -193,5 +195,34 @@ background: @button-background-color-selected; } } + + &.icons .line-number { + .octicon-font(); + opacity: 1; + width: 2ch; + + &:before { + display: inline-block; + min-width: 1ch; + text-align: center; + } + + &.github-FilePatchView-line--added:before { + content: @plus; + } + + &.github-FilePatchView-line--deleted:before { + content: @dash; + } + + &.github-FilePatchView-line--nonewline:before { + content: @no-newline; + } + + &.github-FilePatchView-line--selected { + color: contrast(@button-background-color-selected); + background: @button-background-color-selected; + } + } } } From cefe40c12558ec035a146489239be1288234ef6f Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 4 Oct 2018 18:50:17 +0300 Subject: [PATCH 0419/4053] revert schema.graphql file changes --- graphql/schema.graphql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql/schema.graphql b/graphql/schema.graphql index e148c6aef6..dfadf986b3 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -5284,7 +5284,7 @@ type PullRequestReview implements Node & Comment & Deletable & Updatable & Updat """The body of this review rendered as plain text.""" bodyText: String! - """A list of review comments for the checked out pull request review.""" + """A list of review comments for the current pull request review.""" comments( """Returns the first _n_ elements from the list.""" first: Int From a13fcb4fea17f904b90be19baa09f406bbfd373d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 13:31:51 -0400 Subject: [PATCH 0420/4053] :dancer: Context menu shuffle :dancer: --- menus/git.cson | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/menus/git.cson b/menus/git.cson index 689fe55a2c..0fade56111 100644 --- a/menus/git.cson +++ b/menus/git.cson @@ -39,24 +39,33 @@ } ] '.github-FilePatchView': [ + { + 'type': 'separator' + } { 'label': 'Open File' 'command': 'github:open-file' + 'after': ['core:confirm'] } ] '.github-FilePatchView--staged': [ + { + 'type': 'separator' + } { 'label': 'Unstage Selection' 'command': 'core:confirm' + 'beforeGroupContaining': ['core:undo'] } ] '.github-FilePatchView--unstaged': [ { - 'label': 'Stage Selection' - 'command': 'core:confirm' + 'type': 'separator' } { - 'type': 'separator' + 'label': 'Stage Selection' + 'command': 'core:confirm' + 'beforeGroupContaining': ['core:undo'] } { 'label': 'Discard Selection' From 8eb1f82ed17b2ce3eb6f21880eae70d5f52cf10f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 13:37:17 -0400 Subject: [PATCH 0421/4053] Remove "View (Un)staged Changes" entries from FilePatchItem context menu --- menus/git.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/menus/git.cson b/menus/git.cson index 0fade56111..62a0ea5858 100644 --- a/menus/git.cson +++ b/menus/git.cson @@ -114,7 +114,7 @@ 'command': 'github:toggle-expanded-commit-message-editor' } ] - 'atom-text-editor': [ + '.item-views > atom-text-editor': [ { 'label': 'View Unstaged Changes', 'command': 'github:view-unstaged-changes-for-current-file' From e8a51eb1978d9657f8c92ec6f5522a8ba81bd543 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 13:46:16 -0400 Subject: [PATCH 0422/4053] Remove tabIndex from root div to allow TextEditor to inherit focus --- lib/views/file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index f4230ba52d..36f6716788 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -154,7 +154,7 @@ export default class FilePatchView extends React.Component { ); return ( -
+
{this.renderCommands()} From 107170d80349b38ca505ac46981387ca0bb37016 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 13:57:20 -0400 Subject: [PATCH 0423/4053] Drill, drill, drill them props --- lib/containers/file-patch-container.js | 1 + lib/controllers/file-patch-controller.js | 1 + lib/controllers/root-controller.js | 1 + lib/items/file-patch-item.js | 1 + test/containers/file-patch-container.test.js | 1 + test/items/file-patch-item.test.js | 1 + 6 files changed, 6 insertions(+) diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index eb85a11d7e..2f6e8e5588 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -21,6 +21,7 @@ export default class FilePatchContainer extends React.Component { destroy: PropTypes.func.isRequired, undoLastDiscard: PropTypes.func.isRequired, + surfaceFileAtPath: PropTypes.func.isRequired, } constructor(props) { diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 1fa11bc485..48226da65b 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -22,6 +22,7 @@ export default class FilePatchController extends React.Component { destroy: PropTypes.func.isRequired, discardLines: PropTypes.func.isRequired, undoLastDiscard: PropTypes.func.isRequired, + surfaceFileAtPath: PropTypes.func.isRequired, } constructor(props) { diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index d4e320bacd..cae22ac5cd 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -323,6 +323,7 @@ export default class RootController extends React.Component { discardLines={this.discardLines} undoLastDiscard={this.undoLastDiscard} + surfaceFileAtPath={this.surfaceFromFileAtPath} /> )} diff --git a/lib/items/file-patch-item.js b/lib/items/file-patch-item.js index 86ed8ad9c8..40f6a58dfa 100644 --- a/lib/items/file-patch-item.js +++ b/lib/items/file-patch-item.js @@ -22,6 +22,7 @@ export default class FilePatchItem extends React.Component { discardLines: PropTypes.func.isRequired, undoLastDiscard: PropTypes.func.isRequired, + surfaceFileAtPath: PropTypes.func.isRequired, } static uriPattern = 'atom-github://file-patch/{relPath...}?workdir={workingDirectory}&stagingStatus={stagingStatus}' diff --git a/test/containers/file-patch-container.test.js b/test/containers/file-patch-container.test.js index 81e744117c..a4e12a65f5 100644 --- a/test/containers/file-patch-container.test.js +++ b/test/containers/file-patch-container.test.js @@ -41,6 +41,7 @@ describe('FilePatchContainer', function() { config: atomEnv.config, discardLines: () => {}, undoLastDiscard: () => {}, + surfaceFileAtPath: () => {}, destroy: () => {}, ...overrideProps, }; diff --git a/test/items/file-patch-item.test.js b/test/items/file-patch-item.test.js index c94a1c0964..76cb23d8f8 100644 --- a/test/items/file-patch-item.test.js +++ b/test/items/file-patch-item.test.js @@ -36,6 +36,7 @@ describe('FilePatchItem', function() { config: atomEnv.config, discardLines: () => {}, undoLastDiscard: () => {}, + surfaceFileAtPath: () => {}, ...overrideProps, }; From b91287f614b3311973318ede9bbc975a6dac84fc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 14:37:56 -0400 Subject: [PATCH 0424/4053] Controller method for file surfacing --- lib/controllers/file-patch-controller.js | 7 ++++++- test/controllers/file-patch-controller.test.js | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 48226da65b..49ae71a3e0 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -30,7 +30,7 @@ export default class FilePatchController extends React.Component { autobind( this, 'selectedRowsChanged', - 'undoLastDiscard', 'diveIntoMirrorPatch', 'openFile', + 'undoLastDiscard', 'diveIntoMirrorPatch', 'surfaceFile', 'openFile', 'toggleFile', 'toggleRows', 'toggleModeChange', 'toggleSymlinkChange', 'discardRows', ); @@ -67,6 +67,7 @@ export default class FilePatchController extends React.Component { selectedRowsChanged={this.selectedRowsChanged} diveIntoMirrorPatch={this.diveIntoMirrorPatch} + surfaceFile={this.surfaceFile} openFile={this.openFile} toggleFile={this.toggleFile} toggleRows={this.toggleRows} @@ -93,6 +94,10 @@ export default class FilePatchController extends React.Component { return this.props.workspace.open(uri); } + surfaceFile() { + return this.props.surfaceFileAtPath(this.props.relPath, this.props.stagingStatus); + } + async openFile(positions) { const absolutePath = path.join(this.props.repository.getWorkingDirectoryPath(), this.props.relPath); const editor = await this.props.workspace.open(absolutePath, {pending: true}); diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index c20d8020c3..7a9ab95f1e 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -40,6 +40,7 @@ describe('FilePatchController', function() { destroy: () => {}, discardLines: () => {}, undoLastDiscard: () => {}, + surfaceFileAtPath: () => {}, ...overrideProps, }; @@ -61,6 +62,14 @@ describe('FilePatchController', function() { assert.isTrue(undoLastDiscard.calledWith('b.txt', repository)); }); + it('calls surfaceFileAtPath with set arguments', function() { + const surfaceFileAtPath = sinon.spy(); + const wrapper = shallow(buildApp({relPath: 'c.txt', surfaceFileAtPath})); + wrapper.find('FilePatchView').prop('surfaceFile')(); + + assert.isTrue(surfaceFileAtPath.calledWith('c.txt', 'unstaged')); + }); + describe('diveIntoMirrorPatch()', function() { it('destroys the current pane and opens the staged changes', async function() { const destroy = sinon.spy(); From a5076c564ae3331ae53ae3ed29a5b59aa97bbe87 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 14:38:06 -0400 Subject: [PATCH 0425/4053] Register surfacing command --- lib/views/file-patch-view.js | 2 ++ test/views/file-patch-view.test.js | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 36f6716788..6a5b974728 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -44,6 +44,7 @@ export default class FilePatchView extends React.Component { selectedRowsChanged: PropTypes.func.isRequired, diveIntoMirrorPatch: PropTypes.func.isRequired, + surfaceFile: PropTypes.func.isRequired, openFile: PropTypes.func.isRequired, toggleFile: PropTypes.func.isRequired, toggleRows: PropTypes.func.isRequired, @@ -189,6 +190,7 @@ export default class FilePatchView extends React.Component { + ); diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 7bfcfc797b..6a3145403f 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -62,6 +62,7 @@ describe('FilePatchView', function() { selectedRowsChanged: () => {}, diveIntoMirrorPatch: () => {}, + surfaceFile: () => {}, openFile: () => {}, toggleFile: () => {}, toggleRows: () => {}, @@ -880,6 +881,14 @@ describe('FilePatchView', function() { assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'line'); }); + it('surfaces focus to the git tab', function() { + const surfaceFile = sinon.spy(); + const wrapper = mount(buildApp({surfaceFile})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:surface-file'); + assert.isTrue(surfaceFile.called); + }); + describe('hunk mode navigation', function() { beforeEach(function() { filePatch = buildFilePatch([{ From 156ca3ccb46edc453af4ae26e271c844aa6fb4be Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 4 Oct 2018 14:56:05 -0400 Subject: [PATCH 0426/4053] Prepare 0.19.1-6 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index aad8bb2bb8..d352d192e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.19.1-5", + "version": "0.19.1-6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 63f3b2b36f..8f8f654349 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.19.1-5", + "version": "0.19.1-6", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From 7f03c6623f1fa30ae1e64c0982f4b86856ab7260 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 4 Oct 2018 14:01:18 -0700 Subject: [PATCH 0427/4053] Micro-optimization getting selected item count Co-Authored-By: Ash Wilson --- lib/views/staging-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 2ad8e02b5d..fd562d9402 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -611,7 +611,7 @@ export default class StagingView extends React.Component { const menu = new Menu(); - const selectedItemCount = this.getSelectedItemFilePaths().length; + const selectedItemCount = this.state.selection.getSelectedItems().size; const pluralization = selectedItemCount > 1 ? 's' : ''; menu.append(new MenuItem({ From 651af9c234e7dc3729bc0cd314a3935a560c3981 Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Fri, 5 Oct 2018 11:45:49 +0530 Subject: [PATCH 0428/4053] Remove unnecessary hook from fixture repo --- .../dot-git/hooks/fsmonitor-watchman.sample | 114 ------------------ 1 file changed, 114 deletions(-) delete mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/fsmonitor-watchman.sample diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/fsmonitor-watchman.sample b/test/fixtures/repo-commit-template/dot-git/hooks/fsmonitor-watchman.sample deleted file mode 100755 index e673bb3980..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/hooks/fsmonitor-watchman.sample +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use IPC::Open2; - -# An example hook script to integrate Watchman -# (https://facebook.github.io/watchman/) with git to speed up detecting -# new and modified files. -# -# The hook is passed a version (currently 1) and a time in nanoseconds -# formatted as a string and outputs to stdout all files that have been -# modified since the given time. Paths must be relative to the root of -# the working tree and separated by a single NUL. -# -# To enable this hook, rename this file to "query-watchman" and set -# 'git config core.fsmonitor .git/hooks/query-watchman' -# -my ($version, $time) = @ARGV; - -# Check the hook interface version - -if ($version == 1) { - # convert nanoseconds to seconds - $time = int $time / 1000000000; -} else { - die "Unsupported query-fsmonitor hook version '$version'.\n" . - "Falling back to scanning...\n"; -} - -my $git_work_tree; -if ($^O =~ 'msys' || $^O =~ 'cygwin') { - $git_work_tree = Win32::GetCwd(); - $git_work_tree =~ tr/\\/\//; -} else { - require Cwd; - $git_work_tree = Cwd::cwd(); -} - -my $retry = 1; - -launch_watchman(); - -sub launch_watchman { - - my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') - or die "open2() failed: $!\n" . - "Falling back to scanning...\n"; - - # In the query expression below we're asking for names of files that - # changed since $time but were not transient (ie created after - # $time but no longer exist). - # - # To accomplish this, we're using the "since" generator to use the - # recency index to select candidate nodes and "fields" to limit the - # output to file names only. Then we're using the "expression" term to - # further constrain the results. - # - # The category of transient files that we want to ignore will have a - # creation clock (cclock) newer than $time_t value and will also not - # currently exist. - - my $query = <<" END"; - ["query", "$git_work_tree", { - "since": $time, - "fields": ["name"], - "expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]] - }] - END - - print CHLD_IN $query; - close CHLD_IN; - my $response = do {local $/; }; - - die "Watchman: command returned no output.\n" . - "Falling back to scanning...\n" if $response eq ""; - die "Watchman: command returned invalid output: $response\n" . - "Falling back to scanning...\n" unless $response =~ /^\{/; - - my $json_pkg; - eval { - require JSON::XS; - $json_pkg = "JSON::XS"; - 1; - } or do { - require JSON::PP; - $json_pkg = "JSON::PP"; - }; - - my $o = $json_pkg->new->utf8->decode($response); - - if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) { - print STDERR "Adding '$git_work_tree' to watchman's watch list.\n"; - $retry--; - qx/watchman watch "$git_work_tree"/; - die "Failed to make watchman watch '$git_work_tree'.\n" . - "Falling back to scanning...\n" if $? != 0; - - # Watchman will always return all files on the first query so - # return the fast "everything is dirty" flag to git and do the - # Watchman query just to get it over with now so we won't pay - # the cost in git to look up each individual file. - print "/\0"; - eval { launch_watchman() }; - exit 0; - } - - die "Watchman: $o->{error}.\n" . - "Falling back to scanning...\n" if $o->{error}; - - binmode STDOUT, ":utf8"; - local $, = "\0"; - print @{$o->{files}}; -} From 9320b81db7792858a6a72e7bd54a0f80e7c43e74 Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Fri, 5 Oct 2018 12:08:53 +0530 Subject: [PATCH 0429/4053] Update commit.template tests, use async assert --- test/controllers/commit-controller.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index c643191fc0..0bcc6679f7 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -78,7 +78,7 @@ describe('CommitController', function() { wrapper.setProps({repository: repository1}); assert.equal(wrapper.instance().getCommitMessage(), 'message 1'); wrapper.setProps({repository: repository3}); - assert.equal(wrapper.instance().getCommitMessage(), templateCommitMessage); + await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); }); @@ -89,7 +89,7 @@ describe('CommitController', function() { const templateCommitMessage = await repository.git.getCommitMessageFromTemplate(); app = React.cloneElement(app, {repository}); const wrapper = shallow(app, {disableLifecycleMethods: true}); - assert.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); + await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); }); }); From e51650674ea1c53eb4de6dacfe49f6693a167c0d Mon Sep 17 00:00:00 2001 From: Gaurav Chikhale Date: Fri, 5 Oct 2018 12:33:47 +0530 Subject: [PATCH 0430/4053] Remove all other unnecessary hooks template from fixture --- .../dot-git/hooks/applypatch-msg.sample | 15 -- .../dot-git/hooks/commit-msg.sample | 24 --- .../dot-git/hooks/post-update.sample | 8 - .../dot-git/hooks/pre-applypatch.sample | 14 -- .../dot-git/hooks/pre-commit.sample | 49 ----- .../dot-git/hooks/pre-push.sample | 53 ------ .../dot-git/hooks/pre-rebase.sample | 169 ------------------ .../dot-git/hooks/pre-receive.sample | 24 --- .../dot-git/hooks/prepare-commit-msg.sample | 42 ----- .../dot-git/hooks/update.sample | 128 ------------- 10 files changed, 526 deletions(-) delete mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/applypatch-msg.sample delete mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/commit-msg.sample delete mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/post-update.sample delete mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/pre-applypatch.sample delete mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/pre-commit.sample delete mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/pre-push.sample delete mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/pre-rebase.sample delete mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/pre-receive.sample delete mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/prepare-commit-msg.sample delete mode 100755 test/fixtures/repo-commit-template/dot-git/hooks/update.sample diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/applypatch-msg.sample b/test/fixtures/repo-commit-template/dot-git/hooks/applypatch-msg.sample deleted file mode 100755 index a5d7b84a67..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/hooks/applypatch-msg.sample +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message taken by -# applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. The hook is -# allowed to edit the commit message file. -# -# To enable this hook, rename this file to "applypatch-msg". - -. git-sh-setup -commitmsg="$(git rev-parse --git-path hooks/commit-msg)" -test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} -: diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/commit-msg.sample b/test/fixtures/repo-commit-template/dot-git/hooks/commit-msg.sample deleted file mode 100755 index b58d1184a9..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/hooks/commit-msg.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message. -# Called by "git commit" with one argument, the name of the file -# that has the commit message. The hook should exit with non-zero -# status after issuing an appropriate message if it wants to stop the -# commit. The hook is allowed to edit the commit message file. -# -# To enable this hook, rename this file to "commit-msg". - -# Uncomment the below to add a Signed-off-by line to the message. -# Doing this in a hook is a bad idea in general, but the prepare-commit-msg -# hook is more suited to it. -# -# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" - -# This example catches duplicate Signed-off-by lines. - -test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { - echo >&2 Duplicate Signed-off-by lines. - exit 1 -} diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/post-update.sample b/test/fixtures/repo-commit-template/dot-git/hooks/post-update.sample deleted file mode 100755 index ec17ec1939..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/hooks/post-update.sample +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare a packed repository for use over -# dumb transports. -# -# To enable this hook, rename this file to "post-update". - -exec git update-server-info diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/pre-applypatch.sample b/test/fixtures/repo-commit-template/dot-git/hooks/pre-applypatch.sample deleted file mode 100755 index 4142082bcb..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/hooks/pre-applypatch.sample +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed -# by applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-applypatch". - -. git-sh-setup -precommit="$(git rev-parse --git-path hooks/pre-commit)" -test -x "$precommit" && exec "$precommit" ${1+"$@"} -: diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/pre-commit.sample b/test/fixtures/repo-commit-template/dot-git/hooks/pre-commit.sample deleted file mode 100755 index 68d62d5446..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/hooks/pre-commit.sample +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-commit". - -if git rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 -fi - -# If you want to allow non-ASCII filenames set this variable to true. -allownonascii=$(git config --bool hooks.allownonascii) - -# Redirect output to stderr. -exec 1>&2 - -# Cross platform projects tend to avoid non-ASCII filenames; prevent -# them from being added to the repository. We exploit the fact that the -# printable range starts at the space character and ends with tilde. -if [ "$allownonascii" != "true" ] && - # Note that the use of brackets around a tr range is ok here, (it's - # even required, for portability to Solaris 10's /usr/bin/tr), since - # the square bracket bytes happen to fall in the designated range. - test $(git diff --cached --name-only --diff-filter=A -z $against | - LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 -then - cat <<\EOF -Error: Attempt to add a non-ASCII file name. - -This can cause problems if you want to work with people on other platforms. - -To be portable it is advisable to rename the file. - -If you know what you are doing you can disable this check using: - - git config hooks.allownonascii true -EOF - exit 1 -fi - -# If there are whitespace errors, print the offending file names and fail. -exec git diff-index --check --cached $against -- diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/pre-push.sample b/test/fixtures/repo-commit-template/dot-git/hooks/pre-push.sample deleted file mode 100755 index 6187dbf439..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/hooks/pre-push.sample +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/sh - -# An example hook script to verify what is about to be pushed. Called by "git -# push" after it has checked the remote status, but before anything has been -# pushed. If this script exits with a non-zero status nothing will be pushed. -# -# This hook is called with the following parameters: -# -# $1 -- Name of the remote to which the push is being done -# $2 -- URL to which the push is being done -# -# If pushing without using a named remote those arguments will be equal. -# -# Information about the commits which are being pushed is supplied as lines to -# the standard input in the form: -# -# -# -# This sample shows how to prevent push of commits where the log message starts -# with "WIP" (work in progress). - -remote="$1" -url="$2" - -z40=0000000000000000000000000000000000000000 - -while read local_ref local_sha remote_ref remote_sha -do - if [ "$local_sha" = $z40 ] - then - # Handle delete - : - else - if [ "$remote_sha" = $z40 ] - then - # New branch, examine all commits - range="$local_sha" - else - # Update to existing branch, examine new commits - range="$remote_sha..$local_sha" - fi - - # Check for WIP commit - commit=`git rev-list -n 1 --grep '^WIP' "$range"` - if [ -n "$commit" ] - then - echo >&2 "Found WIP commit in $local_ref, not pushing" - exit 1 - fi - fi -done - -exit 0 diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/pre-rebase.sample b/test/fixtures/repo-commit-template/dot-git/hooks/pre-rebase.sample deleted file mode 100755 index 6cbef5c370..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/hooks/pre-rebase.sample +++ /dev/null @@ -1,169 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006, 2008 Junio C Hamano -# -# The "pre-rebase" hook is run just before "git rebase" starts doing -# its job, and can prevent the command from running by exiting with -# non-zero status. -# -# The hook is called with the following parameters: -# -# $1 -- the upstream the series was forked from. -# $2 -- the branch being rebased (or empty when rebasing the current branch). -# -# This sample shows how to prevent topic branches that are already -# merged to 'next' branch from getting rebased, because allowing it -# would result in rebasing already published history. - -publish=next -basebranch="$1" -if test "$#" = 2 -then - topic="refs/heads/$2" -else - topic=`git symbolic-ref HEAD` || - exit 0 ;# we do not interrupt rebasing detached HEAD -fi - -case "$topic" in -refs/heads/??/*) - ;; -*) - exit 0 ;# we do not interrupt others. - ;; -esac - -# Now we are dealing with a topic branch being rebased -# on top of master. Is it OK to rebase it? - -# Does the topic really exist? -git show-ref -q "$topic" || { - echo >&2 "No such branch $topic" - exit 1 -} - -# Is topic fully merged to master? -not_in_master=`git rev-list --pretty=oneline ^master "$topic"` -if test -z "$not_in_master" -then - echo >&2 "$topic is fully merged to master; better remove it." - exit 1 ;# we could allow it, but there is no point. -fi - -# Is topic ever merged to next? If so you should not be rebasing it. -only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` -only_next_2=`git rev-list ^master ${publish} | sort` -if test "$only_next_1" = "$only_next_2" -then - not_in_topic=`git rev-list "^$topic" master` - if test -z "$not_in_topic" - then - echo >&2 "$topic is already up to date with master" - exit 1 ;# we could allow it, but there is no point. - else - exit 0 - fi -else - not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` - /usr/bin/perl -e ' - my $topic = $ARGV[0]; - my $msg = "* $topic has commits already merged to public branch:\n"; - my (%not_in_next) = map { - /^([0-9a-f]+) /; - ($1 => 1); - } split(/\n/, $ARGV[1]); - for my $elem (map { - /^([0-9a-f]+) (.*)$/; - [$1 => $2]; - } split(/\n/, $ARGV[2])) { - if (!exists $not_in_next{$elem->[0]}) { - if ($msg) { - print STDERR $msg; - undef $msg; - } - print STDERR " $elem->[1]\n"; - } - } - ' "$topic" "$not_in_next" "$not_in_master" - exit 1 -fi - -<<\DOC_END - -This sample hook safeguards topic branches that have been -published from being rewound. - -The workflow assumed here is: - - * Once a topic branch forks from "master", "master" is never - merged into it again (either directly or indirectly). - - * Once a topic branch is fully cooked and merged into "master", - it is deleted. If you need to build on top of it to correct - earlier mistakes, a new topic branch is created by forking at - the tip of the "master". This is not strictly necessary, but - it makes it easier to keep your history simple. - - * Whenever you need to test or publish your changes to topic - branches, merge them into "next" branch. - -The script, being an example, hardcodes the publish branch name -to be "next", but it is trivial to make it configurable via -$GIT_DIR/config mechanism. - -With this workflow, you would want to know: - -(1) ... if a topic branch has ever been merged to "next". Young - topic branches can have stupid mistakes you would rather - clean up before publishing, and things that have not been - merged into other branches can be easily rebased without - affecting other people. But once it is published, you would - not want to rewind it. - -(2) ... if a topic branch has been fully merged to "master". - Then you can delete it. More importantly, you should not - build on top of it -- other people may already want to - change things related to the topic as patches against your - "master", so if you need further changes, it is better to - fork the topic (perhaps with the same name) afresh from the - tip of "master". - -Let's look at this example: - - o---o---o---o---o---o---o---o---o---o "next" - / / / / - / a---a---b A / / - / / / / - / / c---c---c---c B / - / / / \ / - / / / b---b C \ / - / / / / \ / - ---o---o---o---o---o---o---o---o---o---o---o "master" - - -A, B and C are topic branches. - - * A has one fix since it was merged up to "next". - - * B has finished. It has been fully merged up to "master" and "next", - and is ready to be deleted. - - * C has not merged to "next" at all. - -We would want to allow C to be rebased, refuse A, and encourage -B to be deleted. - -To compute (1): - - git rev-list ^master ^topic next - git rev-list ^master next - - if these match, topic has not merged in next at all. - -To compute (2): - - git rev-list master..topic - - if this is empty, it is fully merged to "master". - -DOC_END diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/pre-receive.sample b/test/fixtures/repo-commit-template/dot-git/hooks/pre-receive.sample deleted file mode 100755 index a1fd29ec14..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/hooks/pre-receive.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to make use of push options. -# The example simply echoes all push options that start with 'echoback=' -# and rejects all pushes when the "reject" push option is used. -# -# To enable this hook, rename this file to "pre-receive". - -if test -n "$GIT_PUSH_OPTION_COUNT" -then - i=0 - while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" - do - eval "value=\$GIT_PUSH_OPTION_$i" - case "$value" in - echoback=*) - echo "echo from the pre-receive-hook: ${value#*=}" >&2 - ;; - reject) - exit 1 - esac - i=$((i + 1)) - done -fi diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/prepare-commit-msg.sample b/test/fixtures/repo-commit-template/dot-git/hooks/prepare-commit-msg.sample deleted file mode 100755 index 10fa14c5ab..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/hooks/prepare-commit-msg.sample +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare the commit log message. -# Called by "git commit" with the name of the file that has the -# commit message, followed by the description of the commit -# message's source. The hook's purpose is to edit the commit -# message file. If the hook fails with a non-zero status, -# the commit is aborted. -# -# To enable this hook, rename this file to "prepare-commit-msg". - -# This hook includes three examples. The first one removes the -# "# Please enter the commit message..." help message. -# -# The second includes the output of "git diff --name-status -r" -# into the message, just before the "git status" output. It is -# commented because it doesn't cope with --amend or with squashed -# commits. -# -# The third example adds a Signed-off-by line to the message, that can -# still be edited. This is rarely a good idea. - -COMMIT_MSG_FILE=$1 -COMMIT_SOURCE=$2 -SHA1=$3 - -/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" - -# case "$COMMIT_SOURCE,$SHA1" in -# ,|template,) -# /usr/bin/perl -i.bak -pe ' -# print "\n" . `git diff --cached --name-status -r` -# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; -# *) ;; -# esac - -# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" -# if test -z "$COMMIT_SOURCE" -# then -# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" -# fi diff --git a/test/fixtures/repo-commit-template/dot-git/hooks/update.sample b/test/fixtures/repo-commit-template/dot-git/hooks/update.sample deleted file mode 100755 index 80ba94135c..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/hooks/update.sample +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/sh -# -# An example hook script to block unannotated tags from entering. -# Called by "git receive-pack" with arguments: refname sha1-old sha1-new -# -# To enable this hook, rename this file to "update". -# -# Config -# ------ -# hooks.allowunannotated -# This boolean sets whether unannotated tags will be allowed into the -# repository. By default they won't be. -# hooks.allowdeletetag -# This boolean sets whether deleting tags will be allowed in the -# repository. By default they won't be. -# hooks.allowmodifytag -# This boolean sets whether a tag may be modified after creation. By default -# it won't be. -# hooks.allowdeletebranch -# This boolean sets whether deleting branches will be allowed in the -# repository. By default they won't be. -# hooks.denycreatebranch -# This boolean sets whether remotely creating branches will be denied -# in the repository. By default this is allowed. -# - -# --- Command line -refname="$1" -oldrev="$2" -newrev="$3" - -# --- Safety check -if [ -z "$GIT_DIR" ]; then - echo "Don't run this script from the command line." >&2 - echo " (if you want, you could supply GIT_DIR then run" >&2 - echo " $0 )" >&2 - exit 1 -fi - -if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then - echo "usage: $0 " >&2 - exit 1 -fi - -# --- Config -allowunannotated=$(git config --bool hooks.allowunannotated) -allowdeletebranch=$(git config --bool hooks.allowdeletebranch) -denycreatebranch=$(git config --bool hooks.denycreatebranch) -allowdeletetag=$(git config --bool hooks.allowdeletetag) -allowmodifytag=$(git config --bool hooks.allowmodifytag) - -# check for no description -projectdesc=$(sed -e '1q' "$GIT_DIR/description") -case "$projectdesc" in -"Unnamed repository"* | "") - echo "*** Project description file hasn't been set" >&2 - exit 1 - ;; -esac - -# --- Check types -# if $newrev is 0000...0000, it's a commit to delete a ref. -zero="0000000000000000000000000000000000000000" -if [ "$newrev" = "$zero" ]; then - newrev_type=delete -else - newrev_type=$(git cat-file -t $newrev) -fi - -case "$refname","$newrev_type" in - refs/tags/*,commit) - # un-annotated tag - short_refname=${refname##refs/tags/} - if [ "$allowunannotated" != "true" ]; then - echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 - echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 - exit 1 - fi - ;; - refs/tags/*,delete) - # delete tag - if [ "$allowdeletetag" != "true" ]; then - echo "*** Deleting a tag is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/tags/*,tag) - # annotated tag - if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 - then - echo "*** Tag '$refname' already exists." >&2 - echo "*** Modifying a tag is not allowed in this repository." >&2 - exit 1 - fi - ;; - refs/heads/*,commit) - # branch - if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then - echo "*** Creating a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/heads/*,delete) - # delete branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/remotes/*,commit) - # tracking branch - ;; - refs/remotes/*,delete) - # delete tracking branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a tracking branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - *) - # Anything else (is there anything else?) - echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 - exit 1 - ;; -esac - -# --- Finished -exit 0 From 32b0ada679c58579773ad6a74c2597842a938710 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 5 Oct 2018 00:12:57 -0700 Subject: [PATCH 0431/4053] Add eventSource metadata to `undo-last-discard` event reporting Co-Authored-By: Tilde Ann Thurium --- lib/controllers/file-patch-controller.js | 3 +- lib/views/file-patch-view.js | 22 ++++++++++++-- lib/views/staging-view.js | 30 +++++++++++++++---- .../controllers/file-patch-controller.test.js | 1 + test/views/staging-view.test.js | 9 +++--- 5 files changed, 50 insertions(+), 15 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 5097e14e8c..bedfc51b11 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -539,10 +539,11 @@ export default class FilePatchController extends React.Component { return this.props.discardLines(this.state.filePatch, lines, this.repositoryObserver.getActiveModel()); } - undoLastDiscard() { + undoLastDiscard({eventSource} = {}) { addEvent('undo-last-discard', { package: 'github', component: 'FilePatchController', + eventSource, }); return this.props.undoLastDiscard(this.props.filePath, this.repositoryObserver.getActiveModel()); } diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index efd9e7edf9..0be2ed0152 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -265,7 +265,7 @@ export default class FilePatchView extends React.Component { this.props.hasUndoHistory && this.props.undoLastDiscard()} + callback={this.undoLastDiscardFromCoreUndo} /> {this.props.executableModeChange && this.props.hasUndoHistory && this.props.undoLastDiscard()} + callback={this.undoLastDiscardFromCommand} /> @@ -290,6 +290,22 @@ export default class FilePatchView extends React.Component { ); } + undoLastDiscardFromCommand = () => { + if (this.props.hasUndoHistory) { + this.props.undoLastDiscard({eventSource: {command: 'github:undo-last-discard-in-diff-view'}}); + } + } + + undoLastDiscardFromCoreUndo = () => { + if (this.props.hasUndoHistory) { + this.props.undoLastDiscard({eventSource: {command: 'core:undo'}}); + } + } + + undoLastDiscardFromButton = () => { + this.props.undoLastDiscard({eventSource: 'button'}); + } + renderButtonGroup() { const unstaged = this.props.stagingStatus === 'unstaged'; @@ -298,7 +314,7 @@ export default class FilePatchView extends React.Component { {this.props.hasUndoHistory && unstaged ? ( ) : null} diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index fd562d9402..e3bc8368ae 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -85,7 +85,6 @@ export default class StagingView extends React.Component { 'stageAll', 'unstageAll', 'stageAllMergeConflicts', 'discardAll', 'confirmSelectedItems', 'selectAll', 'selectFirst', 'selectLast', 'diveIntoSelection', 'showDiffView', 'showBulkResolveMenu', 'showActionsMenu', 'resolveCurrentAsOurs', 'resolveCurrentAsTheirs', 'quietlySelectItem', 'didChangeSelectedItems', - 'undoLastDiscard', ); this.subs = new CompositeDisposable( @@ -274,18 +273,36 @@ export default class StagingView extends React.Component { - + - + ); } + undoLastDiscardFromCoreUndo = () => { + this.undoLastDiscard({eventSource: {command: 'core:undo'}}); + } + + undoLastDiscardFromGitHubCommand = () => { + this.undoLastDiscard({eventSource: {command: 'github:undo-last-discard-in-git-tab'}}); + } + + undoLastDiscardFromButton = () => { + this.undoLastDiscard({eventSource: 'button'}); + } + + undoLastDiscardFromHeaderMenu = () => { + this.undoLastDiscard({eventSource: 'header-menu'}); + } + renderActionsMenu() { if (this.props.unstagedChanges.length || this.props.hasUndoHistory) { return ( @@ -302,7 +319,7 @@ export default class StagingView extends React.Component { renderUndoButton() { return ( + onClick={this.undoLastDiscardFromButton}>Undo Discard ); } @@ -628,7 +645,7 @@ export default class StagingView extends React.Component { menu.append(new MenuItem({ label: 'Undo Last Discard', - click: () => this.undoLastDiscard(), + click: this.undoLastDiscardFromHeaderMenu, enabled: this.props.hasUndoHistory, })); @@ -841,7 +858,7 @@ export default class StagingView extends React.Component { } } - undoLastDiscard() { + undoLastDiscard({eventSource} = {}) { if (!this.props.hasUndoHistory) { return; } @@ -849,6 +866,7 @@ export default class StagingView extends React.Component { addEvent('undo-last-discard', { package: 'github', component: 'StagingView', + eventSource, }); this.props.undoLastDiscard(); diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index 7618cac6f2..e58017013c 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -321,6 +321,7 @@ describe('FilePatchController', function() { assert.isTrue(reporterProxy.addEvent.calledWith('undo-last-discard', { package: 'github', component: 'FilePatchController', + eventSource: undefined, })); }); }); diff --git a/test/views/staging-view.test.js b/test/views/staging-view.test.js index e046123b7c..efdd0a125c 100644 --- a/test/views/staging-view.test.js +++ b/test/views/staging-view.test.js @@ -822,15 +822,14 @@ describe('StagingView', function() { describe('undoLastDiscard()', function() { it('records an event', function() { - const wrapper = mount(app); + const wrapper = mount(React.cloneElement(app, {hasUndoHistory: true})); sinon.stub(reporterProxy, 'addEvent'); sinon.stub(wrapper.instance(), 'getSelectedItemFilePaths').returns(['a.txt', 'b.txt']); - wrapper.instance().discardChanges(); - assert.isTrue(reporterProxy.addEvent.calledWith('discard-unstaged-changes', { + wrapper.instance().undoLastDiscard(); + assert.isTrue(reporterProxy.addEvent.calledWith('undo-last-discard', { package: 'github', component: 'StagingView', - fileCount: 2, - type: 'selected', + eventSource: undefined, })); }); }); From d53f448efa1ff9217f1f62f85773d36443b1226e Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 5 Oct 2018 17:31:21 +0900 Subject: [PATCH 0432/4053] Update PR list --- docs/rfcs/003-pull-request-review.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index 06eb3bd368..e5295520d6 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -20,7 +20,9 @@ Peer review is also a critical part of the path to acceptance for pull requests Review progress is indicated for open pull requests listed in the GitHub panel. The pull request corresponding to the checked out branch gets special treatment in it's own section at the top of the list. -pull request list with review progress bars +![image](https://user-images.githubusercontent.com/378023/46524357-89bf6580-c8c3-11e8-8e2d-ea02d5a1f278.png) + +> :construction: This mockup is still WIP and isn't shown for the rest of this RFC. Clicking a pull request in the list opens a `PullRequestDetailItem` in the workspace center. From 53319b247faed1770738ea9e7b1beec9ef968515 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 5 Oct 2018 21:03:32 +0900 Subject: [PATCH 0433/4053] Let's keep it --- docs/rfcs/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index e5295520d6..f69046a46c 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -22,7 +22,7 @@ Review progress is indicated for open pull requests listed in the GitHub panel. ![image](https://user-images.githubusercontent.com/378023/46524357-89bf6580-c8c3-11e8-8e2d-ea02d5a1f278.png) -> :construction: This mockup is still WIP and isn't shown for the rest of this RFC. +> :construction: This mockup is still WIP and probably will change. Clicking a pull request in the list opens a `PullRequestDetailItem` in the workspace center. From 27d212c720e05e564a580235975a8e66b54b74b8 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 5 Oct 2018 21:07:41 +0900 Subject: [PATCH 0434/4053] Remove open-issue-or-pull-request command Since there aren't any changes. --- docs/rfcs/003-pull-request-review.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index f69046a46c..77e84b03c6 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -26,10 +26,6 @@ Review progress is indicated for open pull requests listed in the GitHub panel. Clicking a pull request in the list opens a `PullRequestDetailItem` in the workspace center. -For PRs that are not listed in the panel, users can use the `github:open-issue-or-pull-request` command: - -opening a pull request by URL - ### PullRequestDetailItem Each `PullRequestDetailItem` opened on a pull request displays the full, multi-file diff associated with the pull request. Review comments are shown within the diff. See ["Comment decorations"](#comment-decorations) for description of review comments. From 767464cf74ea3448d82cd174a6969539401c7d09 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 5 Oct 2018 21:18:18 +0900 Subject: [PATCH 0435/4053] Update PullRequestDetailItem --- docs/rfcs/003-pull-request-review.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index 77e84b03c6..1bbe0c885b 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -28,15 +28,13 @@ Clicking a pull request in the list opens a `PullRequestDetailItem` in the works ### PullRequestDetailItem -Each `PullRequestDetailItem` opened on a pull request displays the full, multi-file diff associated with the pull request. Review comments are shown within the diff. See ["Comment decorations"](#comment-decorations) for description of review comments. +Each `PullRequestDetailItem` has the full, multi-file diff associated with the pull request displayed under the "Files" tab. Review comments are shown within the diff. See ["Comment decorations"](#comment-decorations) for description of review comments. -![pull request detail item](https://user-images.githubusercontent.com/7910250/46391711-1df6b600-c693-11e8-87f3-ad4cdbe8ebd8.png) - -> :construction: Update mock to have "Start review button" and "Add single comment" +![pull request detail item](https://user-images.githubusercontent.com/378023/46534569-8fc53e80-c8e3-11e8-8721-b38462b51cc7.png) Diffs are editable _only_ if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. -A panel at the bottom of the pane offers various options for sorting and filtering the diff. It also has a "Review Changes" button. +A panel at the bottom of the pane shows the progress for resolved review comments. It also has a "Review Changes" button to create a new review. #### Sort Options From ed371349aa361282027391cac96c8894a397b66f Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 5 Oct 2018 22:06:46 +0900 Subject: [PATCH 0436/4053] More edits --- docs/rfcs/003-pull-request-review.md | 55 ++++++++++++++++------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index 1bbe0c885b..63b2c112f0 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -26,55 +26,62 @@ Review progress is indicated for open pull requests listed in the GitHub panel. Clicking a pull request in the list opens a `PullRequestDetailItem` in the workspace center. + ### PullRequestDetailItem -Each `PullRequestDetailItem` has the full, multi-file diff associated with the pull request displayed under the "Files" tab. Review comments are shown within the diff. See ["Comment decorations"](#comment-decorations) for description of review comments. +#### Header -![pull request detail item](https://user-images.githubusercontent.com/378023/46534569-8fc53e80-c8e3-11e8-8721-b38462b51cc7.png) +At the top of each `PullRequestDetailItem` is a summary about the pull request, followed by the tabs to switch between different sub-views. -Diffs are editable _only_ if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. +- Overview +- Files (**new**) +- Reviews (**new**) +- Commits +- Build Status -A panel at the bottom of the pane shows the progress for resolved review comments. It also has a "Review Changes" button to create a new review. +After the tabs users can search or filter. The default is to show all files, all authors, and unresolved comments. Filtering based on file type, author, search term makes it possible to narrow down a long list of diffs. Toggling comments or collapse files is also possible. -#### Sort Options +![header](https://user-images.githubusercontent.com/378023/46536358-3829d180-c8e9-11e8-9167-3d1003ab566b.png) -sort by +#### Footer -> :construction: We probably want to find better verbiage than "sort". Let's also consider a dropdown menu UX to select different views of the data. +A panel at the bottom of the pane shows the progress for resolved review comments. It also has a "Review Changes" button to create a new review. + +![reviews panel](https://user-images.githubusercontent.com/378023/46536010-17ad4780-c8e8-11e8-8338-338bb592efc5.png) -The default view is sorted by files. This is akin to the "Files changed" tab on dotcom. It displays the diff for all changed files in the PR. +This panel is persistent throught all sub-views. It allows creating a reviews no matter where you are. Below shown with the existing sub-views: -Sorting by reviews is akin to the review summaries that appear on the "Conversation" tab on dotcom. The comments are displayed grouped by review along with some context lines. +Overview | Commits | Build Status +--- | --- | --- +![overview](https://user-images.githubusercontent.com/378023/46535907-ca30da80-c8e7-11e8-9401-2b8660d56c25.png) | ![commits](https://user-images.githubusercontent.com/378023/46535908-ca30da80-c8e7-11e8-87ca-01637f2554b6.png) | ![build status](https://user-images.githubusercontent.com/378023/46535909-cac97100-c8e7-11e8-8813-47fdaa3ece57.png) -![sorted by reviews](https://user-images.githubusercontent.com/7910250/46394598-6ebfdc00-c69e-11e8-84eb-39ccbcccf736.png) -> :construction: Show multiple reviews stacked +### Files -Sorting by commits is akin to the "Commits" tab on dotcom. A list of commits is displayed in chronological order, oldest commit on top. Clicking a commit expands the diff contents below. If there is a commit message body this is displayed as well. Commit diffs are not editable. +Under the "Files" tab the full, multi-file diff associated with the pull request is displayed. This is akin to the "Files changed" tab on dotcom. It displays the diff for all changed files in the PR. -> :construction: Include commit sort mockup +![files](https://user-images.githubusercontent.com/378023/46536560-d3bb4200-c8e9-11e8-9764-dca0b84245cf.png) -A banner at the bottom of the pane offers navigation to individual files within the diff and to individual review comments, allows each review to be hidden or shown with a filter control, and shows a progress bar that counts "resolved" review comments. The banner remains visible as you scroll the pane. +Review comments are shown within the diff. See ["Comment decorations"](#comment-decorations) for description of review comments. -#### Filter Options +![review comment](https://user-images.githubusercontent.com/378023/46534569-8fc53e80-c8e3-11e8-8721-b38462b51cc7.png) -filter options +Diffs are editable _only_ if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. -The default is to show all files, all authors, and unresolved comments. +### Reviews -Filtering based on file type limits the diff view to displaying only that file type. +Under the "Reviews" tab all reviews of a pull request get shown. This is akin to the review summaries that appear on the "Conversation" tab on dotcom. The comments are displayed grouped by review along with some context lines. -> :construction: Consider adding a "Find" input field that allows us to filter based on search term (which could be a file name, an author, a variable name, etc). +![reviews](https://user-images.githubusercontent.com/378023/46535563-c81a4c00-c8e6-11e8-9c0b-6ea575556101.png) -Clicking an author's avatar displays only their review information. +Comments can be collapsed to get a better overview. -Clicking "unresolved" shows only resolved comments, helping users stay focused on comments that need to be addressed. +![reviews collapsed](https://user-images.githubusercontent.com/378023/46535648-13345f00-c8e7-11e8-8912-ab8acf144e02.png) -Clicking "resolved" shows only resolved comments. This allows users to quickly see what has already been addressed. -Checking "all comments" shows both resolved and unresolved comments. +--- -Clicking "none" hides all comments, in the event that users want to see diff information only. +:point_down: TODO #### Submitting a Review From 139fc0387c53715cfd3e3ea72ada2a12052c27cf Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 5 Oct 2018 10:13:18 -0400 Subject: [PATCH 0437/4053] Copy-paste from the original issue --- docs/react-components.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 docs/react-components.md diff --git a/docs/react-components.md b/docs/react-components.md new file mode 100644 index 0000000000..f1c6877610 --- /dev/null +++ b/docs/react-components.md @@ -0,0 +1,33 @@ +# React Component Architecture + +This is a high-level summary of the organization and implementation of our React components. + +## Classification + +### Items + +**Items** are intended to be used as top-level components within subtrees that are rendered into some [Portal](https://reactjs.org/docs/portals.html) and passed to the Atom API, like pane items, dock items, or tooltips. They are mostly responsible for implementing the [Atom "item" contract](https://github.com/atom/atom/blob/a3631f0dafac146185289ac5e37eaff17b8b0209/src/workspace.js#L29-L174). + +These live within [`lib/items/`](lib/items), are tested within [`test/items/`](test/items), and are named with an `Item` suffix. Examples: `PullRequestDetailItem`, `FilePatchItem`. + +### Containers + +**Containers** are responsible for statefully fetching asynchronous data and rendering their children appropriately. They handle the logic for how subtrees handle loading operations (displaying a loading spinner, passing null objects to their children) and how errors are reported. Containers should mostly be thin wrappers around things like [``](lib/views/observe-model.js), [``](https://facebook.github.io/relay/docs/en/query-renderer.html), or [context](https://reactjs.org/docs/context.html) providers + + These live within [`lib/containers`](lib/containers), are tested within [`test/containers`](test/containers), and are named with a `Container` suffix. Examples: `PrInfoContainer`, `GitTabContainer`. + +### Controllers + +**Controllers** are responsible for implementing action methods and maintaining logical application state for some subtree of components. A Controller's `render()` method should only consist of passing props to a single child, usually a View. + +These live within [`lib/controllers`](lib/controllers), are tested within [`test/controllers`](test/controllers), and are named with a `Controller` suffix. Examples: `GitTabController`, `RecentCommitsController`, `ConflictController`. + +### Views + +**Views** are responsible for accepting props and rendering a DOM tree. View components should contain very few non-render methods and little state. + +These live within [`lib/views`](lib/views), are tested within [`test/views`](test/views), and are named with a `View` suffix. Examples: `GitTabView`, `GithubLoginView`. + +## Atom bridge + +## Atlas From 3e8f3ad2da2e3a3a31eebc3af6bc45b499110824 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 5 Oct 2018 10:19:37 -0400 Subject: [PATCH 0438/4053] Absolute paths maybe? --- docs/react-components.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/react-components.md b/docs/react-components.md index f1c6877610..9ad90a7b60 100644 --- a/docs/react-components.md +++ b/docs/react-components.md @@ -8,26 +8,26 @@ This is a high-level summary of the organization and implementation of our React **Items** are intended to be used as top-level components within subtrees that are rendered into some [Portal](https://reactjs.org/docs/portals.html) and passed to the Atom API, like pane items, dock items, or tooltips. They are mostly responsible for implementing the [Atom "item" contract](https://github.com/atom/atom/blob/a3631f0dafac146185289ac5e37eaff17b8b0209/src/workspace.js#L29-L174). -These live within [`lib/items/`](lib/items), are tested within [`test/items/`](test/items), and are named with an `Item` suffix. Examples: `PullRequestDetailItem`, `FilePatchItem`. +These live within [`lib/items/`](/lib/items), are tested within [`test/items/`](/test/items), and are named with an `Item` suffix. Examples: `PullRequestDetailItem`, `FilePatchItem`. ### Containers **Containers** are responsible for statefully fetching asynchronous data and rendering their children appropriately. They handle the logic for how subtrees handle loading operations (displaying a loading spinner, passing null objects to their children) and how errors are reported. Containers should mostly be thin wrappers around things like [``](lib/views/observe-model.js), [``](https://facebook.github.io/relay/docs/en/query-renderer.html), or [context](https://reactjs.org/docs/context.html) providers - These live within [`lib/containers`](lib/containers), are tested within [`test/containers`](test/containers), and are named with a `Container` suffix. Examples: `PrInfoContainer`, `GitTabContainer`. + These live within [`lib/containers`]/(lib/containers), are tested within [`test/containers`](/test/containers), and are named with a `Container` suffix. Examples: `PrInfoContainer`, `GitTabContainer`. ### Controllers **Controllers** are responsible for implementing action methods and maintaining logical application state for some subtree of components. A Controller's `render()` method should only consist of passing props to a single child, usually a View. -These live within [`lib/controllers`](lib/controllers), are tested within [`test/controllers`](test/controllers), and are named with a `Controller` suffix. Examples: `GitTabController`, `RecentCommitsController`, `ConflictController`. +These live within [`lib/controllers`](/lib/controllers), are tested within [`test/controllers`](/test/controllers), and are named with a `Controller` suffix. Examples: `GitTabController`, `RecentCommitsController`, `ConflictController`. ### Views **Views** are responsible for accepting props and rendering a DOM tree. View components should contain very few non-render methods and little state. -These live within [`lib/views`](lib/views), are tested within [`test/views`](test/views), and are named with a `View` suffix. Examples: `GitTabView`, `GithubLoginView`. ## Atom bridge +These live within [`lib/views`](/lib/views), are tested within [`test/views`](/test/views), and are named with a `View` suffix. Examples: `GitTabView`, `GithubLoginView`. ## Atlas From 3636a57e42c662c5a628856d63a393cbabb00b39 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 5 Oct 2018 10:19:51 -0400 Subject: [PATCH 0439/4053] Nah let's not get too ambitious here --- docs/react-components.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/react-components.md b/docs/react-components.md index 9ad90a7b60..224ac4d24c 100644 --- a/docs/react-components.md +++ b/docs/react-components.md @@ -26,8 +26,6 @@ These live within [`lib/controllers`](/lib/controllers), are tested within [`tes **Views** are responsible for accepting props and rendering a DOM tree. View components should contain very few non-render methods and little state. - -## Atom bridge These live within [`lib/views`](/lib/views), are tested within [`test/views`](/test/views), and are named with a `View` suffix. Examples: `GitTabView`, `GithubLoginView`. ## Atlas From 2e5bebf8014eaa9367a614a15b3ca2c7a823b94a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 5 Oct 2018 14:46:23 -0400 Subject: [PATCH 0440/4053] Let's make the Atlas its own top-level thing --- ...onents.md => react-component-classification.md} | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) rename docs/{react-components.md => react-component-classification.md} (95%) diff --git a/docs/react-components.md b/docs/react-component-classification.md similarity index 95% rename from docs/react-components.md rename to docs/react-component-classification.md index 224ac4d24c..4580547704 100644 --- a/docs/react-components.md +++ b/docs/react-component-classification.md @@ -1,31 +1,27 @@ -# React Component Architecture +# React Component Classification This is a high-level summary of the organization and implementation of our React components. -## Classification - -### Items +## Items **Items** are intended to be used as top-level components within subtrees that are rendered into some [Portal](https://reactjs.org/docs/portals.html) and passed to the Atom API, like pane items, dock items, or tooltips. They are mostly responsible for implementing the [Atom "item" contract](https://github.com/atom/atom/blob/a3631f0dafac146185289ac5e37eaff17b8b0209/src/workspace.js#L29-L174). These live within [`lib/items/`](/lib/items), are tested within [`test/items/`](/test/items), and are named with an `Item` suffix. Examples: `PullRequestDetailItem`, `FilePatchItem`. -### Containers +## Containers **Containers** are responsible for statefully fetching asynchronous data and rendering their children appropriately. They handle the logic for how subtrees handle loading operations (displaying a loading spinner, passing null objects to their children) and how errors are reported. Containers should mostly be thin wrappers around things like [``](lib/views/observe-model.js), [``](https://facebook.github.io/relay/docs/en/query-renderer.html), or [context](https://reactjs.org/docs/context.html) providers These live within [`lib/containers`]/(lib/containers), are tested within [`test/containers`](/test/containers), and are named with a `Container` suffix. Examples: `PrInfoContainer`, `GitTabContainer`. -### Controllers +## Controllers **Controllers** are responsible for implementing action methods and maintaining logical application state for some subtree of components. A Controller's `render()` method should only consist of passing props to a single child, usually a View. These live within [`lib/controllers`](/lib/controllers), are tested within [`test/controllers`](/test/controllers), and are named with a `Controller` suffix. Examples: `GitTabController`, `RecentCommitsController`, `ConflictController`. -### Views +## Views **Views** are responsible for accepting props and rendering a DOM tree. View components should contain very few non-render methods and little state. These live within [`lib/views`](/lib/views), are tested within [`test/views`](/test/views), and are named with a `View` suffix. Examples: `GitTabView`, `GithubLoginView`. - -## Atlas From 8bbf0405cc71f129abbf4fcb318043b2197d6933 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 5 Oct 2018 15:28:14 -0400 Subject: [PATCH 0441/4053] Begin the atlas :world_map: --- docs/react-component-atlas.md | 104 ++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 docs/react-component-atlas.md diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md new file mode 100644 index 0000000000..b67ea1f1c7 --- /dev/null +++ b/docs/react-component-atlas.md @@ -0,0 +1,104 @@ +# React Component Atlas + +This is a high-level overview of the structure of the React component tree that this package creates. It's intended _not_ to be comprehensive, but to give you an idea of where to find specific bits of functionality. + +> [``](/lib/controllers/root-controller.js) +> +> > [``](/lib/items/git-tab-item.js) +> > [``](/lib/containers/git-tab-container.js) +> > [``](/lib/controllers/git-tab-controller.js) +> > [``](/lib/views/git-tab-view.js) +> > +> > > [``](/lib/views/staging-view.js) +> > > +> > +> > > [``](/lib/controllers/commit-controller.js) +> > > [``](/lib/views/commit-view.js) +> > +> > > [``](/lib/controllers/recent-commits-controller.js) +> > > [`` ``](/lib/views/recent-commits-view.js) +> > > +> +> > [``](/lib/items/github-tab-item.js) +> > [``](/lib/containers/github-tab-container.js) +> > [``](/lib/controllers/github-tab-controller.js) +> > [``](/lib/views/github-tab-view.js) +> > +> > > [``](/lib/views/remote-selector-view.js) +> > > +> > +> > > [``](/lib/containers/remote-container.js) +> > > [``](/lib/controllers/remote-controller.js) +> > > +> > > > [``](/lib/controllers/issueish-searches-controller.js) +> > > > +> > > > > [``](/lib/containers/current-pull-request-container.js) +> > > > > +> > > > > > [``](/lib/views/create-pull-request-tile.js) +> > > > > > +> > > > > +> > > > > > [``](/lib/controllers/issueish-list-controller.js) +> > > > > > [``](/lib/views/issueish-list-view.js) +> > > > > > +> > > > +> > > > > [``](/lib/containers/issueish-search-container.js) +> > > > > +> > > > > > [``](/lib/controllers/issueish-list-controller.js) +> > > > > > [``](/lib/views/issueish-list-view.js) +> > > > > > +> +> > [``](/lib/controllers/file-patch-controller.js) +> > [``](/lib/views/file-patch-view.js) +> > +> > :construction: Being rewritten in [#1712](https://github.com/atom/github/pull/1512) :construction: +> +> > [``](/lib/items/issueish-detail-item.js) +> > [``](/lib/containers/issueish-detail-container.js) +> > [``](/lib/controllers/issueish-detail-controller.js) +> > [``](/lib/controllers/issueish-detail-controller.js) +> > +> > > [``](/lib/controllers/issue-timeline-controller.js) +> > > [``](/lib/views/issueish-timeline-view.js) +> > +> > > [``](/lib/controllers/pr-timeline-controller.js) +> > > [``](/lib/views/issueish-timeline-view.js) +> > > +> > +> > > [``](/lib/views/pr-statuses-view.js) +> > > +> > +> > > [``](/lib/views/pr-commits-view.js) +> > > [``](/lib/views/pr-commit-view.js) +> +> > [``](/lib/views/init-dialog.js) +> > [``](/lib/views/clone-dialog.js) +> > [``](/lib/views/open-issueish-dialog.js) +> > [``](/lib/views/credential-dialog.js) +> > +> +> > [``](/lib/controllers/repository-conflict-controller.js) +> > +> > > [``](/lib/controllers/editor-conflict-controller.js) +> > > +> > > > [``](/lib/controllers/conflict-controller.js) +> > > > +> +> > [``](/lib/controllers/status-bar-tile-controller.js) +> > +> > > [``](/lib/views/branch-view.js) +> > > +> > +> > > [``](/lib/views/branch-menu-view.js) +> > > +> > +> > > [``](/lib/views/push-pull-view.js) +> > > +> > +> > > [``](/lib/views/changed-files-count-view.js) +> > > +> > +> > > [``](/lib/views/changed-files-count-view.js) +> > > +> > +> > +> > From f759c78fa0028c28b5fa1925745523cc77c3fae8 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 5 Oct 2018 13:19:31 -0700 Subject: [PATCH 0442/4053] Add eventSource metadata to `discard-unstaged-changes` event recording --- lib/controllers/file-patch-controller.js | 3 +- lib/views/file-patch-view.js | 18 ++++++++---- lib/views/staging-view.js | 28 +++++++++++++------ .../controllers/file-patch-controller.test.js | 1 + test/views/staging-view.test.js | 2 ++ 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index bedfc51b11..a91e5e984b 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -530,11 +530,12 @@ export default class FilePatchController extends React.Component { return textEditor; } - discardLines(lines) { + discardLines(lines, {eventSource} = {}) { addEvent('discard-unstaged-changes', { package: 'github', component: 'FilePatchController', lineCount: lines.length, + eventSource, }); return this.props.discardLines(this.state.filePatch, lines, this.repositoryObserver.getActiveModel()); } diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 0be2ed0152..c7afd149bb 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -262,7 +262,7 @@ export default class FilePatchView extends React.Component { command="github:view-corresponding-diff" callback={() => this.props.isPartiallyStaged && this.props.didDiveIntoCorrespondingFilePatch()} /> - + { + this.props.undoLastDiscard({eventSource: 'button'}); + } + + discardSelectionFromCommand = () => { + this.discardSelection({eventSource: {command: 'github:discard-selected-lines'}}); + } + renderButtonGroup() { const unstaged = this.props.stagingStatus === 'unstaged'; @@ -675,10 +683,10 @@ export default class FilePatchView extends React.Component { didClickDiscardButtonForHunk(hunk) { if (this.state.selection.getSelectedHunks().has(hunk)) { - this.discardSelection(); + this.discardSelection({eventSource: 'button'}); } else { this.setState(prevState => ({selection: prevState.selection.selectHunk(hunk)}), () => { - this.discardSelection(); + this.discardSelection({eventSource: 'button'}); }); } } @@ -726,9 +734,9 @@ export default class FilePatchView extends React.Component { this.props.attemptSymlinkStageOperation(); } - discardSelection() { + discardSelection({eventSource} = {}) { const selectedLines = this.state.selection.getSelectedLines(); - return selectedLines.size ? this.props.discardLines(selectedLines) : null; + return selectedLines.size ? this.props.discardLines(selectedLines, {eventSource}) : null; } goToDiffLine(lineNumber) { diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index e3bc8368ae..f44e2a0025 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -272,15 +272,15 @@ export default class StagingView extends React.Component { - + - + @@ -291,7 +291,7 @@ export default class StagingView extends React.Component { this.undoLastDiscard({eventSource: {command: 'core:undo'}}); } - undoLastDiscardFromGitHubCommand = () => { + undoLastDiscardFromCommand = () => { this.undoLastDiscard({eventSource: {command: 'github:undo-last-discard-in-git-tab'}}); } @@ -303,6 +303,14 @@ export default class StagingView extends React.Component { this.undoLastDiscard({eventSource: 'header-menu'}); } + discardChangesFromCommand = () => { + this.discardChanges({eventSource: {command: 'github:discard-changes-in-selected-files'}}); + } + + discardAllFromCommand = () => { + this.discardAll({eventSource: {command: 'github:discard-all-changes'}}); + } + renderActionsMenu() { if (this.props.unstagedChanges.length || this.props.hasUndoHistory) { return ( @@ -414,13 +422,14 @@ export default class StagingView extends React.Component { return this.props.openFiles(filePaths); } - discardChanges() { + discardChanges({eventSource} = {}) { const filePaths = this.getSelectedItemFilePaths(); addEvent('discard-unstaged-changes', { package: 'github', component: 'StagingView', fileCount: filePaths.length, type: 'selected', + eventSource, }); return this.props.discardWorkDirChangesForPaths(filePaths); } @@ -488,7 +497,7 @@ export default class StagingView extends React.Component { return this.props.attemptFileStageOperation(filePaths, 'unstaged'); } - discardAll() { + discardAll({eventSource} = {}) { if (this.props.unstagedChanges.length === 0) { return null; } const filePaths = this.props.unstagedChanges.map(filePatch => filePatch.filePath); addEvent('discard-unstaged-changes', { @@ -496,6 +505,7 @@ export default class StagingView extends React.Component { component: 'StagingView', fileCount: filePaths.length, type: 'all', + eventSource, }); return this.props.discardWorkDirChangesForPaths(filePaths); } @@ -633,19 +643,19 @@ export default class StagingView extends React.Component { menu.append(new MenuItem({ label: 'Discard All Changes', - click: () => this.discardAll(), + click: () => this.discardAll({eventSource: 'header-menu'}), enabled: this.props.unstagedChanges.length > 0, })); menu.append(new MenuItem({ label: 'Discard Changes in Selected File' + pluralization, - click: () => this.discardChanges(), + click: () => this.discardChanges({eventSource: 'header-menu'}), enabled: !!(this.props.unstagedChanges.length && selectedItemCount), })); menu.append(new MenuItem({ label: 'Undo Last Discard', - click: this.undoLastDiscardFromHeaderMenu, + click: () => this.undoLastDiscard({eventSource: 'header-menu'}), enabled: this.props.hasUndoHistory, })); diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index e58017013c..dc2cf9db83 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -309,6 +309,7 @@ describe('FilePatchController', function() { package: 'github', component: 'FilePatchController', lineCount: 2, + eventSource: undefined, })); }); }); diff --git a/test/views/staging-view.test.js b/test/views/staging-view.test.js index efdd0a125c..8c9fb95503 100644 --- a/test/views/staging-view.test.js +++ b/test/views/staging-view.test.js @@ -801,6 +801,7 @@ describe('StagingView', function() { component: 'StagingView', fileCount: 2, type: 'all', + eventSource: undefined, })); }); }); @@ -816,6 +817,7 @@ describe('StagingView', function() { component: 'StagingView', fileCount: 2, type: 'selected', + eventSource: undefined, })); }); }); From 0310d93815c315cb606fee7fcac10d42b5474c6e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 5 Oct 2018 16:23:23 -0400 Subject: [PATCH 0443/4053] Atlas descriptions --- docs/react-component-atlas.md | 55 ++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md index b67ea1f1c7..35e55f883f 100644 --- a/docs/react-component-atlas.md +++ b/docs/react-component-atlas.md @@ -4,52 +4,73 @@ This is a high-level overview of the structure of the React component tree that > [``](/lib/controllers/root-controller.js) > +> Root of the entire, unified React component tree. Mostly responsible for registering pane items, status bar tiles, workspace commands, and managing dialog box state. Action methods that are shared across broad swaths of the component tree. +> > > [``](/lib/items/git-tab-item.js) > > [``](/lib/containers/git-tab-container.js) > > [``](/lib/controllers/git-tab-controller.js) > > [``](/lib/views/git-tab-view.js) > > +> > The "Git" tab that appears in the right dock (by default). +> > > > > [``](/lib/views/staging-view.js) > > > +> > > The lists of unstaged changes, staged changes, and merge conflicts. > > > > > [``](/lib/controllers/commit-controller.js) > > > [``](/lib/views/commit-view.js) +> > > +> > > The commit message editor, submit button, and co-author selection controls. > > > > > [``](/lib/controllers/recent-commits-controller.js) > > > [`` ``](/lib/views/recent-commits-view.js) > > > +> > > List of most recent commits on the current branch. > > > [``](/lib/items/github-tab-item.js) > > [``](/lib/containers/github-tab-container.js) > > [``](/lib/controllers/github-tab-controller.js) > > [``](/lib/views/github-tab-view.js) > > +> > The "GitHub" tab that appears in the right dock (by default). +> > > > > [``](/lib/views/remote-selector-view.js) > > > +> > > Shown if the current repository has more than one remote that's identified as a github.com remote. > > > > > [``](/lib/containers/remote-container.js) > > > [``](/lib/controllers/remote-controller.js) > > > +> > > GraphQL query and actions that only require the context of a unique repository name to work. +> > > > > > > [``](/lib/controllers/issueish-searches-controller.js) > > > > +> > > > Manages the set of GitHub API issueish searches that we wish to perform, including the special "checked-out pull request" search. +> > > > > > > > > [``](/lib/containers/current-pull-request-container.js) +> > > > > [``](/lib/views/create-pull-request-tile.js) > > > > > -> > > > > > [``](/lib/views/create-pull-request-tile.js) -> > > > > > +> > > > > GraphQL query and result rendering for the special "checked-out pull request" search. +> > > > +> > > > > [``](/lib/controllers/issueish-list-controller.js) +> > > > > [``](/lib/views/issueish-list-view.js) > > > > > -> > > > > > [``](/lib/controllers/issueish-list-controller.js) -> > > > > > [``](/lib/views/issueish-list-view.js) -> > > > > > +> > > > > Render an issueish result as a row within the result list of the current pull request tile. > > > > > > > > > [``](/lib/containers/issueish-search-container.js) > > > > > +> > > > > GraphQL query and result rendering for an issueish search based on the [`search()`](https://developer.github.com/v4/query/#search) GraphQL connection. +> > > > > > > > > > > [``](/lib/controllers/issueish-list-controller.js) > > > > > > [``](/lib/views/issueish-list-view.js) > > > > > > +> > > > > > Render a list of issueish results as rows within the result list of a specific search. > > > [``](/lib/controllers/file-patch-controller.js) > > [``](/lib/views/file-patch-view.js) > > +> > The workspace-center pane that appears when looking at the staged or unstaged changes associated with a file. +> > > > :construction: Being rewritten in [#1712](https://github.com/atom/github/pull/1512) :construction: > > > [``](/lib/items/issueish-detail-item.js) @@ -57,48 +78,66 @@ This is a high-level overview of the structure of the React component tree that > > [``](/lib/controllers/issueish-detail-controller.js) > > [``](/lib/controllers/issueish-detail-controller.js) > > +> > The workspace-center pane that displays information about a pull request or issue ("issueish", collectively) from github.com. +> > > > > [``](/lib/controllers/issue-timeline-controller.js) > > > [``](/lib/views/issueish-timeline-view.js) +> > > +> > > Render "timeline events" (comments, label additions or removals, assignments...) related to an issue. > > > > > [``](/lib/controllers/pr-timeline-controller.js) > > > [``](/lib/views/issueish-timeline-view.js) > > > +> > > Render "timeline events" related to a pull request. > > > > > [``](/lib/views/pr-statuses-view.js) > > > +> > > Display the current build state of a pull request in detail, including a "donut chart" and links to individual build results. > > > > > [``](/lib/views/pr-commits-view.js) > > > [``](/lib/views/pr-commit-view.js) +> > > +> > > Enumerate the commits associated with a pull request. > > > [``](/lib/views/init-dialog.js) > > [``](/lib/views/clone-dialog.js) > > [``](/lib/views/open-issueish-dialog.js) > > [``](/lib/views/credential-dialog.js) > > +> > Various dialog panels we use to (modally) collect information from users. Notably, the CredentialDialog is used for usernames, passwords, SSH key passwords, and GPG passphrases. > > > [``](/lib/controllers/repository-conflict-controller.js) > > +> > Identifies TextEditors opened on files that git believes contain merge conflicts. +> > > > > [``](/lib/controllers/editor-conflict-controller.js) > > > +> > > Parses conflict regions from the buffer associated with a single TextEditor. +> > > > > > > [``](/lib/controllers/conflict-controller.js) > > > > +> > > > Creates TextEditor decorations related to one conflict region, including resolution controls. > > > [``](/lib/controllers/status-bar-tile-controller.js) > > +> > Add the git and GitHub-related tiles to Atom's status bar. +> > > > > [``](/lib/views/branch-view.js) > > > +> > > The little widget that tells you what branch you're on. > > > > > [``](/lib/views/branch-menu-view.js) > > > +> > > Menu that appears within a tooltip when you click the current branch which lets you switch or create branches. > > > > > [``](/lib/views/push-pull-view.js) > > > +> > > Shows the relative position of your local `HEAD` to its upstream ("1 ahead", "2 behind"). Allows you to fetch, pull, or push. > > > > > [``](/lib/views/changed-files-count-view.js) > > > +> > > Displays the git logo and the number of changed files. Clicking it opens the git tab. > > > > > [``](/lib/views/changed-files-count-view.js) > > > -> > -> > -> > +> > > Displays the GitHub logo. Clicking it opens the GitHub tab. From 4fae6dbb203ec06ae844d95836de616b7dc969b1 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 5 Oct 2018 20:24:57 +0000 Subject: [PATCH 0444/4053] chore(package): update dugite to version 1.78.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3b903deff0..3b2610c091 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "bytes": "^3.0.0", "classnames": "2.2.6", "compare-sets": "1.0.1", - "dugite": "^1.66.0", + "dugite": "^1.78.0", "event-kit": "2.5.1", "fs-extra": "4.0.3", "graphql": "0.13.2", From 7dcdf9ab3efbbdb6ef832fd55853c92c9bb2c451 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 5 Oct 2018 20:25:01 +0000 Subject: [PATCH 0445/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 145 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 125 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index ea382f85b2..33fb8bdc40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -510,7 +510,8 @@ "aws4": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha1-1NDpudv8p3vwjusKikcVUP454ok=" + "integrity": "sha1-1NDpudv8p3vwjusKikcVUP454ok=", + "dev": true }, "axobject-query": { "version": "2.0.1", @@ -2430,16 +2431,106 @@ } }, "dugite": { - "version": "1.66.0", - "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.66.0.tgz", - "integrity": "sha1-X9q2aDwLU4p5vb7Emenz0+pyEPk=", + "version": "1.78.0", + "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.78.0.tgz", + "integrity": "sha512-t3/aKKLWS/8gmh6r7Ev2Mm+QiM/lFioQHyokz4/ORQJsJgZlKS/vxt+fzUTydzMI/1wSYRAGzVkJJMTcnh1NQQ==", "requires": { "checksum": "^0.1.1", "mkdirp": "^0.5.1", "progress": "^2.0.0", - "request": "^2.86.0", + "request": "^2.88.0", "rimraf": "^2.5.4", - "tar": "^4.0.2" + "tar": "^4.4.6" + }, + "dependencies": { + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "har-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "mime-db": { + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" + }, + "mime-types": { + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "requires": { + "mime-db": "~1.36.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } } }, "ecc-jsbn": { @@ -2978,7 +3069,8 @@ "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true }, "extend-shallow": { "version": "3.0.2", @@ -3322,7 +3414,7 @@ "fs-minipass": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha1-BsJ3IYRU7CiN93raVKA7hwKqy50=", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "requires": { "minipass": "^2.2.1" } @@ -3575,6 +3667,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "dev": true, "requires": { "ajv": "^5.1.0", "har-schema": "^2.0.0" @@ -4717,9 +4810,9 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "minipass": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.3.tgz", - "integrity": "sha512-/jAn9/tEX4gnpyRATxgHEOV6xbcyxgT7iUnxo9Y3+OB0zX00TgKIv/2FZCf5brBbICcwbLqVv2ImjvWWrQMSYw==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz", + "integrity": "sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4735,7 +4828,7 @@ "minizlib": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", - "integrity": "sha1-EeE2WM5GvDpwomeqxYNZ0eDCnOs=", + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", "requires": { "minipass": "^2.2.1" } @@ -5344,7 +5437,8 @@ "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5754,6 +5848,11 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" + }, "pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", @@ -5771,7 +5870,8 @@ "qs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=" + "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=", + "dev": true }, "raf": { "version": "3.4.0", @@ -6222,6 +6322,7 @@ "version": "2.87.0", "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.6.0", @@ -6248,12 +6349,14 @@ "mime-db": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha1-o0kgUKXLm2NFBUHjnZeI0icng9s=" + "integrity": "sha1-o0kgUKXLm2NFBUHjnZeI0icng9s=", + "dev": true }, "mime-types": { "version": "2.1.18", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "integrity": "sha1-bzI/YKg9ERRvgx/xH9ZuL+VQO7g=", + "dev": true, "requires": { "mime-db": "~1.33.0" } @@ -6999,9 +7102,9 @@ } }, "tar": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.4.tgz", - "integrity": "sha1-7IQJ+un2ZaQ1XMO0CH0IICMruM0=", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.6.tgz", + "integrity": "sha512-tMkTnh9EdzxyfW+6GK6fCahagXsnYk6kE6S9Gr9pjVdys769+laCTbodXDhPAjzVtEBazRgP0gYqOjnk9dQzLg==", "requires": { "chownr": "^1.0.1", "fs-minipass": "^1.2.5", @@ -7015,7 +7118,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -7263,6 +7366,7 @@ "version": "2.3.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha1-7GDO44rGdQY//JelwYlwV47oNlU=", + "dev": true, "requires": { "punycode": "^1.4.1" } @@ -7520,7 +7624,8 @@ "uuid": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "dev": true }, "validate-npm-package-license": { "version": "3.0.3", From ac20762d01859d348062f43bc7a5877cb712f4cc Mon Sep 17 00:00:00 2001 From: Colin Schindler Date: Fri, 5 Oct 2018 18:40:05 -0400 Subject: [PATCH 0446/4053] change 'current' to 'checkout out' --- lib/containers/current-pull-request-container.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/containers/current-pull-request-container.js b/lib/containers/current-pull-request-container.js index 92479afcdf..1f012df810 100644 --- a/lib/containers/current-pull-request-container.js +++ b/lib/containers/current-pull-request-container.js @@ -159,7 +159,7 @@ export default class CurrentPullRequestContainer extends React.Component { controllerProps() { return { - title: 'Current pull request', + title: 'Checked out pull request', onOpenIssueish: this.props.onOpenIssueish, emptyComponent: this.renderEmptyTile, }; From e9fc237a6a9f8314bf84b7ae5757619e5e2b7df6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 7 Oct 2018 18:56:16 -0400 Subject: [PATCH 0447/4053] Move transpilation output to test/output --- test/helpers.js | 4 ++-- test/{ => output}/transpiled/.gitignore | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename test/{ => output}/transpiled/.gitignore (100%) diff --git a/test/helpers.js b/test/helpers.js index ed2a8b3bc2..1794edb60d 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -227,12 +227,12 @@ export async function disableFilesystemWatchers(atomEnv) { } const packageRoot = path.resolve(__dirname, '..'); -const transpiledRoot = path.resolve(__dirname, 'transpiled'); +const transpiledRoot = path.resolve(__dirname, 'output/transpiled/'); export function transpile(...relPaths) { return Promise.all( relPaths.map(async relPath => { - const untranspiledPath = require.resolve(relPath); + const untranspiledPath = path.resolve(__dirname, '..', relPath); const transpiledPath = path.join(transpiledRoot, path.relative(packageRoot, untranspiledPath)); const untranspiledSource = await fs.readFile(untranspiledPath, {encoding: 'utf8'}); diff --git a/test/transpiled/.gitignore b/test/output/transpiled/.gitignore similarity index 100% rename from test/transpiled/.gitignore rename to test/output/transpiled/.gitignore From 9f1085dcdf6f1395f0803a2fbd2d5c690d180416 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 7 Oct 2018 19:14:56 -0400 Subject: [PATCH 0448/4053] Use electron-link, electron-mksnapshot, and globby --- package-lock.json | 647 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 3 + 2 files changed, 633 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 33fb8bdc40..6dc0a17553 100644 --- a/package-lock.json +++ b/package-lock.json @@ -203,7 +203,7 @@ "@smashwilson/atom-mocha-test-runner": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@smashwilson/atom-mocha-test-runner/-/atom-mocha-test-runner-1.4.0.tgz", - "integrity": "sha1-AjOAreJPt5xrC7TlXdTuc7LFdfo=", + "integrity": "sha512-Zp50XTy2QZEk53PUxXQ1kLTAkSwEuM2X7JXtMGLRWuU68piFghkXGaopTrjXK3CwgzmmFi26m65sTCrXg3zqbg==", "dev": true, "requires": { "diff": "3.5.0", @@ -246,6 +246,15 @@ "integrity": "sha512-EGoI4ylB/lPOaqXqtzAyL8HcgOuCtH2hkEaLmkueOYufsTFWBn4VCvlCDC2HW8Q+9iF+QVC3sxjDKQYjHQeZ9w==", "dev": true }, + "abstract-leveldown": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz", + "integrity": "sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + }, "acorn": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", @@ -386,6 +395,12 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, "array-includes": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", @@ -471,12 +486,28 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "ast-types": { + "version": "0.6.16", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.6.16.tgz", + "integrity": "sha1-BCBbcu3dGVqP6qCB8R0ClKJN7ZM=", + "dev": true + }, "ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", "dev": true }, + "ast-util": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/ast-util/-/ast-util-0.6.0.tgz", + "integrity": "sha1-DZE9BPDpgx5T+ZkdyZAJ4tp3SBA=", + "dev": true, + "requires": { + "ast-types": "~0.6.7", + "private": "~0.1.6" + } + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -1597,6 +1628,12 @@ "tweetnacl": "^0.14.3" } }, + "bindings": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", + "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==", + "dev": true + }, "bl": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", @@ -1728,6 +1765,12 @@ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-0.1.1.tgz", "integrity": "sha1-dtglxNblDga3ox61IMBNCMwjUHE=" }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -1794,6 +1837,24 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, "camelize": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", @@ -2017,7 +2078,7 @@ "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha1-30boZ9D8Kuxmo0ZitAapzK//Ww8=", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, "commondir": { @@ -2042,6 +2103,18 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -2084,7 +2157,7 @@ "cross-env": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", - "integrity": "sha1-bs1MAV1Xc+YUA57lKQdmabnRJvI=", + "integrity": "sha512-jtdNFfFW1hB7sMhr/H6rW1Z45LFqyI431m3qU6bFXcQ3Eh7LtBuG3h74o7ohHZ3crrRkkqHlo4jYHFPcjroANg==", "dev": true, "requires": { "cross-spawn": "^6.0.5", @@ -2094,7 +2167,7 @@ "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha1-Sl7Hxk364iw6FBJNus3uhG2Ay8Q=", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -2167,6 +2240,15 @@ "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", "dev": true }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, "damerau-levenshtein": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", @@ -2256,6 +2338,16 @@ "strip-bom": "^3.0.0" } }, + "deferred-leveldown": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-4.0.2.tgz", + "integrity": "sha512-5fMC8ek8alH16QiV0lTCis610D1Zt1+LA4MS4d63JgS32lrCjTFDUFz2ao09/j2I4Bqb5jL4FZYwu7Jz0XO1ww==", + "dev": true, + "requires": { + "abstract-leveldown": "~5.0.0", + "inherits": "^2.0.3" + } + }, "define-properties": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", @@ -2554,6 +2646,66 @@ "semver": "^5.3.0" } }, + "electron-download": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz", + "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==", + "dev": true, + "requires": { + "debug": "^3.0.0", + "env-paths": "^1.0.0", + "fs-extra": "^4.0.1", + "minimist": "^1.2.0", + "nugget": "^2.0.1", + "path-exists": "^3.0.0", + "rc": "^1.2.1", + "semver": "^5.4.1", + "sumchecker": "^2.0.2" + }, + "dependencies": { + "debug": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "electron-link": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/electron-link/-/electron-link-0.2.2.tgz", + "integrity": "sha1-uWvx/MrowwyAuiaTBq+UVOYtP2U=", + "dev": true, + "requires": { + "ast-util": "^0.6.0", + "encoding-down": "~5.0.0", + "indent-string": "^2.1.0", + "leveldown": "~4.0.0", + "levelup": "~3.0.0", + "recast": "^0.12.6", + "resolve": "^1.5.0", + "source-map": "^0.5.6" + } + }, + "electron-mksnapshot": { + "version": "3.0.0-beta.1", + "resolved": "https://registry.npmjs.org/electron-mksnapshot/-/electron-mksnapshot-3.0.0-beta.1.tgz", + "integrity": "sha512-0Q4yV7jCnXiCFOAih8ZvmFBS492kPzQYtsIj30pVyWsytAEyxmyPQD5NbSAaAAJt5di2XocxfvbAEgcAhUXEqA==", + "dev": true, + "requires": { + "electron-download": "^4.1.0", + "extract-zip": "^1.6.5" + } + }, "emoji-regex": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", @@ -2568,6 +2720,19 @@ "iconv-lite": "~0.4.13" } }, + "encoding-down": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-5.0.4.tgz", + "integrity": "sha512-8CIZLDcSKxgzT+zX8ZVfgNbu8Md2wq/iqa1Y7zyVR18QBEAc0Nmzuvj/N5ykSKpfGzjM8qxbaFntLPwnVoUhZw==", + "dev": true, + "requires": { + "abstract-leveldown": "^5.0.0", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0", + "xtend": "^4.0.1" + } + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -2582,6 +2747,12 @@ "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", "dev": true }, + "env-paths": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", + "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", + "dev": true + }, "enzyme": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.4.1.tgz", @@ -2627,9 +2798,8 @@ "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, - "optional": true, "requires": { "prr": "~1.0.1" } @@ -3180,6 +3350,18 @@ } } }, + "extract-zip": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", + "yauzl": "2.4.1" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -3190,6 +3372,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, + "fast-future": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fast-future/-/fast-future-1.0.2.tgz", + "integrity": "sha1-hDWpqqAteSSNF9cE52JZMB2ZKAo=", + "dev": true + }, "fast-glob": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.2.tgz", @@ -3260,6 +3448,15 @@ "integrity": "sha1-5tCdFOk2CzDghA0WCfdptw9Ww+o=", "dev": true }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -3486,6 +3683,12 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -3607,7 +3810,7 @@ "grim": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/grim/-/grim-2.0.2.tgz", - "integrity": "sha1-52CinKe4NDsMH/r2ziDyGkbuiu0=", + "integrity": "sha512-Qj7hTJRfd87E/gUgfvM0YIH/g2UA2SV6niv6BYXk1o6w4mhgv+QyYM1EjOJQljvzgEj4SqSsRWldXIeKHz3e3Q==", "dev": true, "requires": { "event-kit": "^2.0.0" @@ -3616,7 +3819,7 @@ "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha1-8nNdwig2dPpnR4sQGBBZNVw2nl4=", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, "handlebars": { @@ -3841,6 +4044,15 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, "individual": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/individual/-/individual-3.0.0.tgz", @@ -4527,7 +4739,7 @@ "less": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/less/-/less-3.8.1.tgz", - "integrity": "sha1-8xdYWY71oZMN1MrvqeQ0BkHnHh0=", + "integrity": "sha512-8HFGuWmL3FhQR0aH89escFNBQH/nEiYPP2ltDFdQw2chE28Yx2E3lhAIq9Y2saYwLSwa699s4dBVEfCY8Drf7Q==", "dev": true, "requires": { "clone": "^2.1.2", @@ -4544,12 +4756,94 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true } } }, + "level-codec": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.0.tgz", + "integrity": "sha512-OIpVvjCcZNP5SdhcNupnsI1zo5Y9Vpm+k/F1gfG5kXrtctlrwanisakweJtE0uA0OpLukRfOQae+Fg0M5Debhg==", + "dev": true + }, + "level-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.0.tgz", + "integrity": "sha512-AmY4HCp9h3OiU19uG+3YWkdELgy05OTP/r23aNHaQKWv8DO787yZgsEuGVkoph40uwN+YdUKnANlrxSsoOaaxg==", + "dev": true, + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-2.0.3.tgz", + "integrity": "sha512-I6Heg70nfF+e5Y3/qfthJFexhRw/Gi3bIymCoXAlijZdAcLaPuWSJs3KXyTYf23ID6g0o2QF62Yh+grOXY3Rig==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.5", + "xtend": "^4.0.0" + } + }, + "leveldown": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-4.0.1.tgz", + "integrity": "sha512-ZlBKVSsglPIPJnz4ggB8o2R0bxDxbsMzuQohbfgoFMVApyTE118DK5LNRG0cRju6rt3OkGxe0V6UYACGlq/byg==", + "dev": true, + "requires": { + "abstract-leveldown": "~5.0.0", + "bindings": "~1.3.0", + "fast-future": "~1.0.2", + "nan": "~2.10.0", + "prebuild-install": "^4.0.0" + }, + "dependencies": { + "nan": { + "version": "2.10.0", + "resolved": "http://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "dev": true + }, + "prebuild-install": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-4.0.0.tgz", + "integrity": "sha512-7tayxeYboJX0RbVzdnKyGl2vhQRWr6qfClEXDhOkXjuaOKCw2q8aiuFhONRYVsG/czia7KhpykIlI2S2VaPunA==", + "dev": true, + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^1.0.2", + "github-from-package": "0.0.0", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "node-abi": "^2.2.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "os-homedir": "^1.0.1", + "pump": "^2.0.1", + "rc": "^1.1.6", + "simple-get": "^2.7.0", + "tar-fs": "^1.13.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + } + } + } + }, + "levelup": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-3.0.1.tgz", + "integrity": "sha512-TrrLDPC/BfP35ei2uK+L6Cc7kpI1NxIChwp+BUB6jrHG3A8gtrr9jx1UZ9bi2w1O6VN7jYO4LUoq1iKRP5AREg==", + "dev": true, + "requires": { + "deferred-leveldown": "~4.0.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~2.0.0", + "xtend": "~4.0.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -4640,6 +4934,16 @@ "js-tokens": "^3.0.0" } }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, "lru-cache": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", @@ -4679,6 +4983,12 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -4712,6 +5022,99 @@ "mimic-fn": "^1.0.0" } }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, "merge-source-map": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", @@ -4759,7 +5162,7 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, "optional": true }, @@ -4872,7 +5275,7 @@ "mocha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha1-bYrlCPWRZ/lA8rWzxKYSrlDJCuY=", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, "requires": { "browser-stdout": "1.3.1", @@ -4891,7 +5294,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -4900,7 +5303,7 @@ "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -5129,6 +5532,21 @@ "boolbase": "~1.0.0" } }, + "nugget": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", + "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", + "dev": true, + "requires": { + "debug": "^2.1.3", + "minimist": "^1.1.0", + "pretty-bytes": "^1.0.2", + "progress-stream": "^1.1.0", + "request": "^2.45.0", + "single-line-log": "^1.1.2", + "throttleit": "0.0.2" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -5699,6 +6117,12 @@ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, "path-to-regexp": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", @@ -5723,6 +6147,12 @@ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -5798,6 +6228,16 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "pretty-bytes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", + "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.1.0" + } + }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", @@ -5819,6 +6259,16 @@ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=" }, + "progress-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", + "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", + "dev": true, + "requires": { + "speedometer": "~0.1.2", + "through2": "~0.2.3" + } + }, "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -5840,8 +6290,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true, - "optional": true + "dev": true }, "pseudomap": { "version": "1.0.2", @@ -6078,6 +6527,43 @@ } } }, + "recast": { + "version": "0.12.9", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.12.9.tgz", + "integrity": "sha512-y7ANxCWmMW8xLOaiopiRDlyjQ9ajKRENBH+2wjntIbk3A6ZR1+BLQttkmSHMY7Arl+AAZFwJ10grg2T6f1WI8A==", + "dev": true, + "requires": { + "ast-types": "0.10.1", + "core-js": "^2.4.1", + "esprima": "~4.0.0", + "private": "~0.1.5", + "source-map": "~0.6.1" + }, + "dependencies": { + "ast-types": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.10.1.tgz", + "integrity": "sha512-UY7+9DPzlJ9VM8eY0b2TUZcZvF+1pO0hzMtAyjBYKhOmnvRlqYNYnWdtsMj0V16CGaMlpL0G1jnLbLo4AyotuQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, "regenerator-runtime": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", @@ -6442,6 +6928,15 @@ "resolve-from": "^1.0.0" } }, + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, "resolve-from": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", @@ -6537,7 +7032,7 @@ "semver": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha1-ff3YgUvbfKvHvg+x1zTPtmyUBHc=" + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" }, "set-blocking": { "version": "2.0.0", @@ -6613,6 +7108,28 @@ "simple-concat": "^1.0.0" } }, + "single-line-log": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", + "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", + "dev": true, + "requires": { + "string-width": "^1.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, "sinon": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-6.0.1.tgz", @@ -6861,6 +7378,12 @@ "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", "dev": true }, + "speedometer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", + "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", + "dev": true + }, "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -7016,11 +7539,29 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "sumchecker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", + "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -7280,11 +7821,62 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "throttleit": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", + "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, + "through2": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", + "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9", + "xtend": "~2.1.1" + }, + "dependencies": { + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, "tinycolor2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", @@ -7376,6 +7968,12 @@ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz", "integrity": "sha1-WEZ4Yje0I5AU8F2xVrZDIS1MbzY=" }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", @@ -7410,6 +8008,12 @@ "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=", "dev": true }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, "ua-parser-js": { "version": "0.7.14", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.14.tgz", @@ -7838,6 +8442,15 @@ "camelcase": "^4.1.0" } }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "~1.0.1" + } + }, "yubikiri": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/yubikiri/-/yubikiri-1.0.0.tgz", diff --git a/package.json b/package.json index 3b2610c091..1ccc7e0141 100644 --- a/package.json +++ b/package.json @@ -80,10 +80,13 @@ "cross-env": "5.2.0", "dedent-js": "1.0.1", "electron-devtools-installer": "2.2.4", + "electron-link": "0.2.2", + "electron-mksnapshot": "3.0.0-beta.1", "enzyme": "3.4.1", "eslint": "5.0.1", "eslint-config-fbjs-opensource": "1.0.0", "eslint-plugin-jsx-a11y": "^6.1.1", + "globby": "5.0.0", "hock": "1.3.3", "lodash.isequal": "4.5.0", "mkdirp": "0.5.1", From 0aeb2deb3e4d617c07e1cb340a93a5e68c57f7f6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 7 Oct 2018 19:15:44 -0400 Subject: [PATCH 0449/4053] Unit test to verify snapshottability --- test/output/snapshot-cache/.gitignore | 2 + test/snapshot.test.js | 60 +++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 test/output/snapshot-cache/.gitignore create mode 100644 test/snapshot.test.js diff --git a/test/output/snapshot-cache/.gitignore b/test/output/snapshot-cache/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/test/output/snapshot-cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/test/snapshot.test.js b/test/snapshot.test.js new file mode 100644 index 0000000000..94f57ee7af --- /dev/null +++ b/test/snapshot.test.js @@ -0,0 +1,60 @@ +// Ensure that all of our source can be snapshotted correctly. + +import fs from 'fs-extra'; +import vm from 'vm'; +import path from 'path'; +import temp from 'temp'; +import globby from 'globby'; +import childProcess from 'child_process'; +import electronLink from 'electron-link'; + +import {transpile} from './helpers'; + +describe('snapshot generation', function() { + it('successfully preprocesses and snapshots the package', async function() { + this.timeout(60000); + + const baseDirPath = path.resolve(__dirname, '..'); + const workDir = temp.mkdirSync('github-snapshot-'); + const snapshotScriptPath = path.join(workDir, 'snapshot-source.js'); + const snapshotBlobPath = path.join(workDir, 'snapshot-blob.bin'); + const coreModules = new Set(['electron', 'atom']); + + const sourceFiles = await globby(['lib/**/*.js'], {cwd: baseDirPath}); + await transpile(...sourceFiles); + + await fs.copyFile( + path.resolve(__dirname, '../package.json'), + path.resolve(__dirname, 'output/transpiled/package.json'), + ); + + const {snapshotScript} = await electronLink({ + baseDirPath, + mainPath: path.join(__dirname, 'output/transpiled/lib/index.js'), + cachePath: path.join(__dirname, 'output/snapshot-cache'), + shouldExcludeModule: ({requiringModulePath, requiredModulePath}) => { + const requiredModuleRelativePath = path.relative(baseDirPath, requiredModulePath); + + if (requiredModulePath.endsWith('.node')) { return true; } + if (coreModules.has(requiredModulePath)) { return true; } + if (requiredModuleRelativePath.startsWith(path.join('node_modules/dugite'))) { return true; } + if (requiredModuleRelativePath.endsWith(path.join('node_modules/temp/lib/temp.js'))) { return true; } + if (requiredModuleRelativePath.endsWith(path.join('node_modules/graceful-fs/graceful-fs.js'))) { return true; } + if (requiredModuleRelativePath.endsWith(path.join('node_modules/fs-extra/lib/index.js'))) { return true; } + + return false; + }, + }); + + // TODO minify source with terser + + await fs.writeFile(snapshotScriptPath, snapshotScript, 'utf8'); + + vm.runInNewContext(snapshotScript, undefined, {filename: snapshotScriptPath, displayErrors: true}); + + childProcess.execFileSync( + path.join(__dirname, '../node_modules/electron-mksnapshot/bin/mksnapshot'), + ['--no-use_ic', snapshotScriptPath, '--startup_blob', snapshotBlobPath], + ); + }); +}); From b00e03f7801bf3f454d7bce1e63008fa9802aba6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 7 Oct 2018 20:58:32 -0400 Subject: [PATCH 0450/4053] Documentation about the way we interact with git --- docs/git-interactions.md | 116 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 docs/git-interactions.md diff --git a/docs/git-interactions.md b/docs/git-interactions.md new file mode 100644 index 0000000000..5030ff6366 --- /dev/null +++ b/docs/git-interactions.md @@ -0,0 +1,116 @@ +# Git interactions + +Describe the various classes involved in interacting with git and what kinds of behavior to find in each. + +The GitHub package uses [dugite](https://github.com/desktop/dugite) to execute git commands as subprocesses. Dugite bundles a minimal git distribution built from the primary git tree. This has the advantages that we ensure compatibility and consistency with native git operations and that Atom users don't need to download and install git themselves, at the cost of a larger download size (by about 30MB). + +## WorkerManager and Workers + +When a subprocess is spawned from Node.js, the resident set of memory pages needs to be copied into the new process' address space. This copy happens _synchronously_ even when using asynchronous variants of functions from the `child_process` module, and from an Electron process, the RSS can become quite large. Because this blocks the event loop it locks the processing of UI events. This leads to a quite noticeable degradation of Atom's performance when spawning a large number of subprocesses, manifesting as stuttering and locking. + +To work around this, the GitHub package creates a secondary Electron renderer process, with no visible window, and uses an IPC request/response protocol to perform subprocess creation within that process instead. The sidecar renderer process tracks a running average of the duration of the synchronous portion of the spawn calls it performs and, if it degrades too much, self-destructs and re-launches itself. The IPC and process creation overhead are easily cancelled out by the smoothing that this brings. + +The sidecar process execution is implemented on the host process side by the [`WorkerManager`, `Worker`, `RendererProcess` and `Operation`](/lib/worker-manager.js) classes. The client side is implemented by [`worker.js`](/lib/worker.js), which is loaded by [`renderer.html`](/lib/renderer.html). + +If you wish to see the sidecar renderer process window with its diagnostic information, set the environment variable `ATOM_GITHUB_SHOW_RENDERER_WINDOW` before launching Atom. To opt out of the sidecar process entirely (for CI tests, for example) set `ATOM_GITHUB_INLINE_GIT_EXEC`. + +## Git Shell Out Strategy + +The [`GitShellOutStrategy`](/lib/git-shell-out-strategy.js) class is responsible for composing the actual commands and arguments passed to `git` subprocesses, either through dugite directly or through the `WorkerManager`. An asynchronous queue implementation manages git command concurrency: commands that acquire a lock on the git index - write operations - run serially, but read operations are permitted to execute in parallel. + +Command arguments are injected to override problematic git configuration options that could break our ability to parse git's output for certain commands, and to register Atom's GitPromptServer as a handler for SSH, https auth, and GPG credential requests. + +It also measures performance data and reports diagnostics to the dev console if the appropriate Atom configuration key is set. + +`GitShellOutStrategy` methods communicate by means of plain JavaScript objects and strings. They are very low-level; each method calls a single `git` command and reports any output with minimal postprocessing or parsing. + +> Historical note: `GitShellOutStrategy` and [`CompositeGitStrategy`](/lib/composite-git-strategy.js) are the remnants of exploratory work to back some operations by calls to [libgit2]() by means of [node-git](). The performance and stability cost ended up not being worth it for us. + +## GitPromptServer + +A [`GitTempDir`](/lib/git-temp-dir.js) and [`GitPromptServer`](/lib/git-prompt-server.js) are created during certain `GitShellOutStrategy` methods to service any credential requests that git requires. We handle passphrase requests by: + +* Creating a temporary directory. +* Copying a set of [helper scripts](/bin) to the temporary directory and, on non-Windows platforms, marking them executable. These scripts are `/bin/sh` scripts that execute their corresponding JavaScript modules as Node.js processes with the current Electron binary (by setting `ELECTRON_RUN_AS_NODE=1`), propagating along any arguments. +* A UNIX domain socket or named pipe is created within the temporary directory. :memo: _Note that UNIX domain socket paths are limited to a maximum of 140 characters for [reasons](). On platforms where this is an issue, the temporary directory name must be short enough to accommodate this._ +* The host Atom process creates a server listening on the UNIX domain socket or named pipe. +* The `git` subprocess is spawned, configured to use the copied helper scripts as credential handlers. + * For HTTPS authentication, the argument `-c credential.helper=...` is used to ensure [`bin/git-credential-atom.js`](/bin/git-credential-atom.js) is used as the highest-priority [git credential helper](). `git-credential-atom.js` implements git's credential helper protocol by: + 1. Executing any credential helpers configured by your system git. Some git installations are already configured to read from the OS keychain, but dugite's bundled git won't respect configution from your system installation. + 2. Reading an Atom-specific key from your OS keychain. If you have logged in to the GitHub tab, your OAuth token will be found here as well. + 3. If neither of those are successful, connect to the socket opened by `GitPromptServer` and write a JSON query. + 4. When a JSON reply is received, it is written back to git on stdout. + 5. If git reports that the credential is accepted, and if the "remember me" flag was set in the query reply, the provided password will be written to the OS keychain. + 6. If git reports that the credential was rejected, the provided password will be deleted from the OS keychain. + * To unlock SSH keys, the environment variables `SSH_ASKPASS` and `GIT_ASKPASS` are set to the path to the script that runs [`git-askpass-atom.js`](bin/git-askpass-atom.js). `DISPLAY` is also set to a non-empty value so that `ssh` will respect `SSH_ASKPASS`. `git-askpass-atom.js` reads its prompt from its process arguments, attempts to execute the system askpass if one is present, and falls back to querying the `GitPromptServer` if that does not succeed. Its passphrase is written to stdout. + * For GPG passphrases, `-c gpg.program=...` is set to [`bin/gpg-wrapper.sh`](/bin/gpg-wrapper.sh). `gpg-wrapper.sh` attempts to use the `--passphrase-fd` argument to GPG to prompt for your passphrase by reading and writing to file descriptor 3. Unfortunately, more recent versions of GPG not longer respect this argument (and use a much more complicated architecture for pinentry configuration through `gpg-agent`,) so for now native GPG pinentry programs must often be used. + * On Linux, `GIT_SSH_COMMAND` is set to [`bin/linux-ssh-wrapper.sh`](/bin/linux-ssh-wrapper.sh), a wrapper script that runs the ssh command in a new process group. Otherwise, `ssh` will ignore `SSH_ASKPASS` and insist on prompting on the tty you used to launch Atom. + +## Repository + +[`Repository`](/lib/models/repository.js) is the higher-level model class that most of the view layer uses to interact with a git repository. + +Repositories are stateful: when created with a path, they are **loading**, after which they may become **present** if a `.git` directory is found, or **empty** otherwise. They may also be **absent** if you don't even have a path. **Empty** repositories may transition to **initializing** or **cloning** if a `git init` or `git clone` operation is begun. + +Repository instances mostly delegate operations to their current _state instance_. (This delegation is not automatic; there is [an explicit list](/lib/models/repository.js#L265-363) of methods that are delegated, which must be updated if new functionality is added.) However, Repositories do directly implement methods for: + +* Composite operations that chain together several one-git-command pieces from its state, and +* Alias operations that re-interpret the result from a single primitive command in different ways. + +### Present + +[`Present`](/lib/models/repository-states/present.js) is the most often-used state because it represents a `Repository` that's actually there to operate on. Present has methods for all primitive `git` operations, implemented as calls to the active git strategy. + +Present's methods communicate with a language of model objects: [`Branch`](/lib/models/branch.js), [`Commit`](/lib/models/commit.js), [`FilePatch`](/lib/models/file-patch.js). + +Present is responsible for caching the results of commands that read state and for selectively busting invalidated cache keys based on write operations that are performed or filesystem activity observed within the `.git` directory. + +To write a method that reads from the cache, first locate or create a new cache key. These are static `CacheKey` objects found within [the `Key` structure](/lib/models/repository-states/present.js#L1072-1165). If the git operation depends on some of its operations, you may need to introduce a function that creates a unique cache key based on its input. + +```js +const Keys = { + // Single static key that does not depend on input. + lastCommit: new CacheKey('last-commit'), + + // A group of related cache keys. + config: { + // Generate a key based on a command argument. + // The created key belongs to two "groups" that can be used to invalidate it. + oneWith: (setting, local) => { + return new CacheKey(`config:${setting}:${local}`, ['config', `config:${local}`]); + }, + + // Used to invalidate *all* cache entries belonging to a given group at once. + all: new GroupKey('config'), + }, +} +``` + +Then write your method to call `this.cache.getOrSet()` with the appropriate key or keys as its first argument: + +```js +getConfig(option, local = false) { + return this.cache.getOrSet(Keys.config.oneWith(option, local), () => { + return this.git().getConfig(option, {local}); + }); +} +``` + +To write a method that may invalidate the cache, wrap it with the `invalidate()` method: + +```js +setConfig(setting, value, options) { + return this.invalidate( + () => Keys.config.eachWithSetting(setting), + () => this.git().setConfig(setting, value, options), + ); +} +``` + +To respond appropriately to git commands performed externally, be sure to also add invalidation logic to the [Present::observeFilesystemChange()](/lib/models/repository-states/present.js#L94-160). + +### State + +[`State`](/lib/models/repository-states/state.js) is the root class of the hierarchy used to implement Repository states. It provides implementations of all expected state methods that do nothing and return an appropriate null object. + +When adding new git functionality, be sure to provide an appropriate null version of your methods here, so that newly added methods will work properly on Repositories that are loading, empty, or absent. From b50b1a8ff1437daabd73245d1bb2872e73570027 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 7 Oct 2018 22:17:06 -0700 Subject: [PATCH 0451/4053] Nah, terser wouldn't test anything --- test/snapshot.test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/snapshot.test.js b/test/snapshot.test.js index 94f57ee7af..bc6934852a 100644 --- a/test/snapshot.test.js +++ b/test/snapshot.test.js @@ -46,8 +46,6 @@ describe('snapshot generation', function() { }, }); - // TODO minify source with terser - await fs.writeFile(snapshotScriptPath, snapshotScript, 'utf8'); vm.runInNewContext(snapshotScript, undefined, {filename: snapshotScriptPath, displayErrors: true}); From 5ce20a632d00774463fab04469e964025d7fcb74 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 7 Oct 2018 22:35:53 -0700 Subject: [PATCH 0452/4053] Document our git interaction layers --- docs/git-interactions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/git-interactions.md b/docs/git-interactions.md index 5030ff6366..e53aea7cda 100644 --- a/docs/git-interactions.md +++ b/docs/git-interactions.md @@ -24,7 +24,7 @@ It also measures performance data and reports diagnostics to the dev console if `GitShellOutStrategy` methods communicate by means of plain JavaScript objects and strings. They are very low-level; each method calls a single `git` command and reports any output with minimal postprocessing or parsing. -> Historical note: `GitShellOutStrategy` and [`CompositeGitStrategy`](/lib/composite-git-strategy.js) are the remnants of exploratory work to back some operations by calls to [libgit2]() by means of [node-git](). The performance and stability cost ended up not being worth it for us. +> Historical note: `GitShellOutStrategy` and [`CompositeGitStrategy`](/lib/composite-git-strategy.js) are the remnants of exploratory work to back some operations by calls to [libgit2](https://libgit2.org/) by means of [nodegit](https://www.npmjs.com/package/nodegit). The performance and stability cost ended up not being worth it for us. ## GitPromptServer @@ -32,10 +32,10 @@ A [`GitTempDir`](/lib/git-temp-dir.js) and [`GitPromptServer`](/lib/git-prompt-s * Creating a temporary directory. * Copying a set of [helper scripts](/bin) to the temporary directory and, on non-Windows platforms, marking them executable. These scripts are `/bin/sh` scripts that execute their corresponding JavaScript modules as Node.js processes with the current Electron binary (by setting `ELECTRON_RUN_AS_NODE=1`), propagating along any arguments. -* A UNIX domain socket or named pipe is created within the temporary directory. :memo: _Note that UNIX domain socket paths are limited to a maximum of 140 characters for [reasons](). On platforms where this is an issue, the temporary directory name must be short enough to accommodate this._ +* A UNIX domain socket or named pipe is created within the temporary directory. :memo: _Note that UNIX domain socket paths are limited to a maximum of 107 characters for [reasons](https://unix.stackexchange.com/questions/367008/why-is-socket-path-length-limited-to-a-hundred-chars). On platforms where this is an issue, the temporary directory name must be short enough to accommodate this._ * The host Atom process creates a server listening on the UNIX domain socket or named pipe. * The `git` subprocess is spawned, configured to use the copied helper scripts as credential handlers. - * For HTTPS authentication, the argument `-c credential.helper=...` is used to ensure [`bin/git-credential-atom.js`](/bin/git-credential-atom.js) is used as the highest-priority [git credential helper](). `git-credential-atom.js` implements git's credential helper protocol by: + * For HTTPS authentication, the argument `-c credential.helper=...` is used to ensure [`bin/git-credential-atom.js`](/bin/git-credential-atom.js) is used as the highest-priority [git credential helper](https://git-scm.com/docs/git-credential). `git-credential-atom.js` implements git's credential helper protocol by: 1. Executing any credential helpers configured by your system git. Some git installations are already configured to read from the OS keychain, but dugite's bundled git won't respect configution from your system installation. 2. Reading an Atom-specific key from your OS keychain. If you have logged in to the GitHub tab, your OAuth token will be found here as well. 3. If neither of those are successful, connect to the socket opened by `GitPromptServer` and write a JSON query. @@ -107,7 +107,7 @@ setConfig(setting, value, options) { } ``` -To respond appropriately to git commands performed externally, be sure to also add invalidation logic to the [Present::observeFilesystemChange()](/lib/models/repository-states/present.js#L94-160). +To respond appropriately to git commands performed externally, be sure to also add invalidation logic to the [`Present::observeFilesystemChange()`](/lib/models/repository-states/present.js#L94-160). ### State From 0a0792b3ad78867ef63958752646d7fd11a0f495 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 7 Oct 2018 23:08:48 -0700 Subject: [PATCH 0453/4053] Line range links need Ls on the start and end --- docs/git-interactions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/git-interactions.md b/docs/git-interactions.md index e53aea7cda..77eef4cc4e 100644 --- a/docs/git-interactions.md +++ b/docs/git-interactions.md @@ -52,7 +52,7 @@ A [`GitTempDir`](/lib/git-temp-dir.js) and [`GitPromptServer`](/lib/git-prompt-s Repositories are stateful: when created with a path, they are **loading**, after which they may become **present** if a `.git` directory is found, or **empty** otherwise. They may also be **absent** if you don't even have a path. **Empty** repositories may transition to **initializing** or **cloning** if a `git init` or `git clone` operation is begun. -Repository instances mostly delegate operations to their current _state instance_. (This delegation is not automatic; there is [an explicit list](/lib/models/repository.js#L265-363) of methods that are delegated, which must be updated if new functionality is added.) However, Repositories do directly implement methods for: +Repository instances mostly delegate operations to their current _state instance_. (This delegation is not automatic; there is [an explicit list](/lib/models/repository.js#L265-L363) of methods that are delegated, which must be updated if new functionality is added.) However, Repositories do directly implement methods for: * Composite operations that chain together several one-git-command pieces from its state, and * Alias operations that re-interpret the result from a single primitive command in different ways. @@ -65,7 +65,7 @@ Present's methods communicate with a language of model objects: [`Branch`](/lib/ Present is responsible for caching the results of commands that read state and for selectively busting invalidated cache keys based on write operations that are performed or filesystem activity observed within the `.git` directory. -To write a method that reads from the cache, first locate or create a new cache key. These are static `CacheKey` objects found within [the `Key` structure](/lib/models/repository-states/present.js#L1072-1165). If the git operation depends on some of its operations, you may need to introduce a function that creates a unique cache key based on its input. +To write a method that reads from the cache, first locate or create a new cache key. These are static `CacheKey` objects found within [the `Key` structure](/lib/models/repository-states/present.js#L1072-L1165). If the git operation depends on some of its operations, you may need to introduce a function that creates a unique cache key based on its input. ```js const Keys = { @@ -107,7 +107,7 @@ setConfig(setting, value, options) { } ``` -To respond appropriately to git commands performed externally, be sure to also add invalidation logic to the [`Present::observeFilesystemChange()`](/lib/models/repository-states/present.js#L94-160). +To respond appropriately to git commands performed externally, be sure to also add invalidation logic to the [`Present::observeFilesystemChange()`](/lib/models/repository-states/present.js#L94-L160). ### State From 0eef75ca9d1333a82f934ab079682edeac4e5c5a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 8 Oct 2018 15:34:41 -0700 Subject: [PATCH 0454/4053] Turns out Mocha needs an "extension" and is very specific about it --- test/{snapshot.test.js => generation.snapshot.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{snapshot.test.js => generation.snapshot.js} (100%) diff --git a/test/snapshot.test.js b/test/generation.snapshot.js similarity index 100% rename from test/snapshot.test.js rename to test/generation.snapshot.js From 259c848ccc1fa11c24936f4572594b86690c2a31 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 8 Oct 2018 15:34:58 -0700 Subject: [PATCH 0455/4053] Mux test suffixes with an env var --- test/runner.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/runner.js b/test/runner.js index ba110bc3b8..321cf84a84 100644 --- a/test/runner.js +++ b/test/runner.js @@ -75,10 +75,13 @@ if (process.env.ATOM_GITHUB_BABEL_ENV === 'coverage' && !process.env.NYC_CONFIG) global._nyc.wrap(); } +const testSuffixes = process.env.ATOM_GITHUB_TEST_SUITE === 'snapshot' ? ['snapshot.js'] : ['test.js']; + module.exports = createRunner({ htmlTitle: `GitHub Package Tests - pid ${process.pid}`, reporter: process.env.MOCHA_REPORTER || 'list', overrideTestPaths: [/spec$/, /test/], + testSuffixes, }, (mocha, {terminate}) => { // Ensure that we expect to be deployable to this version of Atom. const engineRange = require('../package.json').engines.atom; From 7357f97deafe1fa1d3224fa989ab2b84f4a34ebd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 8 Oct 2018 15:36:12 -0700 Subject: [PATCH 0456/4053] npm script as an entry point --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1ccc7e0141..fa8be1bcca 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "test:coverage:text": "nyc --reporter=text npm run test:coverage", "test:coverage:html": "nyc --reporter=html npm run test:coverage", "test:coverage:lcov": "npm run test:coverage", + "test:snapshot": "cross-env-shell ATOM_GITHUB_TEST_SUITE=snapshot \"${ATOM_SCRIPT_PATH:-atom} --test test\"", "coveralls": "nyc report --reporter=text-lcov | coveralls", "lint": "eslint --max-warnings 0 test lib", "fetch-schema": "node script/fetch-schema", From 75e2287f4ddc963409077512c79a0dcf3b0180f8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 8 Oct 2018 15:38:35 -0700 Subject: [PATCH 0457/4053] CircleCI workflow for snapshotting --- .circleci/config.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4343e459ca..1d953b7be5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,6 +64,19 @@ jobs: - UNTIL_TIMEOUT: "30000" - CIRCLE_BUILD_IMAGE: osx - ATOM_CHANNEL: dev + snapshot: + <<: *defaults + environment: + - ATOM_LINT_WITH_BUNDLED_NODE: "true" + - APM_TEST_PACKAGES: "" + - npm_config_clang: "1" + - CC: clang + - CXX: clang++ + - MOCHA_TIMEOUT: "60000" + - UNTIL_TIMEOUT: "30000" + - CIRCLE_BUILD_IMAGE: osx + - ATOM_CHANNEL: dev + - ATOM_GITHUB_TEST_SUITE: snapshot workflows: version: 2 @@ -72,3 +85,4 @@ workflows: - stable - beta - dev + - snapshot From 5472d9d10600b4f22c0ec984812c63fa83e837a0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 8 Oct 2018 18:04:37 -0700 Subject: [PATCH 0458/4053] Ensure push.default has not changed from the default for tests Co-Authored-By: Katrina Uychaco --- test/helpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/helpers.js b/test/helpers.js index 1794edb60d..1d4c707324 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -55,6 +55,7 @@ export async function cloneRepository(repoName = 'three-files') { await git.exec(['config', '--local', 'commit.template', templatePath]); await git.exec(['config', '--local', 'user.email', FAKE_USER.email]); await git.exec(['config', '--local', 'user.name', FAKE_USER.name]); + await git.exec(['config', '--local', 'push.default', 'simple']); await git.exec(['checkout', '--', '.']); // discard \r in working directory cachedClonedRepos[repoName] = cachedPath; } From 09f2eb939f2e3bee88afb1eca3bf4cf89759cd69 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 8 Oct 2018 21:37:44 -0700 Subject: [PATCH 0459/4053] Depend on cross-unzip Co-Authored-By: Katrina Uychaco Co-Authored-By: Tilde Ann Thurium --- package-lock.json | 14 +++++++++++--- package.json | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6dc0a17553..a58c8ccd05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2197,9 +2197,9 @@ } }, "cross-unzip": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/cross-unzip/-/cross-unzip-0.0.2.tgz", - "integrity": "sha1-UYO8R6CVWb78+YzEZXlkmZNZNy8=", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cross-unzip/-/cross-unzip-0.2.1.tgz", + "integrity": "sha1-Ae0dS7JDujObLD8Dxbp6eIGJhMY=", "dev": true }, "cryptiles": { @@ -2644,6 +2644,14 @@ "cross-unzip": "0.0.2", "rimraf": "^2.5.2", "semver": "^5.3.0" + }, + "dependencies": { + "cross-unzip": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/cross-unzip/-/cross-unzip-0.0.2.tgz", + "integrity": "sha1-UYO8R6CVWb78+YzEZXlkmZNZNy8=", + "dev": true + } } }, "electron-download": { diff --git a/package.json b/package.json index fa8be1bcca..5a34d79c67 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "chai-as-promised": "7.1.1", "coveralls": "^3.0.1", "cross-env": "5.2.0", + "cross-unzip": "0.2.1", "dedent-js": "1.0.1", "electron-devtools-installer": "2.2.4", "electron-link": "0.2.2", From 2c6fad829a2973076d030bf9c6cbccd5c1a35e29 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 8 Oct 2018 21:39:27 -0700 Subject: [PATCH 0460/4053] Download React dev tools with fetch() before electron-devtools-installer Co-Authored-By: Katrina Uychaco Co-Authored-By: Tilde Ann Thurium --- lib/controllers/root-controller.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 34e0550060..ebe474acfd 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -1,5 +1,6 @@ import fs from 'fs-extra'; import path from 'path'; +import {remote} from 'electron'; import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; @@ -373,11 +374,38 @@ export default class RootController extends React.Component { } } - installReactDevTools() { + async installReactDevTools() { // Prevent electron-link from attempting to descend into electron-devtools-installer, which is not available // when we're bundled in Atom. const devToolsName = 'electron-devtools-installer'; const devTools = require(devToolsName); + + const crossUnzipName = 'cross-unzip'; + const unzip = require(crossUnzipName); + + const reactId = devTools.REACT_DEVELOPER_TOOLS.id; + + const url = + 'https://clients2.google.com/service/update2/crx?' + + `response=redirect&x=id%3D${reactId}%26uc&prodversion=32`; + const extensionFolder = path.resolve(remote.app.getPath('userData'), `extensions/${reactId}`); + const extensionFile = `${extensionFolder}.crx`; + await fs.ensureDir(path.dirname(extensionFile)); + const response = await fetch(url, {method: 'GET'}); + const body = Buffer.from(await response.arrayBuffer()); + await fs.writeFile(extensionFile, body); + + await new Promise((resolve, reject) => { + unzip(extensionFile, extensionFolder, async err => { + if (err && !await fs.exists(path.join(extensionFolder, 'manifest.json'))) { + reject(err); + } + + resolve(); + }); + }); + + await fs.ensureDir(extensionFolder, 0o755); devTools.default(devTools.REACT_DEVELOPER_TOOLS); } From 84491666e54235dcb64edc8fab0e199cb4254066 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 8 Oct 2018 21:44:48 -0700 Subject: [PATCH 0461/4053] Display a message when the dev tools are available Co-Authored-By: Katrina Uychaco --- lib/controllers/root-controller.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index ebe474acfd..1d2502b7d4 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -406,7 +406,10 @@ export default class RootController extends React.Component { }); await fs.ensureDir(extensionFolder, 0o755); - devTools.default(devTools.REACT_DEVELOPER_TOOLS); + await devTools.default(devTools.REACT_DEVELOPER_TOOLS); + + // eslint-disable-next-line no-console + console.log('🌈 Reload your window to start using the React dev tools!'); } getRepositoryForWorkdir(workdir) { From cc64add9408ac3c364495e227b06562e36490a45 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 8 Oct 2018 21:49:56 -0700 Subject: [PATCH 0462/4053] Actually let's use an Atom notification because it's rad Co-Authored-By: Katrina Uychaco --- lib/controllers/root-controller.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 1d2502b7d4..6f80d56360 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -408,8 +408,7 @@ export default class RootController extends React.Component { await fs.ensureDir(extensionFolder, 0o755); await devTools.default(devTools.REACT_DEVELOPER_TOOLS); - // eslint-disable-next-line no-console - console.log('🌈 Reload your window to start using the React dev tools!'); + this.props.notificationManager.addSuccess('🌈 Reload your window to start using the React dev tools!'); } getRepositoryForWorkdir(workdir) { From 305ba1ce3932f0967e8a82ba70cc16285bc5eb9e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 9 Oct 2018 16:08:13 +0200 Subject: [PATCH 0463/4053] add tracking for cloning repo events --- lib/controllers/root-controller.js | 1 + test/controllers/root-controller.test.js | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 6f80d56360..577b7e1dd8 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -472,6 +472,7 @@ export default class RootController extends React.Component { {detail: e.stdErr, dismissable: true}, ); } finally { + addEvent('clone-repo', {package: 'github'}); this.setState({cloneDialogInProgress: false, cloneDialogActive: false}); } } diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index d139648ae7..4c83ff1d51 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -342,7 +342,8 @@ describe('RootController', function() { assert.lengthOf(wrapper.find('Panel').find({location: 'modal'}).find('CloneDialog'), 1); }); - it('triggers the clone callback on accept', function() { + it('triggers the clone callback on accept and fires `clone-repo` event', function() { + sinon.stub(reporterProxy, 'addEvent'); wrapper.instance().openCloneDialog(); wrapper.update(); @@ -351,6 +352,7 @@ describe('RootController', function() { resolveClone(); assert.isTrue(cloneRepositoryForProjectPath.calledWith('git@github.com:atom/github.git', '/home/me/github')); + assert.isTrue(reporterProxy.addEvent.calledWith('clone-repo', {package: 'github'})) }); it('marks the clone dialog as in progress during clone', async function() { @@ -372,8 +374,9 @@ describe('RootController', function() { assert.isFalse(wrapper.find('CloneDialog').exists()); }); - it('creates a notification if the clone fails', async function() { + it('creates a notification if the clone fails and does not fire `clone-repo` event', async function() { sinon.stub(notificationManager, 'addError'); + sinon.stub(reporterProxy, 'addEvent'); wrapper.instance().openCloneDialog(); wrapper.update(); @@ -393,6 +396,7 @@ describe('RootController', function() { 'Unable to clone git@github.com:nope/nope.git', sinon.match({detail: sinon.match(/this is stderr/)}), )); + assert.isFalse(reporterProxy.addEvent.called); }); it('dismisses the clone panel on cancel', function() { From 19856cdf70c42f183d700730a26e75d2317a12a4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 09:03:04 -0700 Subject: [PATCH 0464/4053] Link to the repository-states README --- docs/git-interactions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/git-interactions.md b/docs/git-interactions.md index 77eef4cc4e..e73614d07d 100644 --- a/docs/git-interactions.md +++ b/docs/git-interactions.md @@ -50,7 +50,7 @@ A [`GitTempDir`](/lib/git-temp-dir.js) and [`GitPromptServer`](/lib/git-prompt-s [`Repository`](/lib/models/repository.js) is the higher-level model class that most of the view layer uses to interact with a git repository. -Repositories are stateful: when created with a path, they are **loading**, after which they may become **present** if a `.git` directory is found, or **empty** otherwise. They may also be **absent** if you don't even have a path. **Empty** repositories may transition to **initializing** or **cloning** if a `git init` or `git clone` operation is begun. +Repositories are stateful: when created with a path, they are **loading**, after which they may become **present** if a `.git` directory is found, or **empty** otherwise. They may also be **absent** if you don't even have a path. **Empty** repositories may transition to **initializing** or **cloning** if a `git init` or `git clone` operation is begun. For more details about Repository states, see [the `lib/models/repository-states/` README](/lib/models/repository-states/). Repository instances mostly delegate operations to their current _state instance_. (This delegation is not automatic; there is [an explicit list](/lib/models/repository.js#L265-L363) of methods that are delegated, which must be updated if new functionality is added.) However, Repositories do directly implement methods for: From c2ea15e2302a2ccd68788b9c3ac782b067977fa4 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Tue, 9 Oct 2018 17:16:30 -0700 Subject: [PATCH 0465/4053] Create .vsts.yml --- .vsts.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .vsts.yml diff --git a/.vsts.yml b/.vsts.yml new file mode 100644 index 0000000000..2f173b19d1 --- /dev/null +++ b/.vsts.yml @@ -0,0 +1,10 @@ +pool: + vmImage: 'Ubuntu 16.04' + +steps: +- script: echo Hello, world! + displayName: 'Run a one-line script' + +- script: | + echo Add other tasks to build, test, and deploy your project. + displayName: 'Test Task' From 4ec516f8a5b43b8b3cebcc8fb84740b00f1d713e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 17:49:39 -0700 Subject: [PATCH 0466/4053] Let's see if this gets a Linux build going --- .vsts.yml | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index 2f173b19d1..c630701acd 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -1,10 +1,18 @@ -pool: - vmImage: 'Ubuntu 16.04' - -steps: -- script: echo Hello, world! - displayName: 'Run a one-line script' - -- script: | - echo Add other tasks to build, test, and deploy your project. - displayName: 'Test Task' +jobs: +- job: Linux + pool: + vmImage: ubuntu-16.04 + variables: + display: ":99" + steps: + - script: | + curl -s -L "https://atom.io/download/deb?channel=dev" \ + -H 'Accept: application/octet-stream' \ + -o 'atom-amd64.deb' + /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 + dpkg-deb -x atom-amd64.deb "${HOME}/atom" + displayName: install Atom + - script: apm ci + displayName: install dependencies + - script: atom --test test/ + displayName: run tests From 7717ce3788d2a7df24c81f73428c29cfbe04232d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 17:52:59 -0700 Subject: [PATCH 0467/4053] Let's drop a set -x in there --- .vsts.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.vsts.yml b/.vsts.yml index c630701acd..2e116e66b6 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -6,6 +6,7 @@ jobs: display: ":99" steps: - script: | + set -x curl -s -L "https://atom.io/download/deb?channel=dev" \ -H 'Accept: application/octet-stream' \ -o 'atom-amd64.deb' From c0b93ff7adca0d009ca4ad473c6d99f5c5cabf4a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 18:04:29 -0700 Subject: [PATCH 0468/4053] Set the PATH --- .vsts.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.vsts.yml b/.vsts.yml index 2e116e66b6..df01fd9780 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -15,5 +15,9 @@ jobs: displayName: install Atom - script: apm ci displayName: install dependencies + env: + PATH: "${PATH}:${HOME}/atom/usr/bin" - script: atom --test test/ displayName: run tests + env: + PATH: "${PATH}:${HOME}/atom/usr/bin" From cdb256f58a831501ff7928dd5b86e35429729b5f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 18:08:02 -0700 Subject: [PATCH 0469/4053] Let's try using variables --- .vsts.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index df01fd9780..12a64a1ce4 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -4,8 +4,10 @@ jobs: vmImage: ubuntu-16.04 variables: display: ":99" + apm: "${HOME}/atom/usr/bin/apm" + atom: "${HOME}/atom/usr/bin/atom" steps: - - script: | + - bash: | set -x curl -s -L "https://atom.io/download/deb?channel=dev" \ -H 'Accept: application/octet-stream' \ @@ -13,11 +15,7 @@ jobs: /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 dpkg-deb -x atom-amd64.deb "${HOME}/atom" displayName: install Atom - - script: apm ci + - bash: "${APM}" ci displayName: install dependencies - env: - PATH: "${PATH}:${HOME}/atom/usr/bin" - - script: atom --test test/ + - bash: "${ATOM}" --test test/ displayName: run tests - env: - PATH: "${PATH}:${HOME}/atom/usr/bin" From 1638e0f11613e9a42e69ef0ea693a9b6da42d60c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 18:09:17 -0700 Subject: [PATCH 0470/4053] YAML --- .vsts.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index 12a64a1ce4..2b6ca1dba3 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -15,7 +15,9 @@ jobs: /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 dpkg-deb -x atom-amd64.deb "${HOME}/atom" displayName: install Atom - - bash: "${APM}" ci + - bash: | + "${APM}" ci displayName: install dependencies - - bash: "${ATOM}" --test test/ + - bash: | + "${ATOM}" --test test/ displayName: run tests From e05b1484f74eb95ab7e2b5f0b63273ba20c23caa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 18:12:11 -0700 Subject: [PATCH 0471/4053] Screw it, use /tmp --- .vsts.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index 2b6ca1dba3..c0a1b2cfb8 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -4,8 +4,6 @@ jobs: vmImage: ubuntu-16.04 variables: display: ":99" - apm: "${HOME}/atom/usr/bin/apm" - atom: "${HOME}/atom/usr/bin/atom" steps: - bash: | set -x @@ -13,11 +11,9 @@ jobs: -H 'Accept: application/octet-stream' \ -o 'atom-amd64.deb' /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 - dpkg-deb -x atom-amd64.deb "${HOME}/atom" + dpkg-deb -x atom-amd64.deb /tmp/atom displayName: install Atom - - bash: | - "${APM}" ci + - bash: /tmp/atom/usr/bin/apm ci displayName: install dependencies - - bash: | - "${ATOM}" --test test/ + - bash: /tmp/atom/usr/bin/atom --test test/ displayName: run tests From 4eb5f8086308897d0d4b480f99a557c7a21f09ba Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 18:14:31 -0700 Subject: [PATCH 0472/4053] Let's see if that directory is being populated --- .vsts.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.vsts.yml b/.vsts.yml index c0a1b2cfb8..38cf903c58 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -12,6 +12,7 @@ jobs: -o 'atom-amd64.deb' /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 dpkg-deb -x atom-amd64.deb /tmp/atom + find /tmp/atom -type d displayName: install Atom - bash: /tmp/atom/usr/bin/apm ci displayName: install dependencies From dad693fbc10235c70caec43e0cd335cc6a981aa9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 18:16:35 -0700 Subject: [PATCH 0473/4053] Dump the files in /usr/bin --- .vsts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vsts.yml b/.vsts.yml index 38cf903c58..b5061e4d44 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -12,7 +12,7 @@ jobs: -o 'atom-amd64.deb' /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 dpkg-deb -x atom-amd64.deb /tmp/atom - find /tmp/atom -type d + find /tmp/atom/usr/bin -type f displayName: install Atom - bash: /tmp/atom/usr/bin/apm ci displayName: install dependencies From 6ab2e9a6534b55480781e6d26bbb5f5763fa01fd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 18:19:04 -0700 Subject: [PATCH 0474/4053] Let's try to find atom- and apm- --- .vsts.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vsts.yml b/.vsts.yml index b5061e4d44..a06bba582f 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -12,7 +12,8 @@ jobs: -o 'atom-amd64.deb' /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 dpkg-deb -x atom-amd64.deb /tmp/atom - find /tmp/atom/usr/bin -type f + find /tmp/atom/ -type f -name 'atom*' + find /tmp/atom/ -type f -name 'apm*' displayName: install Atom - bash: /tmp/atom/usr/bin/apm ci displayName: install dependencies From 4febd69dad43df346ff9c3779ec24e251b7df78f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 18:21:19 -0700 Subject: [PATCH 0475/4053] Use the find output --- .vsts.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index a06bba582f..a57794c7a7 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -15,7 +15,7 @@ jobs: find /tmp/atom/ -type f -name 'atom*' find /tmp/atom/ -type f -name 'apm*' displayName: install Atom - - bash: /tmp/atom/usr/bin/apm ci + - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci displayName: install dependencies - - bash: /tmp/atom/usr/bin/atom --test test/ + - bash: /tmp/atom/usr/bin/atom-dev --test test/ displayName: run tests From 5852e623a8b2bf2d844d3c2fbec604b5ddcc090a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 18:26:03 -0700 Subject: [PATCH 0476/4053] Install libgconf --- .vsts.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.vsts.yml b/.vsts.yml index a57794c7a7..f46543531e 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -11,6 +11,7 @@ jobs: -H 'Accept: application/octet-stream' \ -o 'atom-amd64.deb' /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 + apt-get install libgconf-2-4 dpkg-deb -x atom-amd64.deb /tmp/atom find /tmp/atom/ -type f -name 'atom*' find /tmp/atom/ -type f -name 'apm*' From 1d09a0a16a76d9abd1cf369ceecf277938bcd399 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 18:28:53 -0700 Subject: [PATCH 0477/4053] sudo? --- .vsts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vsts.yml b/.vsts.yml index f46543531e..66c96f661e 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -11,7 +11,7 @@ jobs: -H 'Accept: application/octet-stream' \ -o 'atom-amd64.deb' /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 - apt-get install libgconf-2-4 + sudo apt-get install libgconf-2-4 dpkg-deb -x atom-amd64.deb /tmp/atom find /tmp/atom/ -type f -name 'atom*' find /tmp/atom/ -type f -name 'apm*' From 90bbddff9a3373b79e430d3cc8c312f5d98d6fea Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 18:34:36 -0700 Subject: [PATCH 0478/4053] update and pass -y --- .vsts.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index 66c96f661e..0e1c87d8fe 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -11,10 +11,8 @@ jobs: -H 'Accept: application/octet-stream' \ -o 'atom-amd64.deb' /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 - sudo apt-get install libgconf-2-4 + sudo apt-get update && sudo apt-get install -yyq libgconf-2-4 dpkg-deb -x atom-amd64.deb /tmp/atom - find /tmp/atom/ -type f -name 'atom*' - find /tmp/atom/ -type f -name 'apm*' displayName: install Atom - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci displayName: install dependencies From 93ceee2f78d721eb9ce1d71ce74b180f0f2875c9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 22:02:39 -0700 Subject: [PATCH 0479/4053] Install other aptitude dependencies --- .vsts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vsts.yml b/.vsts.yml index 0e1c87d8fe..3f121cc9d3 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -11,7 +11,7 @@ jobs: -H 'Accept: application/octet-stream' \ -o 'atom-amd64.deb' /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 - sudo apt-get update && sudo apt-get install -yyq libgconf-2-4 + sudo apt-get update && sudo apt-get install -yyq libgconf-2-4 build-essential git libsecret-1-dev dpkg-deb -x atom-amd64.deb /tmp/atom displayName: install Atom - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci From e378e1553723d1655ef5b5f33898ed4da54dcc8e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 22:16:00 -0700 Subject: [PATCH 0480/4053] That set -x is no longer necessary --- .vsts.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.vsts.yml b/.vsts.yml index 3f121cc9d3..ffae675f51 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -6,7 +6,6 @@ jobs: display: ":99" steps: - bash: | - set -x curl -s -L "https://atom.io/download/deb?channel=dev" \ -H 'Accept: application/octet-stream' \ -o 'atom-amd64.deb' From 751db6a5f79a3e72960343b91d2d7691dc19e246 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 22:16:17 -0700 Subject: [PATCH 0481/4053] Start hacking on a macOS build --- .vsts.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.vsts.yml b/.vsts.yml index ffae675f51..b27d5fbd7f 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -17,3 +17,19 @@ jobs: displayName: install dependencies - bash: /tmp/atom/usr/bin/atom-dev --test test/ displayName: run tests + +- job: MacOS + pool: + vmImage: macos-10.13 + steps: + - bash: | + curl -s -L "https://atom.io/download/mac?channel=dev" \ + -H 'Accept: application/octet-stream' \ + -o "atom.zip" + mkdir -p /tmp/atom + unzip -q atom.zip /tmp/atom + displayName: install Atom + - bash: /tmp/atom/Atom.app/Contents/Resources/app/apm/bin/apm ci + displayName: install dependencies + - bash: /tmp/atom/Atom.app/Contents/Resources/app/atom.sh --test test/ + displayName: run tests From 2857753087fb8c581a8a2c11dfed0d7bbf493d5f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 22:18:49 -0700 Subject: [PATCH 0482/4053] Missed a -d argument --- .vsts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vsts.yml b/.vsts.yml index b27d5fbd7f..0dc40888ce 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -27,7 +27,7 @@ jobs: -H 'Accept: application/octet-stream' \ -o "atom.zip" mkdir -p /tmp/atom - unzip -q atom.zip /tmp/atom + unzip -q atom.zip -d /tmp/atom displayName: install Atom - bash: /tmp/atom/Atom.app/Contents/Resources/app/apm/bin/apm ci displayName: install dependencies From 32b8d58ce0eff21d4df25bbcb536796ab17aaa1d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 22:22:41 -0700 Subject: [PATCH 0483/4053] Diagnostics --- .vsts.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vsts.yml b/.vsts.yml index 0dc40888ce..d2ca930b04 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -23,11 +23,14 @@ jobs: vmImage: macos-10.13 steps: - bash: | + set -x curl -s -L "https://atom.io/download/mac?channel=dev" \ -H 'Accept: application/octet-stream' \ -o "atom.zip" mkdir -p /tmp/atom unzip -q atom.zip -d /tmp/atom + find /tmp/atom -name 'atom*' + find /tmp/atom -name 'apm*' displayName: install Atom - bash: /tmp/atom/Atom.app/Contents/Resources/app/apm/bin/apm ci displayName: install dependencies From efe716cadbf43446fe70b2b2d65421b0d2e8eb73 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 22:26:41 -0700 Subject: [PATCH 0484/4053] Use the paths we just found --- .vsts.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index d2ca930b04..b97ae25e9b 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -32,7 +32,7 @@ jobs: find /tmp/atom -name 'atom*' find /tmp/atom -name 'apm*' displayName: install Atom - - bash: /tmp/atom/Atom.app/Contents/Resources/app/apm/bin/apm ci + - bash: /tmp/atom/Atom\ Dev.app/Contents/Resources/app/apm/bin/apm ci displayName: install dependencies - - bash: /tmp/atom/Atom.app/Contents/Resources/app/atom.sh --test test/ + - bash: /tmp/atom/Atom\ Dev.app/Contents/Resources/app/atom.sh --test test/ displayName: run tests From 1ab6056643d29dcd70bd588cb49a9e308b0190f5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 22:49:50 -0700 Subject: [PATCH 0485/4053] A Windows build maybe --- .vsts.yml | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/.vsts.yml b/.vsts.yml index b97ae25e9b..f4d0fe9c61 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -36,3 +36,58 @@ jobs: displayName: install dependencies - bash: /tmp/atom/Atom\ Dev.app/Contents/Resources/app/atom.sh --test test/ displayName: run tests + +- job: Windows + pool: + vmImage: vs2015-win2012r2 + steps: + - powershell: | + Set-StrictMode -Version Latest + $script:ATOMROOT = "$env:AGENT_HOME_DIRECTORY/atom" + Set-Location $env:AGENT_BUILD_DIRECTORY + + $env:ELECTRON_NO_ATTACH_CONSOLE = "true" + [Environment]::SetEnvironmentVariable("ELECTRON_NO_ATTACH_CONSOLE", "true", "User") + $env:ELECTRON_ENABLE_LOGGING = "YES" + [Environment]::SetEnvironmentVariable("ELECTRON_ENABLE_LOGGING", "YES", "User") + + $script:ATOM_SCRIPT_PATH = "$script:ATOMROOT\Atom Dev\resources\cli\atom.cmd" + $script:APM_SCRIPT_PATH = "$script:ATOMROOT\Atom Dev\resources\app\apm\bin\apm.cmd" + + function DownloadAtom() { + Write-Host "Downloading latest Atom release" + $source = "https://atom.io/download/windows_zip?channel=dev" + $destination = "atom.zip" + + (New-Object System.Net.WebClient).DownloadFile($source, $destination) + } + + function ExtractAtom() { + [System.IO.Compression.ZipFile]::ExtractToDirectory("atom.zip", $script:ATOMROOT) + } + + function InstallDependencies() { + Write-Host "Installing package dependencies" + & "$script:APM_SCRIPT_PATH" ci + if ($LASTEXITCODE -ne 0) { + Write-Host "Dependency installation failed" + $host.SetShouldExit($LASTEXITCODE) + exit + } + } + + function RunTests() { + Write-Host "Running tests" + & "$scripot:ATOM_SCRIPT_PATH" --test test 2>&1 | %{ "$_" } + + if ($LASTEXITCODE -ne 0) { + Write-Host "Specs Failed" + $host.SetShouldExit($LASTEXITCODE) + exit + } + } + + DownloadAtom + ExtractAtom + InstallDependencies + RunTests From f187f1ac971cf39018342be50f0559fe7abffb44 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 22:50:08 -0700 Subject: [PATCH 0486/4053] Typo :eyes: --- .vsts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vsts.yml b/.vsts.yml index f4d0fe9c61..8a200cd517 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -78,7 +78,7 @@ jobs: function RunTests() { Write-Host "Running tests" - & "$scripot:ATOM_SCRIPT_PATH" --test test 2>&1 | %{ "$_" } + & "$script:ATOM_SCRIPT_PATH" --test test 2>&1 | %{ "$_" } if ($LASTEXITCODE -ne 0) { Write-Host "Specs Failed" From 188b19809ed27e5775b9124b00e82066b44a81fc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 22:53:27 -0700 Subject: [PATCH 0487/4053] Touch up env var references --- .vsts.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index 8a200cd517..4e703b6042 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -43,8 +43,7 @@ jobs: steps: - powershell: | Set-StrictMode -Version Latest - $script:ATOMROOT = "$env:AGENT_HOME_DIRECTORY/atom" - Set-Location $env:AGENT_BUILD_DIRECTORY + $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" $env:ELECTRON_NO_ATTACH_CONSOLE = "true" [Environment]::SetEnvironmentVariable("ELECTRON_NO_ATTACH_CONSOLE", "true", "User") From e8da95001996bf992eed1886696eb494adff65e2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 22:57:30 -0700 Subject: [PATCH 0488/4053] Let's see if we have Expand-Archive --- .vsts.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index 4e703b6042..c458dc0d28 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -59,10 +59,7 @@ jobs: $destination = "atom.zip" (New-Object System.Net.WebClient).DownloadFile($source, $destination) - } - - function ExtractAtom() { - [System.IO.Compression.ZipFile]::ExtractToDirectory("atom.zip", $script:ATOMROOT) + Expand-Archive -Path "atom.zip" -DestinationPath $script:ATOMROOT } function InstallDependencies() { @@ -87,6 +84,5 @@ jobs: } DownloadAtom - ExtractAtom InstallDependencies RunTests From 36305882a1eab18b6b86b7b0b83ce131a02f0315 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 9 Oct 2018 23:08:05 -0700 Subject: [PATCH 0489/4053] Split Windows build steps --- .vsts.yml | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index c458dc0d28..f84406c5b3 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -45,44 +45,42 @@ jobs: Set-StrictMode -Version Latest $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" - $env:ELECTRON_NO_ATTACH_CONSOLE = "true" - [Environment]::SetEnvironmentVariable("ELECTRON_NO_ATTACH_CONSOLE", "true", "User") - $env:ELECTRON_ENABLE_LOGGING = "YES" - [Environment]::SetEnvironmentVariable("ELECTRON_ENABLE_LOGGING", "YES", "User") + Write-Host "Downloading latest Atom release" + $source = "https://atom.io/download/windows_zip?channel=dev" + $destination = "atom.zip" - $script:ATOM_SCRIPT_PATH = "$script:ATOMROOT\Atom Dev\resources\cli\atom.cmd" + (New-Object System.Net.WebClient).DownloadFile($source, $destination) + Expand-Archive -Path "atom.zip" -DestinationPath $script:ATOMROOT + displayName: install Atom + - powershell: | + Set-StrictMode -Version Latest + $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" $script:APM_SCRIPT_PATH = "$script:ATOMROOT\Atom Dev\resources\app\apm\bin\apm.cmd" - function DownloadAtom() { - Write-Host "Downloading latest Atom release" - $source = "https://atom.io/download/windows_zip?channel=dev" - $destination = "atom.zip" - - (New-Object System.Net.WebClient).DownloadFile($source, $destination) - Expand-Archive -Path "atom.zip" -DestinationPath $script:ATOMROOT + Write-Host "Installing package dependencies" + & "$script:APM_SCRIPT_PATH" ci + if ($LASTEXITCODE -ne 0) { + Write-Host "Dependency installation failed" + $host.SetShouldExit($LASTEXITCODE) + exit } + displayName: install dependencies + - powershell: | + Set-StrictMode -Version Latest + $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" + $script:ATOM_SCRIPT_PATH = "$script:ATOMROOT\Atom Dev\resources\cli\atom.cmd" - function InstallDependencies() { - Write-Host "Installing package dependencies" - & "$script:APM_SCRIPT_PATH" ci - if ($LASTEXITCODE -ne 0) { - Write-Host "Dependency installation failed" - $host.SetShouldExit($LASTEXITCODE) - exit - } - } + $env:ELECTRON_NO_ATTACH_CONSOLE = "true" + [Environment]::SetEnvironmentVariable("ELECTRON_NO_ATTACH_CONSOLE", "true", "User") + $env:ELECTRON_ENABLE_LOGGING = "YES" + [Environment]::SetEnvironmentVariable("ELECTRON_ENABLE_LOGGING", "YES", "User") - function RunTests() { - Write-Host "Running tests" - & "$script:ATOM_SCRIPT_PATH" --test test 2>&1 | %{ "$_" } + Write-Host "Running tests" + & "$script:ATOM_SCRIPT_PATH" --test test 2>&1 | %{ "$_" } - if ($LASTEXITCODE -ne 0) { - Write-Host "Specs Failed" - $host.SetShouldExit($LASTEXITCODE) - exit - } + if ($LASTEXITCODE -ne 0) { + Write-Host "Specs Failed" + $host.SetShouldExit($LASTEXITCODE) + exit } - - DownloadAtom - InstallDependencies - RunTests + displayName: run tests From 6b6fb71f50f03bfbc7fde64be0c8d162c71574e7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 09:15:42 -0700 Subject: [PATCH 0490/4053] mocha-junit-reporter is the one that Azure devops is happiest with maybe --- package-lock.json | 59 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 60 insertions(+) diff --git a/package-lock.json b/package-lock.json index a58c8ccd05..c7c4af689c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1916,6 +1916,12 @@ "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", "dev": true }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", + "dev": true + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -2202,6 +2208,12 @@ "integrity": "sha1-Ae0dS7JDujObLD8Dxbp6eIGJhMY=", "dev": true }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", + "dev": true + }, "cryptiles": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", @@ -5006,6 +5018,17 @@ "object-visit": "^1.0.0" } }, + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "dev": true, + "requires": { + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" + } + }, "md5-hex": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.3.0.tgz", @@ -5328,6 +5351,36 @@ "request-json": "^0.6.3" } }, + "mocha-junit-reporter": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.18.0.tgz", + "integrity": "sha512-y3XuqKa2+HRYtg0wYyhW/XsLm2Ps+pqf9HaTAt7+MVUAKFJaNAHOrNseTZo9KCxjfIbxUWwckP5qCDDPUmjSWA==", + "dev": true, + "requires": { + "debug": "^2.2.0", + "md5": "^2.1.0", + "mkdirp": "~0.5.1", + "strip-ansi": "^4.0.0", + "xml": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "mocha-multi-reporters": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz", @@ -8404,6 +8457,12 @@ "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", "dev": true }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 5a34d79c67..c557e67763 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "mkdirp": "0.5.1", "mocha": "^5.2.0", "mocha-appveyor-reporter": "0.4.1", + "mocha-junit-reporter": "1.18.0", "mocha-multi-reporters": "^1.1.7", "mocha-stress": "1.0.0", "node-fetch": "2.2.0", From 2f669d72f344620e8e9e7eb57443aa5f25b3070d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 09:31:17 -0700 Subject: [PATCH 0491/4053] Activate the JUnit XML reporter --- .vsts.yml | 23 +++++++++++++++-------- test/runner.js | 17 +++++------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index f84406c5b3..29ab9a64eb 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -17,6 +17,9 @@ jobs: displayName: install dependencies - bash: /tmp/atom/usr/bin/atom-dev --test test/ displayName: run tests + env: + TEST_JUNIT_XML_PATH: test-results.xml + FORCE_COLOR: 0 - job: MacOS pool: @@ -29,13 +32,14 @@ jobs: -o "atom.zip" mkdir -p /tmp/atom unzip -q atom.zip -d /tmp/atom - find /tmp/atom -name 'atom*' - find /tmp/atom -name 'apm*' displayName: install Atom - bash: /tmp/atom/Atom\ Dev.app/Contents/Resources/app/apm/bin/apm ci displayName: install dependencies - bash: /tmp/atom/Atom\ Dev.app/Contents/Resources/app/atom.sh --test test/ displayName: run tests + env: + TEST_JUNIT_XML_PATH: test-results.xml + FORCE_COLOR: 0 - job: Windows pool: @@ -60,9 +64,9 @@ jobs: Write-Host "Installing package dependencies" & "$script:APM_SCRIPT_PATH" ci if ($LASTEXITCODE -ne 0) { - Write-Host "Dependency installation failed" - $host.SetShouldExit($LASTEXITCODE) - exit + Write-Host "Dependency installation failed" + $host.SetShouldExit($LASTEXITCODE) + exit } displayName: install dependencies - powershell: | @@ -79,8 +83,11 @@ jobs: & "$script:ATOM_SCRIPT_PATH" --test test 2>&1 | %{ "$_" } if ($LASTEXITCODE -ne 0) { - Write-Host "Specs Failed" - $host.SetShouldExit($LASTEXITCODE) - exit + Write-Host "Specs Failed" + $host.SetShouldExit($LASTEXITCODE) + exit } displayName: run tests + env: + TEST_JUNIT_XML_PATH: test-results.xml + FORCE_COLOR: 0 diff --git a/test/runner.js b/test/runner.js index 321cf84a84..3974bf75e1 100644 --- a/test/runner.js +++ b/test/runner.js @@ -110,18 +110,11 @@ module.exports = createRunner({ if (process.env.TEST_JUNIT_XML_PATH) { mocha.reporter(require('mocha-multi-reporters'), { - reportersEnabled: 'xunit, list', - xunitReporterOptions: { - output: process.env.TEST_JUNIT_XML_PATH, - }, - }); - } else if (process.env.APPVEYOR_API_URL) { - mocha.reporter(require('mocha-appveyor-reporter')); - } else if (process.env.CIRCLECI === 'true') { - mocha.reporter(require('mocha-multi-reporters'), { - reportersEnabled: 'xunit, list', - xunitReporterOptions: { - output: path.join('test-results', 'mocha', 'test-results.xml'), + reportersEnabled: 'junit, list', + junitReporterOptions: { + mochaFile: process.env.TEST_JUNIT_XML_PATH, + useFullSuiteTitle: true, + suiteTitleSeparedBy: ' / ', }, }); } From aaf33b27f4868a7d635cd84dd1c8b1e9ed113e1a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 09:36:10 -0700 Subject: [PATCH 0492/4053] Publish test reports --- .vsts.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.vsts.yml b/.vsts.yml index 29ab9a64eb..c242acc291 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -17,9 +17,15 @@ jobs: displayName: install dependencies - bash: /tmp/atom/usr/bin/atom-dev --test test/ displayName: run tests + continueOnError: true env: TEST_JUNIT_XML_PATH: test-results.xml FORCE_COLOR: 0 + - task: PublishTestResults@2 + inputs: + testResultsFormat: JUnit + testResultsFiles: test-results.xml + testRunTitle: Linux - job: MacOS pool: @@ -37,9 +43,15 @@ jobs: displayName: install dependencies - bash: /tmp/atom/Atom\ Dev.app/Contents/Resources/app/atom.sh --test test/ displayName: run tests + continueOnError: true env: TEST_JUNIT_XML_PATH: test-results.xml FORCE_COLOR: 0 + - task: PublishTestResults@2 + inputs: + testResultsFormat: JUnit + testResultsFiles: test-results.xml + testRunTitle: MacOS - job: Windows pool: @@ -88,6 +100,12 @@ jobs: exit } displayName: run tests + continueOnError: true env: TEST_JUNIT_XML_PATH: test-results.xml FORCE_COLOR: 0 + - task: PublishTestResults@2 + inputs: + testResultsFormat: JUnit + testResultsFiles: test-results.xml + testRunTitle: Windows From 504103456383bf1e3af0c18b4db0d0ddf465edf9 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 10 Oct 2018 19:01:38 +0200 Subject: [PATCH 0493/4053] instrumentation for context menu actions --- lib/controllers/root-controller.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 577b7e1dd8..a26299f5ef 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -94,6 +94,16 @@ export default class RootController extends React.Component { this.subscription = new CompositeDisposable( this.props.repository.onMergeError(this.gitTabTracker.ensureVisible), ); + + this.props.commandRegistry.onDidDispatch(event => { + if (event.type && event.type.startsWith('github:') + && event.detail && event.detail[0] && event.detail[0].contextCommand) { + addEvent('context-menu-action', { + package: 'github', + command: event.type, + }); + } + }); } componentDidMount() { From 16762939415f157557679f7c5b09cedc907c4bea Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 10:35:15 -0700 Subject: [PATCH 0494/4053] Explicitly use Agent.HomeDirectory --- .vsts.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index c242acc291..7af2863658 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -19,12 +19,12 @@ jobs: displayName: run tests continueOnError: true env: - TEST_JUNIT_XML_PATH: test-results.xml + TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml FORCE_COLOR: 0 - task: PublishTestResults@2 inputs: testResultsFormat: JUnit - testResultsFiles: test-results.xml + testResultsFiles: $(Agent.HomeDirectory)/test-results.xml testRunTitle: Linux - job: MacOS @@ -45,12 +45,12 @@ jobs: displayName: run tests continueOnError: true env: - TEST_JUNIT_XML_PATH: test-results.xml + TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml FORCE_COLOR: 0 - task: PublishTestResults@2 inputs: testResultsFormat: JUnit - testResultsFiles: test-results.xml + testResultsFiles: $(Agent.HomeDirectory)/test-results.xml testRunTitle: MacOS - job: Windows @@ -102,10 +102,10 @@ jobs: displayName: run tests continueOnError: true env: - TEST_JUNIT_XML_PATH: test-results.xml + TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml FORCE_COLOR: 0 - task: PublishTestResults@2 inputs: testResultsFormat: JUnit - testResultsFiles: test-results.xml + testResultsFiles: $(Agent.HomeDirectory)/test-results.xml testRunTitle: Windows From b0acfb97145f554ce4a8fc6d91c36ddc520d8335 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 10:45:34 -0700 Subject: [PATCH 0495/4053] ??? --- .vsts.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.vsts.yml b/.vsts.yml index 7af2863658..7ddaa19d09 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -15,7 +15,9 @@ jobs: displayName: install Atom - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci displayName: install dependencies - - bash: /tmp/atom/usr/bin/atom-dev --test test/ + - bash: | + /tmp/atom/usr/bin/atom-dev --test test/ + echo "TEST_JUNIT_XML_PATH=${TEST_JUNIT_XML_PATH}" displayName: run tests continueOnError: true env: From eb619a5f670b1fddbe73dc186bcc143bb4ab62f5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 15:11:38 -0700 Subject: [PATCH 0496/4053] NOTICE ME VSTS-SENPAI --- .vsts.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vsts.yml b/.vsts.yml index 7ddaa19d09..fa31977095 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -16,8 +16,9 @@ jobs: - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci displayName: install dependencies - bash: | - /tmp/atom/usr/bin/atom-dev --test test/ echo "TEST_JUNIT_XML_PATH=${TEST_JUNIT_XML_PATH}" + env + /tmp/atom/usr/bin/atom-dev --test test/ displayName: run tests continueOnError: true env: From 8a992dbac7b0fb32a849202add785b12da7c78ce Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 10 Oct 2018 23:56:43 +0000 Subject: [PATCH 0497/4053] chore(package): update semver to version 5.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a34d79c67..96e5e60cab 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "node-fetch": "2.2.0", "nyc": "13.0.0", "relay-compiler": "1.6.2", - "semver": "5.5.1", + "semver": "5.6.0", "sinon": "6.0.1", "test-until": "1.1.1" }, From 70cc1c57eb1a209f93d3d590789a34144b565ef6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 10 Oct 2018 23:56:47 +0000 Subject: [PATCH 0498/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index a58c8ccd05..10c7967d5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5060,7 +5060,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -7038,9 +7038,9 @@ "dev": true }, "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, "set-blocking": { "version": "2.0.0", From 04e3bcdab47c1269a93c049403032456faa02c3d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 17:35:22 -0700 Subject: [PATCH 0499/4053] Look mocha-multi-reporters is hard okay --- test/runner.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/runner.js b/test/runner.js index 3974bf75e1..af14bae295 100644 --- a/test/runner.js +++ b/test/runner.js @@ -109,8 +109,9 @@ module.exports = createRunner({ mocha.timeout(parseInt(process.env.MOCHA_TIMEOUT || '5000', 10)); if (process.env.TEST_JUNIT_XML_PATH) { + console.log(`logging junit results to ${process.env.TEST_JUNIT_XML_PATH}`); mocha.reporter(require('mocha-multi-reporters'), { - reportersEnabled: 'junit, list', + reporterEnabled: 'mocha-junit-reporter, list', junitReporterOptions: { mochaFile: process.env.TEST_JUNIT_XML_PATH, useFullSuiteTitle: true, From ccd54a5ee1b807fed9b26626273846a9964e96f9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 17:47:17 -0700 Subject: [PATCH 0500/4053] Okay now that's working --- test/runner.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/runner.js b/test/runner.js index af14bae295..13b6929609 100644 --- a/test/runner.js +++ b/test/runner.js @@ -109,7 +109,6 @@ module.exports = createRunner({ mocha.timeout(parseInt(process.env.MOCHA_TIMEOUT || '5000', 10)); if (process.env.TEST_JUNIT_XML_PATH) { - console.log(`logging junit results to ${process.env.TEST_JUNIT_XML_PATH}`); mocha.reporter(require('mocha-multi-reporters'), { reporterEnabled: 'mocha-junit-reporter, list', junitReporterOptions: { From 6b49c0ea4df74687f5a2067631b82b4d7cd4ed6e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 17:48:02 -0700 Subject: [PATCH 0501/4053] Let's see if that file is actually there --- .vsts.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index fa31977095..b444725a03 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -16,9 +16,8 @@ jobs: - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci displayName: install dependencies - bash: | - echo "TEST_JUNIT_XML_PATH=${TEST_JUNIT_XML_PATH}" - env /tmp/atom/usr/bin/atom-dev --test test/ + ls -l ${AGENT_HOMEDIRECTORY} displayName: run tests continueOnError: true env: From cdfa26cc9c039f2dc081469f56bef092f61db483 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 17:48:45 -0700 Subject: [PATCH 0502/4053] Oh right --- test/runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runner.js b/test/runner.js index 13b6929609..101f08fc55 100644 --- a/test/runner.js +++ b/test/runner.js @@ -111,7 +111,7 @@ module.exports = createRunner({ if (process.env.TEST_JUNIT_XML_PATH) { mocha.reporter(require('mocha-multi-reporters'), { reporterEnabled: 'mocha-junit-reporter, list', - junitReporterOptions: { + mochaJunitReporterReporterOptions: { mochaFile: process.env.TEST_JUNIT_XML_PATH, useFullSuiteTitle: true, suiteTitleSeparedBy: ' / ', From dc83ed16d1113d429578408a226b5a150b2e2f6e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 17:56:48 -0700 Subject: [PATCH 0503/4053] Fewer diagnostics --- .vsts.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index b444725a03..7af2863658 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -15,9 +15,7 @@ jobs: displayName: install Atom - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci displayName: install dependencies - - bash: | - /tmp/atom/usr/bin/atom-dev --test test/ - ls -l ${AGENT_HOMEDIRECTORY} + - bash: /tmp/atom/usr/bin/atom-dev --test test/ displayName: run tests continueOnError: true env: From ca7c4a359f7df7cc37eb3be17cf9f890cb4b184f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 18:08:51 -0700 Subject: [PATCH 0504/4053] Use condition: on the publish step instead of continueOnError: --- .vsts.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index 7af2863658..9531fe3b58 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -17,7 +17,6 @@ jobs: displayName: install dependencies - bash: /tmp/atom/usr/bin/atom-dev --test test/ displayName: run tests - continueOnError: true env: TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml FORCE_COLOR: 0 @@ -26,6 +25,7 @@ jobs: testResultsFormat: JUnit testResultsFiles: $(Agent.HomeDirectory)/test-results.xml testRunTitle: Linux + condition: succeededOrFailed() - job: MacOS pool: @@ -43,7 +43,6 @@ jobs: displayName: install dependencies - bash: /tmp/atom/Atom\ Dev.app/Contents/Resources/app/atom.sh --test test/ displayName: run tests - continueOnError: true env: TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml FORCE_COLOR: 0 @@ -52,6 +51,7 @@ jobs: testResultsFormat: JUnit testResultsFiles: $(Agent.HomeDirectory)/test-results.xml testRunTitle: MacOS + condition: succeededOrFailed() - job: Windows pool: @@ -100,7 +100,6 @@ jobs: exit } displayName: run tests - continueOnError: true env: TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml FORCE_COLOR: 0 @@ -109,3 +108,4 @@ jobs: testResultsFormat: JUnit testResultsFiles: $(Agent.HomeDirectory)/test-results.xml testRunTitle: Windows + condition: succeededOrFailed() From 809432d3ab510c0a4bb719a400a3483a08a10d45 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 18:37:34 -0700 Subject: [PATCH 0505/4053] Add a linting job --- .vsts.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.vsts.yml b/.vsts.yml index 9531fe3b58..2564016497 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -109,3 +109,22 @@ jobs: testResultsFiles: $(Agent.HomeDirectory)/test-results.xml testRunTitle: Windows condition: succeededOrFailed() + +- job: Lint + pool: + vmImage: ubuntu-16.04 + variables: + display: ":99" + steps: + - bash: | + curl -s -L "https://atom.io/download/deb?channel=dev" \ + -H 'Accept: application/octet-stream' \ + -o 'atom-amd64.deb' + /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 + sudo apt-get update && sudo apt-get install -yyq libgconf-2-4 build-essential git libsecret-1-dev + dpkg-deb -x atom-amd64.deb /tmp/atom + displayName: install Atom + - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci + displayName: install dependencies + - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/node_modules/.bin/npm run lint + displayName: run the linter From 3f52173a344598223bf7b5fa3b57f8f1813d69e7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 18:39:38 -0700 Subject: [PATCH 0506/4053] Snapshot job --- .vsts.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.vsts.yml b/.vsts.yml index 2564016497..8e8090cbb6 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -128,3 +128,33 @@ jobs: displayName: install dependencies - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/node_modules/.bin/npm run lint displayName: run the linter + +jobs: +- job: Snapshot + pool: + vmImage: ubuntu-16.04 + variables: + display: ":99" + steps: + - bash: | + curl -s -L "https://atom.io/download/deb?channel=dev" \ + -H 'Accept: application/octet-stream' \ + -o 'atom-amd64.deb' + /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 + sudo apt-get update && sudo apt-get install -yyq libgconf-2-4 build-essential git libsecret-1-dev + dpkg-deb -x atom-amd64.deb /tmp/atom + displayName: install Atom + - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci + displayName: install dependencies + - bash: /tmp/atom/usr/bin/atom-dev --test test/ + displayName: run tests + env: + ATOM_GITHUB_TEST_SUITE: snapshot + TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml + FORCE_COLOR: 0 + - task: PublishTestResults@2 + inputs: + testResultsFormat: JUnit + testResultsFiles: $(Agent.HomeDirectory)/test-results.xml + testRunTitle: Snapshot + condition: succeededOrFailed() From 564137e7b66ad2b5d092777eed8fcd04bc8cfa30 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 18:40:06 -0700 Subject: [PATCH 0507/4053] Copy paste fail --- .vsts.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.vsts.yml b/.vsts.yml index 8e8090cbb6..3aef34fb23 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -129,7 +129,6 @@ jobs: - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/node_modules/.bin/npm run lint displayName: run the linter -jobs: - job: Snapshot pool: vmImage: ubuntu-16.04 From d0cff3ed9def1da8694b900d686511eb7e47ae95 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 21:42:40 -0700 Subject: [PATCH 0508/4053] Use .gitattributes to keep fixtures lf --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..c323b28ee2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +test/fixtures/conflict-marker-examples/*.txt text eol=lf From 26e8e34802aed6f2a07327afab9e75d3055fda6e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 22:17:11 -0700 Subject: [PATCH 0509/4053] Normalize 8.3 repo paths on Windows --- test/helpers.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/helpers.js b/test/helpers.js index 1d4c707324..5ae1754fe4 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -2,6 +2,7 @@ import fs from 'fs-extra'; import path from 'path'; import temp from 'temp'; import until from 'test-until'; +import globby from 'globby'; import transpiler from 'atom-babel6-transpiler'; import React from 'react'; @@ -27,10 +28,13 @@ assert.autocrlfEqual = (actual, expected, ...args) => { // for each subsequent request to clone makes cloning // 2-3x faster on macOS and 5-10x faster on Windows const cachedClonedRepos = {}; -function copyCachedRepo(repoName) { +async function copyCachedRepo(repoName) { const workingDirPath = temp.mkdirSync('git-fixture-'); - fs.copySync(cachedClonedRepos[repoName], workingDirPath); - return fs.realpath(workingDirPath); + await fs.copy(cachedClonedRepos[repoName], workingDirPath); + const realPath = await fs.realpath(workingDirPath); + + // Normalize as a "long path" (not 8.3 form) on Windows + return (await globby(realPath))[0]; } export const FAKE_USER = { From db604acb0ac1374007173ebf991b5d4ac000049d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 22:43:01 -0700 Subject: [PATCH 0510/4053] Let's try to convert %TEMP% in Powershell land --- .vsts.yml | 4 ++++ test/helpers.js | 5 +---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index 3aef34fb23..44118d7b8a 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -86,6 +86,10 @@ jobs: $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" $script:ATOM_SCRIPT_PATH = "$script:ATOMROOT\Atom Dev\resources\cli\atom.cmd" + Write-Output "TEMP before: $env:TEMP" + $env:TEMP = (Get-Item -LiteralPath $env:TEMP).FullName + Write-Output "TEMP after: $env:TEMP" + $env:ELECTRON_NO_ATTACH_CONSOLE = "true" [Environment]::SetEnvironmentVariable("ELECTRON_NO_ATTACH_CONSOLE", "true", "User") $env:ELECTRON_ENABLE_LOGGING = "YES" diff --git a/test/helpers.js b/test/helpers.js index 5ae1754fe4..1d033893ee 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -31,10 +31,7 @@ const cachedClonedRepos = {}; async function copyCachedRepo(repoName) { const workingDirPath = temp.mkdirSync('git-fixture-'); await fs.copy(cachedClonedRepos[repoName], workingDirPath); - const realPath = await fs.realpath(workingDirPath); - - // Normalize as a "long path" (not 8.3 form) on Windows - return (await globby(realPath))[0]; + return fs.realpath(workingDirPath); } export const FAKE_USER = { From 89e3d6d974cb39686fb1621c1ed6e1cb5ed48dfb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 22:56:40 -0700 Subject: [PATCH 0511/4053] :shirt: --- test/helpers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/helpers.js b/test/helpers.js index 1d033893ee..b3a5ba62f6 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -2,7 +2,6 @@ import fs from 'fs-extra'; import path from 'path'; import temp from 'temp'; import until from 'test-until'; -import globby from 'globby'; import transpiler from 'atom-babel6-transpiler'; import React from 'react'; From d8cc3c3c59cb1521c983065428c058b3152f71ed Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 22:57:44 -0700 Subject: [PATCH 0512/4053] Comment that hackery --- .vsts.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index 44118d7b8a..74ac53c0df 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -86,9 +86,8 @@ jobs: $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" $script:ATOM_SCRIPT_PATH = "$script:ATOMROOT\Atom Dev\resources\cli\atom.cmd" - Write-Output "TEMP before: $env:TEMP" + # Normalize %TEMP% as a long (non 8.3) path. $env:TEMP = (Get-Item -LiteralPath $env:TEMP).FullName - Write-Output "TEMP after: $env:TEMP" $env:ELECTRON_NO_ATTACH_CONSOLE = "true" [Environment]::SetEnvironmentVariable("ELECTRON_NO_ATTACH_CONSOLE", "true", "User") From 812635bb233155bc721e97fd6ed72b10587576b2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 23:17:24 -0700 Subject: [PATCH 0513/4053] Skip symlink tests on Windows --- .vsts.yml | 1 + .../controllers/file-patch-controller.test.js | 20 +++++++++++++++++++ test/models/file-patch.test.js | 5 +++++ test/models/repository.test.js | 5 +++++ 4 files changed, 31 insertions(+) diff --git a/.vsts.yml b/.vsts.yml index 74ac53c0df..5535d99ed1 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -106,6 +106,7 @@ jobs: env: TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml FORCE_COLOR: 0 + ATOM_GITHUB_SKIP_SYMLINKS: 1 - task: PublishTestResults@2 inputs: testResultsFormat: JUnit diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index dc2cf9db83..767d738e73 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -341,6 +341,11 @@ describe('FilePatchController', function() { } it('unstages added lines that don\'t require symlink change', async function() { + if (process.env.ATOM_GITHUB_SKIP_SYMLINKS) { + this.skip(); + return; + } + const workingDirPath = await cloneRepository('symlinks'); const repository = await buildRepository(workingDirPath); @@ -379,6 +384,11 @@ describe('FilePatchController', function() { }); it('stages deleted lines that don\'t require symlink change', async function() { + if (process.env.ATOM_GITHUB_SKIP_SYMLINKS) { + this.skip(); + return; + } + const workingDirPath = await cloneRepository('symlinks'); const repository = await buildRepository(workingDirPath); @@ -411,6 +421,11 @@ describe('FilePatchController', function() { }); it('stages symlink change when staging added lines that depend on change', async function() { + if (process.env.ATOM_GITHUB_SKIP_SYMLINKS) { + this.skip(); + return; + } + const workingDirPath = await cloneRepository('symlinks'); const repository = await buildRepository(workingDirPath); @@ -445,6 +460,11 @@ describe('FilePatchController', function() { }); it('unstages symlink change when unstaging deleted lines that depend on change', async function() { + if (process.env.ATOM_GITHUB_SKIP_SYMLINKS) { + this.skip(); + return; + } + const workingDirPath = await cloneRepository('symlinks'); const repository = await buildRepository(workingDirPath); diff --git a/test/models/file-patch.test.js b/test/models/file-patch.test.js index 09998c40c6..6edc3771b0 100644 --- a/test/models/file-patch.test.js +++ b/test/models/file-patch.test.js @@ -357,6 +357,11 @@ describe('FilePatch', function() { }); it('handles typechange patches for a file replaced with a symlink', async function() { + if (process.env.ATOM_GITHUB_SKIP_SYMLINKS) { + this.skip(); + return; + } + const workdirPath = await cloneRepository('symlinks'); const repository = await buildRepository(workdirPath); diff --git a/test/models/repository.test.js b/test/models/repository.test.js index f72833b17e..b5ad183752 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -312,6 +312,11 @@ describe('Repository', function() { }); it('can stage and unstage symlink changes without staging file contents', async function() { + if (process.env.ATOM_GITHUB_SKIP_SYMLINKS) { + this.skip(); + return; + } + const workingDirPath = await cloneRepository('symlinks'); const repo = new Repository(workingDirPath); await repo.getLoadPromise(); From b3338120e898174ab6511430473ae50fd494de99 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 23:31:37 -0700 Subject: [PATCH 0514/4053] Few more symlink tests --- test/controllers/file-patch-controller.test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index 767d738e73..5ae254d570 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -497,6 +497,11 @@ describe('FilePatchController', function() { }); it('stages file deletion when all deleted lines are staged', async function() { + if (process.env.ATOM_GITHUB_SKIP_SYMLINKS) { + this.skip(); + return; + } + const workingDirPath = await cloneRepository('symlinks'); const repository = await buildRepository(workingDirPath); await repository.getLoadPromise(); @@ -528,6 +533,11 @@ describe('FilePatchController', function() { }); it('unstages file creation when all added lines are unstaged', async function() { + if (process.env.ATOM_GITHUB_SKIP_SYMLINKS) { + this.skip(); + return; + } + const workingDirPath = await cloneRepository('symlinks'); const repository = await buildRepository(workingDirPath); From 9a1002e19ee1e97938ccbb8bb11a3a05f2ad5c15 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 10 Oct 2018 23:33:02 -0700 Subject: [PATCH 0515/4053] :arrow_up: timeouts --- .vsts.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.vsts.yml b/.vsts.yml index 5535d99ed1..f0ea1df80d 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -20,6 +20,8 @@ jobs: env: TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml FORCE_COLOR: 0 + MOCHA_TIMEOUT: 60000 + UNTIL_TIMEOUT: 30000 - task: PublishTestResults@2 inputs: testResultsFormat: JUnit @@ -46,6 +48,8 @@ jobs: env: TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml FORCE_COLOR: 0 + MOCHA_TIMEOUT: 60000 + UNTIL_TIMEOUT: 30000 - task: PublishTestResults@2 inputs: testResultsFormat: JUnit @@ -107,6 +111,8 @@ jobs: TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml FORCE_COLOR: 0 ATOM_GITHUB_SKIP_SYMLINKS: 1 + MOCHA_TIMEOUT: 60000 + UNTIL_TIMEOUT: 30000 - task: PublishTestResults@2 inputs: testResultsFormat: JUnit From bbea07d28af0106084dcb1a0526903186b2c1194 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 11 Oct 2018 09:53:34 -0700 Subject: [PATCH 0516/4053] Build Matrix Attempt #1 --- .vsts.yml | 51 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index f0ea1df80d..51038dac77 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -2,20 +2,31 @@ jobs: - job: Linux pool: vmImage: ubuntu-16.04 + strategy: + matrix: + dev: + ATOM_CHANNEL: dev + ATOM_NAME: atom-dev + beta: + ATOM_CHANNEL: beta + ATOM_NAME: atom-beta + stable: + ATOM_CHANNEL: stable + ATOM_NAME: atom variables: display: ":99" steps: - bash: | - curl -s -L "https://atom.io/download/deb?channel=dev" \ + curl -s -L "https://atom.io/download/deb?channel=${ATOM_CHANNEL}" \ -H 'Accept: application/octet-stream' \ -o 'atom-amd64.deb' /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 sudo apt-get update && sudo apt-get install -yyq libgconf-2-4 build-essential git libsecret-1-dev dpkg-deb -x atom-amd64.deb /tmp/atom displayName: install Atom - - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci + - bash: /tmp/atom/usr/share/${ATOM_NAME}/resources/app/apm/bin/apm ci displayName: install dependencies - - bash: /tmp/atom/usr/bin/atom-dev --test test/ + - bash: /tmp/atom/usr/bin/${ATOM_NAME} --test test/ displayName: run tests env: TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml @@ -32,18 +43,29 @@ jobs: - job: MacOS pool: vmImage: macos-10.13 + strategy: + matrix: + dev: + ATOM_CHANNEL: dev + ATOM_APP: Atom Dev.app + beta: + ATOM_CHANNEL: beta + ATOM_APP: Atom Beta.app + stable: + ATOM_CHANNEL: stable + ATOM_APP: Atom.app steps: - bash: | set -x - curl -s -L "https://atom.io/download/mac?channel=dev" \ + curl -s -L "https://atom.io/download/mac?channel=${ATOM_CHANNEL}" \ -H 'Accept: application/octet-stream' \ -o "atom.zip" mkdir -p /tmp/atom unzip -q atom.zip -d /tmp/atom displayName: install Atom - - bash: /tmp/atom/Atom\ Dev.app/Contents/Resources/app/apm/bin/apm ci + - bash: '"/tmp/atom/${ATOM_APP}/Contents/Resources/app/apm/bin/apm" ci' displayName: install dependencies - - bash: /tmp/atom/Atom\ Dev.app/Contents/Resources/app/atom.sh --test test/ + - bash: '"/tmp/atom/${ATOM_APP}/Contents/Resources/app/atom.sh" --test test/' displayName: run tests env: TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml @@ -60,13 +82,24 @@ jobs: - job: Windows pool: vmImage: vs2015-win2012r2 + strategy: + matrix: + dev: + ATOM_CHANNEL: dev + ATOM_DIRECTORY: Atom Dev + beta: + ATOM_CHANNEL: beta + ATOM_DIRECTORY: Atom Beta + stable: + ATOM_CHANNEL: stable + ATOM_DIRECTORY: Atom steps: - powershell: | Set-StrictMode -Version Latest $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" Write-Host "Downloading latest Atom release" - $source = "https://atom.io/download/windows_zip?channel=dev" + $source = "https://atom.io/download/windows_zip?channel=$env:ATOM_CHANNEL" $destination = "atom.zip" (New-Object System.Net.WebClient).DownloadFile($source, $destination) @@ -75,7 +108,7 @@ jobs: - powershell: | Set-StrictMode -Version Latest $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" - $script:APM_SCRIPT_PATH = "$script:ATOMROOT\Atom Dev\resources\app\apm\bin\apm.cmd" + $script:APM_SCRIPT_PATH = "$script:ATOMROOT\$env:ATOM_DIRECTORY\resources\app\apm\bin\apm.cmd" Write-Host "Installing package dependencies" & "$script:APM_SCRIPT_PATH" ci @@ -88,7 +121,7 @@ jobs: - powershell: | Set-StrictMode -Version Latest $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" - $script:ATOM_SCRIPT_PATH = "$script:ATOMROOT\Atom Dev\resources\cli\atom.cmd" + $script:ATOM_SCRIPT_PATH = "$script:ATOMROOT\$env:ATOM_DIRECTORY\resources\cli\atom.cmd" # Normalize %TEMP% as a long (non 8.3) path. $env:TEMP = (Get-Item -LiteralPath $env:TEMP).FullName From f2be8f5222971ed798dd8129cd66bd1e6955f40d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 11 Oct 2018 11:58:04 -0700 Subject: [PATCH 0517/4053] Append ATOM_CHANNEL to JUnit test result suite titles --- .vsts.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.vsts.yml b/.vsts.yml index 51038dac77..fe25b4e8bb 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -37,7 +37,7 @@ jobs: inputs: testResultsFormat: JUnit testResultsFiles: $(Agent.HomeDirectory)/test-results.xml - testRunTitle: Linux + testRunTitle: Linux $(ATOM_CHANNEL) condition: succeededOrFailed() - job: MacOS @@ -76,7 +76,7 @@ jobs: inputs: testResultsFormat: JUnit testResultsFiles: $(Agent.HomeDirectory)/test-results.xml - testRunTitle: MacOS + testRunTitle: MacOS $(ATOM_CHANNEL) condition: succeededOrFailed() - job: Windows @@ -150,7 +150,7 @@ jobs: inputs: testResultsFormat: JUnit testResultsFiles: $(Agent.HomeDirectory)/test-results.xml - testRunTitle: Windows + testRunTitle: Windows $(ATOM_CHANNEL) condition: succeededOrFailed() - job: Lint From 4a63654d6f213c2f004e1f4e38c619f679b8f10a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 11 Oct 2018 13:03:27 -0700 Subject: [PATCH 0518/4053] Stress-test that flaky test --- test/models/file-system-change-observer.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/file-system-change-observer.test.js b/test/models/file-system-change-observer.test.js index eb07bcf1c1..1e4ef1c349 100644 --- a/test/models/file-system-change-observer.test.js +++ b/test/models/file-system-change-observer.test.js @@ -24,7 +24,7 @@ describe('FileSystemChangeObserver', function() { } }); - it('emits an event when a project file is modified, created, or deleted', async function() { + it.stress(50, 'emits an event when a project file is modified, created, or deleted', async function() { const workdirPath = await cloneRepository('three-files'); const repository = await buildRepository(workdirPath); observer = createObserver(repository); From deddaf8a4fc8d4920dc2764213ba043f7d96b9a9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 11 Oct 2018 14:03:55 -0700 Subject: [PATCH 0519/4053] Add this.retries(5) to known flakes --- test/models/file-system-change-observer.test.js | 4 +++- test/worker-manager.test.js | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/models/file-system-change-observer.test.js b/test/models/file-system-change-observer.test.js index 1e4ef1c349..59c7d45d32 100644 --- a/test/models/file-system-change-observer.test.js +++ b/test/models/file-system-change-observer.test.js @@ -24,7 +24,9 @@ describe('FileSystemChangeObserver', function() { } }); - it.stress(50, 'emits an event when a project file is modified, created, or deleted', async function() { + it('emits an event when a project file is modified, created, or deleted', async function() { + this.retries(5); // FLAKE + const workdirPath = await cloneRepository('three-files'); const repository = await buildRepository(workdirPath); observer = createObserver(repository); diff --git a/test/worker-manager.test.js b/test/worker-manager.test.js index 82f5d9d251..3c96eba874 100644 --- a/test/worker-manager.test.js +++ b/test/worker-manager.test.js @@ -149,6 +149,8 @@ describe('WorkerManager', function() { describe('when the manager process is destroyed', function() { it('destroys all the renderer processes that were created', async function() { + this.retries(5); // FLAKE + const browserWindow = new BrowserWindow({show: !!process.env.ATOM_GITHUB_SHOW_RENDERER_WINDOW}); browserWindow.loadURL('about:blank'); sinon.stub(Worker.prototype, 'getWebContentsId').returns(browserWindow.webContents.id); From 83db202703d25498c51989cd4973de1370a5c936 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 12 Oct 2018 09:43:29 -0700 Subject: [PATCH 0520/4053] Reorganize the contributing guide --- CONTRIBUTING.md | 191 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 159 insertions(+), 32 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf0a801ccd..00bab29694 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,16 +1,172 @@ # Contributing to the Atom GitHub Package -For general contributing information, see the [Atom contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md); however, right now, contributing to the GitHub package differs from contributing to other core Atom packages in some ways. +For general contributing information, see the [Atom contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md); however, contributing to the GitHub package differs from contributing to other core Atom packages in some ways. In particular, the GitHub package is under constant development by a portion of the core Atom team, and there is currently a clear vision for short- to medium-term features and enhancements. That doesn't mean we won't merge pull requests or fix other issues, but it *does* mean that you should consider discussing things with us first so that you don't spend time implementing things in a way that differs from the patterns we want to establish or build a feature that we're already working on. -Feel free to [open an issue](https://github.com/atom/github/issues) if you want to discuss anything with us. If you're curious what we're working on and will be working on in the near future, you can take a look at [our short-term roadmap](https://github.com/atom/github/projects/8). +Feel free to [open an issue](https://github.com/atom/github/issues) if you want to discuss anything with us. Depending on the scope and complexity of your proposal we may ask you to reframe it as an [RFC (request for comments)](/docs/how-we-work.md#new-features). If you're curious what we're working on and will be working on in the near future, you can take a look at [our most recent sprint project](https://github.com/atom/github/project) or [accepted RFCs](/docs/rfcs/). + +## Getting started + +If you're working on the GitHub package day-to-day, it's useful to have a development environment configured to use the latest and greatest source. + +1. Run an [Atom nightly build]() if you can. Occasionally, we depend on upstream changes in Atom that have not yet percolated through to stable builds. This will also help us notice any changes in Atom core that cause regressions. It may also be convenient to create shell aliases from `atom` to `atom-nightly` and `apm` to `apm-nightly`. +2. Install the GitHub package from its git URL: + + ```sh + apm-nightly install atom/github + ``` + + When you run Atom in non-dev-mode (`atom-nightly .`) you'll be running the latest _merged_ code in this repository. If this isn't stable enough for day-to-day work, then we have bugs to fix :wink: +3. Link your GitHub package source in dev mode: + + ```sh + # In the root directory of your atom/github clone + apm-nightly link --dev . + ``` + + When you run Atom in dev mode (`atom-nightly -d .`) you'll be running your local changes. This is useful for reproducing bugs or trying out new changes live before merging them. + +### Running tests + +The GitHub package's specs may be run with Atom's graphical test runner or from the command line. + +Launch the graphical test runner by executing `Window: Run Package Specs` from the command palette. Once a test window is visible, tests may be re-run by refreshing it with cmd-R. Toggle the developer tools within the test runner window with cmd-shift-I to see syntax errors, warnings, or the output from `console.log` debug statements. + +To run tests from the command line, use: + +```sh +atom-nightly --test test/ +``` + +If this process exits with no output and a nonzero exit status, try: + +```sh +atom-nightly --enable-electron-logging --test test/ +``` + +#### Flakes + +Occasionally, a test unrelated to your changes may fail sporadically. We file issues for these with the ["flaky-test" label]() and add a retry statement: + +```js +it('passes sometimes and fails sometimes', function() { + this.retries(5); // FLAKE + + // .. +}) +``` + +If that isn't enough to pass the suite reliably -- for example, if a failure manipulates some global state to cause it to fail again on the retries -- skip the test until we can investigate further: + +```js +// FLAKE +it.skip('breaks everything horribly when it fails', function() { + // .. +}); +``` + +If you wish to help make these more reliable (for which we would be eternally grateful! :pray:) we have a helper that focuses and re-runs a single `it` or `describe` block many times: + +```js +it.stress(100, 'seems to break sometimes', function() { + // +}); +``` + +### Style and formatting + +We enforce style consistency with eslint and the [fbjs-opensource]() ruleset. Our CI will automatically verify that pull requests conform to the existing ruleset. If you wish to check your changes against our rules before you submit a pull request, run: + +```sh +npm run lint +``` + +It's often more convenient to have Atom automatically lint and correct your source as you edit. To set this up, you'll need to install a frontend and a backend linter packages. I use [linter-eslint]() as a backend and [atom-ide-ui]() as a frontend. + +```sh +apm-nightly install atom-ide-ui linter-eslint +``` + +### Coverage + +Code coverage by our specs is measured by [istanbul]() and reported to [Coveralls](). Links to coverage information will be available in a pull request comment and a status check. While we don't _enforce_ full coverage, we do encourage submissions to not regress our coverage percentage whenever feasible. + +If you wish to preview coverage data locally, run one of: + +```sh +# ASCII table output +npm run test:coverage:text + +# HTML document output +npm run test:coverage:html + +# lcov output +npm run test:coverage +``` + +Generating lcov data allows you to integrate an Atom package like [atom-lcov]() to see covered and uncovered source lines and branches with editor annotations. + +If you prefer the graphical test runner, it may be altered to generate lcov coverage data by adding a command like the following to your `init.js` file: + +```js +atom.commands.add('atom-workspace', { + 'me:run-package-specs': () => { + atom.workspace.getElement().runPackageSpecs({ + env: Object.assign({}, process.env, {ATOM_GITHUB_BABEL_ENV: 'coverage'}) + }); + }, +}); +``` + +### Snapshotting + +To accelerate its launch time, Atom constructs a [v8 snapshot]() at build time that may be loaded much more efficiently than parsing source code from scratch. As a bundled core package, the GitHub package is included in this snapshot. A tool called [electron-link]() is used to pre-process all bundled source to prepare it for snapshot generation. This does introduce some constraints on the code constructs that may be used, however. While uncommon, it pays to be aware of the limitations this introduces. + +The most commonly encountered hindrance is that you cannot reference DOM primitives, native modules, or Atom API constructs _at module require time_ - in other words, with a top-level `const` or `let` expression, or a function or the constructor of a class invoked from one: + +```js +import {TextBuffer} from 'atom'; + +// Error: static reference to DOM methods +const node = document.createElement('div') + +// Error: indirect static reference to core Atom API +function makeTextBuffer() { + return new TextBuffer({text: 'oops'}); +} +const theBuffer = newTextBuffer(); + +// Error: static reference to DOM in class definition +class SomeElement extends HTMLElement { + // ... +} +``` + +Introducing new third-party npm package dependencies (as non-`devDependencies`) or upgrading existing ones can also result in snapshot regressions, because authors of general-purpose npm packages, naturally, don't consider this :wink: + +We do have a CI job in our test matrix that verifies that a electron-link and snapshot creation succeed for each commit. + +If any of these situations are _unavoidable_, individual modules _may_ be excluded from the snapshot generation process by adding them to the exclusion lists [within Atom's build scripts]() and [the GitHub package's snapshot testing script](). Use this solution very sparingly, though, as it impacts Atom's startup time and adds confusion. ## Technical contribution tips +### More information + +We have a growing body of documentation about the architecture and organization of our source code in the [`docs/` subdirectory](/docs) of this repository. Check there for detailed technical dives into the layers of our git integration, our React component architecture, and other information. + +We use the following technologies: + +* [Atom API](https://atom.io/docs) to interact with the editor. +* [React]() is the framework that powers our view implementation. +* We interact with GitHub via its [GraphQL]() API. +* [Relay](https://github.com/facebook/relay) is a layer of glue between React and GraphQL queries that handles responsibilities like query composition and caching. +* Our tests are written with [Mocha]() and [Chai](). We also use [Enzyme]() to assert against React behavior. +* We use a [custom Babel 6 transpiler pipeline]() to write modern source with JSX, `import` statements, and other constructs unavailable natively within Atom's Node.js version. + ### Updating the GraphQL Schema -This project uses [Relay](https://github.com/facebook/relay) for its GitHub integration. There's a source-level transform that depends on having a local copy of the GraphQL schema available. If you need to update the local schema to the latest version, run +Relay includes a source-level transform that depends on having a local copy of the GraphQL schema available. If you need to update the local schema to the latest version, run ```bash GITHUB_TOKEN=abcdef0123456789 npm run fetch-schema @@ -22,14 +178,6 @@ Please check in the generated `graphql/schema.graphql`. In addition, if you make any changes to any of the GraphQL queries or fragments (inside the `graphql` tagged template literals), you will need to run `npm run relay` to regenerate the statically-generated query files. -## Testing - -To run tests, open the command palette and select "Run Package Specs". This will open a new window running "GitHub Package Tests". If the window stays blank for more than a few seconds, open DevTools and check for error messages. - -To re-run tests, you can refresh that window by pressing `Cmd + R` in DevTools. - -You can also run them on the command line with `npm run test`. - ### Async Tests Sometimes it's necessary to test async operations. For example, imagine the following test: @@ -66,24 +214,3 @@ await assert.async.equal(value, 1) This transpiles into a form similar to the one above, so is asynchronous, but if the test fails, we'll still see a message that contains 'expected 0 to equal 1'. When writing tests that depend on values that get set asynchronously, prefer `assert.async.x(...)` over other forms. - -## Living on the edge - -If you're working on the GitHub package day-to-day, it's useful to have a development environment configured to use the latest and greatest source. - -1. [Build Atom from master](https://github.com/atom/atom/tree/master/docs/build-instructions) frequently if you can. This will help us notice any changes in Atom core that cause regressions. -2. Install the GitHub package from its git URL: - - ```sh - $ apm install atom/github - ``` - - When you run Atom in non-dev-mode (`atom .`) you'll be running the latest _merged_ code in this repository. If this isn't stable enough for day-to-day work, then we have bugs to fix :wink: -3. Link your GitHub package source in dev mode: - - ```sh - # In the root directory of your atom/github clone - $ apm link --dev . - ``` - - When you run Atom in dev mode (`atom -d .`) you'll be running your local changes. This is useful for reproducing bugs or trying out new changes live before merging them. From 9eeb9760fc638eaeab009bdcac7299b8418f249a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 12 Oct 2018 10:52:13 -0700 Subject: [PATCH 0521/4053] Process ideas that @kuychaco and @annthurium and I brainstormed --- docs/core-team-process.md | 79 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 docs/core-team-process.md diff --git a/docs/core-team-process.md b/docs/core-team-process.md new file mode 100644 index 0000000000..a2cda3c437 --- /dev/null +++ b/docs/core-team-process.md @@ -0,0 +1,79 @@ +# Core @atom/github team process + +This guide describes the way that the core @atom/github team works together day-to-day. + +We value: + +* **Trust** in each other's judgement and instincts. +* Feeling **included** and present among the team. +* Respect for **differing individual preferences** in social needs and tolerance for practices like pair programming. +* Acknowledgement that **we are distributed geographically** and the differences in timezone and daily schedules that that implies. +* **Continuous improvement** to find what works best for the team we are today and for the immediate problem at hand, and to adjust as both of these change fluidly. + +## Organization + +When we plan, we choose to pursue _a single task_ as a single team, rather than distributing tasks among ourselves from a queue and working on independent tasks in parallel. This is intended to increase the amount and quality of communication we can share in chat and in synchronous meetings: it's much easier to maintain an ongoing technical conversation when all participants share the mental context of a unified goal. + +This does _not_ mean that we all pair program all the time. We do get value from pair programming but this is not always practical or desirable. Pair programming may be chosen independently from the methods below -- functionally, the pair becomes one "developer" in any of the descriptions. + +To implement this, we have several distinct approaches we can employ: + +### 1. Seams + +Divide the issue at hand among the team along the abstraction layers in our codebase. Each developer continuously negotiates the interface with neighboring layers by an active Slack conversation, correcting their direction based on feedback. Developers push their work as commits to a single shared branch, documenting and coordinating overall progress in a shared pull request. + +> Example: developer A implements changes to the model, developer B implements the view component, and developer C implements the controller methods. Developer B writes code as though the model and controller are already complete, using sinon mocks for tests and communicating the view's needs as they arise. Developer C proceeds similarly with the controller methods. Developer A gives feedback on the feasibility of requested model functionality from both A and B and negotiates method names and property names. When developer C leaves for the day or takes time off, developers A and B proceed, leaving asynchronous notes for developer C as pull request comments for them to catch up on when they come back online. + +:+1: _Advantages:_ + +* Encourages high-touch, continuous conversation involving and relevant to the full team. +* Resilient to time off and asynchronicity. +* Minimizes the need to context switch up and down abstraction layers while working. + +:-1: _Disadvantages:_ + +* Diminishes variety of work done by any individual developer, which could become boring. +* Reduces the familiarity developed by any single developer to a single abstraction layer within the codebase. +* Timing may become difficult. It's possible that one "seam" may take much more time to implement than the others, which could lead to a bottleneck. +* Some efforts will not be decomposable into easily identified seams for division of labor. + +### 2. Pull request hierarchy + +The problem at hand is decomposed into a queue of relatively independent tasks to complete. A primary branch and a pull request are created to accumulate the full, shippable solution on full completion. Each developer creates an individual branch from the primary one and pushes commits as they work, opening a pull request that targets the primary branch as a base. Developers review one another's sub-pull requests with pull request reviews and coordinate merges to the primary until all tasks are complete, at which point the primary pull request is merged. + +> Example: developers A and B create and push a parent branch `a-b/big-feature` and open pull request 123 with an overall problem definition and a checklist of tasks to complete. Developer A creates branch `a/user-story-a` from `a-b/big-feature` and opens pull request 444 while developer B works on branch `b/user-story-b` and pull request 555. Developer A reviews and merges pull request 555 while developer B moves on to branch `b/user-story-c`, then developer B reviews and merges pull request 444. Developers A and B continuously calibrate the task list to represent the remaining work. Once the task list is complete, the primary pull request 123 is merged and the feature is shipped. + +:+1: _Advantages:_ + +* Makes it less likely that one developer may block the others when their tasks take longer than expected. +* More asynchronous-friendly. +* Leaves a trail of documentation for each task. + +:-1: _Disadvantages:_ + +* Decomposing tasks well is challenging. +* Less communication-friendly; we risk a developer on a long-running task feeling isolated. + +### 3. Hand-offs + +In this method, each developer (or pair) tackles a single problem in serial during their working hours. When the next developer becomes available, the previous one writes a summary of their efforts and progress in a hand-off, either synchronously and interactively in Slack or asynchronously with a pull request comment. Once the next developer is caught up, they make progress and hand off to the next, and so on. + +> Example: developer A logs in during their morning and works for a few hours on the next phase of a feature implementation. They make some progress on the model, but don't progress the controller beyond some stubs and don't get a chance to touch the view at all. When developer B logs in, developer A shares their progress with a conversation in Slack or Zoom until developer B is confident that they understand the problem's current state, at which point developer B begins working and making commits to the feature branch. Developer B implements the view, correcting and adding some methods to the model as needed. Finally, developer C logs in, and developers A and C pair to write the controller methods. They write a comment on the pull request as they wrap up describing the changes that they've made together. Developer B returns the next day, puts the finishing touches on the tests, writes or refines some documentation on the new code, and merges the pull request. + +:+1: _Advantages:_ + +* Maximizes knowledge transfer among participants: everyone gets a chance to work on and become familiar with all the system's layers. +* Ensures that nobody needs to wait when somebody else is stuck. +* Handles differences in timezones gracefully. + +:-1: _Disadvantages:_ + +* Overlap times need to be negotiated, either by pair programming or using another method to divvy up work. If we all overlap significantly it functionally decays to one of the other solutions. +* Hand-offs are high communication touchpoints, but the rest of the time is more isolated. + +## Ambient socialization + +In addition to these strategies, we can take advantage of other technologies to help us feel connected in an ambient way. + +* We all open [Teletype]() portals as we work, even when not actively pairing, and share the URL in Slack. We join each other's portals in a window on a separate Atom window and watch each other's progress as a background process. +* We stream to the world on [Twitch](https://twitch.tv) as we work. We sometimes jump into each other's streams to chat or catch up. From 498234da945955a8fcc21f4d4c2ab8a5d69d38a3 Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 15 Oct 2018 11:16:02 +0900 Subject: [PATCH 0522/4053] Replace "reviews collapsed" mockup --- docs/rfcs/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index 63b2c112f0..8c8df3d680 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -76,7 +76,7 @@ Under the "Reviews" tab all reviews of a pull request get shown. This is akin to Comments can be collapsed to get a better overview. -![reviews collapsed](https://user-images.githubusercontent.com/378023/46535648-13345f00-c8e7-11e8-8912-ab8acf144e02.png) +![reviews collapsed](https://user-images.githubusercontent.com/378023/46926357-62a72780-d06b-11e8-9344-23389d1c727c.png) --- From 2fd9a9e2b8093e4d114c7c6913eb38b4f3293e3e Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 15 Oct 2018 12:10:52 +0900 Subject: [PATCH 0523/4053] Add "Create a new Review" section --- docs/rfcs/003-pull-request-review.md | 52 ++++++++++++++++------------ 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index 8c8df3d680..8e24e684d7 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -79,24 +79,47 @@ Comments can be collapsed to get a better overview. ![reviews collapsed](https://user-images.githubusercontent.com/378023/46926357-62a72780-d06b-11e8-9344-23389d1c727c.png) ---- +### Create a new Review -:point_down: TODO +Hovering along the gutter within a pull request diff region in a `TextEditor` or a `PullRequestDetailItem` reveals a `+` icon. Clicking the `+` icon reveals a new comment box, which may be used to submit a single comment or start a multi-comment review: + +![new review](https://user-images.githubusercontent.com/378023/46926996-49ec4100-d06e-11e8-9fb7-86607861efdd.png) -#### Submitting a Review +* Clicking "Add single comment" submits a diff comment and does not create a draft review. +* Clicking "Start a review" creates a draft review and attaches the authored comment to it. -review changes button +![pending review](https://user-images.githubusercontent.com/378023/46927357-e06d3200-d06f-11e8-9eae-b4c289fe16ae.png) -Clicking the "Review Changes" button reveals a UI much like dotcom's: +* If a draft review is already in progress, the "Start a review" button reads "Add review comment". +* And an additional row is added with options to "Start a new conversation" or "Finish your review". -review changes panel +#### Submit a new Review + +Clicking "Finish your review" or "Review Changes" in the footer... + +![reviews panel](https://user-images.githubusercontent.com/378023/46536010-17ad4780-c8e8-11e8-8338-338bb592efc5.png) -> :construction: Update the Review Changes mockup +... expands the footer to: + +![submit review](https://user-images.githubusercontent.com/378023/46927736-ef54e400-d071-11e8-99d9-0ea1001fc50d.png) * The review summary is a TextEditor that may be used to compose a summary comment. +* Files with peding review comments are listed and make it possible to navigate between them. +* A review can be marked as "Comment", "Approve" or "Recommend changes" (.com's "Request changes"). * Choosing "Cancel" dismisses the review and any comments made. If there are local review comments that will be lost, a confirmation prompt is shown first. * Choosing "Submit review" submits the drafted review to GitHub. +![resolve a review](https://user-images.githubusercontent.com/378023/46927875-c08b3d80-d072-11e8-978b-024111312d79.png) + +* Review comments can be resolved by clicking on the "Resolve conversation" buttons. If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. + + +--- + +:point_down: TODO + +--- + #### Summary Box At the top of the pane is the existing summary box: @@ -152,21 +175,6 @@ Within the multi-file diff view or a TextEditor, a block decoration is used to s * For comment decorations within a `TextEditor`, clicking the "diff" button opens the corresponding `PullRequestDetailItem` and scrolls to focus the equivalent comment. * Reaction emoji may be added to each comment with the "emoji" button. Existing emoji reaction tallies are included beneath each comment. -### Line comment creation - -Hovering along the gutter within a pull request diff region in a `TextEditor` or a `PullRequestDetailItem` reveals a `+` icon, which may be clicked to begin a new review: - -![plus-icon](https://user-images.githubusercontent.com/378023/40348708-6698b2ea-5ddf-11e8-8eaa-9d95bc483fb1.png) - -Clicking the `+` reveals a new comment box, which may be used to submit a single comment or begin a multi-comment review: - -![single-review](https://user-images.githubusercontent.com/378023/40351475-78a527c2-5de7-11e8-8006-72d859514ecc.png) - -* If a draft review is already in progress, the "Add single comment" button is disabled and the "Start a review" button reads "Add review comment". -* Clicking "Add single comment" submits a non-review diff comment and does not create a draft review. This button is disabled unless the "reply" editor is expanded and has non-whitespace content. -* Clicking "Start a review" creates a draft review and attaches the authored comment to it. This button is disabled unless the "reply" editor is expanded and has non-whitespace content. -* Clicking "mark as resolved" marks the comment as resolved with on GitHub. If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. - ## Drawbacks This adds a substantial amount of complexity to the UI, which is only justified for users that use GitHub pull request reviews. From add388e748aa9e1b2feb7d979effa3d778d40f5f Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 15 Oct 2018 12:20:56 +0900 Subject: [PATCH 0524/4053] :fire: Remove "Summary box" --- docs/rfcs/003-pull-request-review.md | 31 ---------------------------- 1 file changed, 31 deletions(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index 8e24e684d7..505625bd21 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -120,37 +120,6 @@ Clicking "Finish your review" or "Review Changes" in the footer... --- -#### Summary Box - -At the top of the pane is the existing summary box: - -pull request details pane summary box - -> :construction: Add conversation/timeline icon and progress bar - -Clicking on the "22 commits" opens the commit view and changes the bottom panel to indicate sort by commits. - -Clicking on the "1 changed files" opens the files view and changes the bottom panel to indicate sort by files and "all files" checked. - -Clicking on the build status summary icon (green checkmark, donut chart, or X) expands an ephemeral panel beneath the summary box showing build review status. Clicking the icon again or clicking on "dismiss" dismisses it. - -emphemeral checks panel - -Clicking on the conversation/timeline icon expands an ephemeral panel beneath the summary box showing a very timeline view. The PR description and PR comments are displayed here. Other note-worthy timeline events are displayed in a very minimal fashion. At the bottom is an input field to add a new PR comment. - -> :construction: Add conversation/timeline popover mockup - -Clicking the "expand" icon on the top right opens this information in a new pane to the right for easy side-by-side viewing with the diff (much like our current markdown preview opens in a separate pane). - -> :construction: Add conversation/timeline pane item - -Clicking on the a commit takes you to the commit view and expands the selected commit, centering it in view. - -> :construction: Add commit view mockup - -Clicking on a review reference takes you to the review view and expands the selected review, centering it in view. - -> :construction: Add review mockup ### In-editor decorations From 979ced51c47086e6b9f5ed316abecf9045d83f15 Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 15 Oct 2018 12:39:58 +0900 Subject: [PATCH 0525/4053] Add "Single file diff" section --- docs/rfcs/003-pull-request-review.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index 505625bd21..df52317c31 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -114,24 +114,22 @@ Clicking "Finish your review" or "Review Changes" in the footer... * Review comments can be resolved by clicking on the "Resolve conversation" buttons. If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. ---- +### Single file diff -:point_down: TODO +Clicking on the `<>` icon in a review comment switches from the multi-file diff to the entire file with diffs. If possible, the scrollposition is retained. This allows to quickly get more context about the code. ---- +![single file diff](https://user-images.githubusercontent.com/378023/46928308-e9accd80-d074-11e8-8de3-a16140e74907.png) +Clicking the `file+-` icon, switches back to the multi-file diff. -### In-editor decorations +** :question: Open question:** Should there be a way to switch to the file without a diff? Should the inline comment still be shown only an icon or nothing? -When opening a TextEditor on a file that has been annotated with review comments on the current pull request, a block decoration is used to show the comment content at the corresponding position within the file content. Also, a gutter decoration is used to reveal lines that are included within the current pull requests' diff and may therefore include comments. -![in-editor review comment decoration](https://user-images.githubusercontent.com/378023/44790482-69bcc800-abda-11e8-8a0f-922c0942b8c6.png) +--- -> :construction: Add gutter decoration? +:point_down: TODO -* The comment's position is calculated from the position acquired by the GitHub API response, modified based on the git diff of that file (following renames) between the owning review's attached commit and the current state of the working copy (including any local modifications). Once created, the associated marker will also track unsaved modifications to the file in real time. -* The up and down arrow buttons navigate to the next and previous review comments within this review within their respective TextEditors. -* The "diff" button navigates to the corresponding pull request's detail item and scrolls to center the same comment within that view. +--- ### Comment decorations From b2d0353865500ff0a8cf8c350eebae22b81ed47b Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 15 Oct 2018 16:46:35 +0900 Subject: [PATCH 0526/4053] More edits --- docs/rfcs/003-pull-request-review.md | 128 +++++++++++++-------------- 1 file changed, 62 insertions(+), 66 deletions(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index df52317c31..093c448a65 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -58,28 +58,42 @@ Overview | Commits | Build Status ### Files -Under the "Files" tab the full, multi-file diff associated with the pull request is displayed. This is akin to the "Files changed" tab on dotcom. It displays the diff for all changed files in the PR. +Under the "Files" tab the full, multi-file diff associated with the pull request is displayed. This is akin to the "Files changed" tab on dotcom. ![files](https://user-images.githubusercontent.com/378023/46536560-d3bb4200-c8e9-11e8-9764-dca0b84245cf.png) -Review comments are shown within the diff. See ["Comment decorations"](#comment-decorations) for description of review comments. +* Diffs are editable. +* Editing the diff is _only_ possible if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. -![review comment](https://user-images.githubusercontent.com/378023/46534569-8fc53e80-c8e3-11e8-8721-b38462b51cc7.png) +Uncollapsed (default) | Collapsed +--- | --- +![files](https://user-images.githubusercontent.com/378023/46536560-d3bb4200-c8e9-11e8-9764-dca0b84245cf.png) | ![collapsed files](https://user-images.githubusercontent.com/378023/46931273-7069a680-d085-11e8-9ea7-c96a1772fe27.png) + +* For large diffs, the files can be collapsed to get a better overview. -Diffs are editable _only_ if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. ### Reviews -Under the "Reviews" tab all reviews of a pull request get shown. This is akin to the review summaries that appear on the "Conversation" tab on dotcom. The comments are displayed grouped by review along with some context lines. +The "Reviews" tab shows all reviews. This is akin to the review summaries that appear on the "Conversation" tab on dotcom. In addition, each review also includes review comments and their diff. ![reviews](https://user-images.githubusercontent.com/378023/46535563-c81a4c00-c8e6-11e8-9c0b-6ea575556101.png) -Comments can be collapsed to get a better overview. +Uncollapsed (default) | Collapsed +--- | --- +![reviews](https://user-images.githubusercontent.com/378023/46535563-c81a4c00-c8e6-11e8-9c0b-6ea575556101.png) | ![collapsed reviews](https://user-images.githubusercontent.com/378023/46926357-62a72780-d06b-11e8-9344-23389d1c727c.png) + +* Comments can be collapsed to get a better overview. +* Reviews get sorted by: + 1. "recommended" changes + 2. "commented" changes + 3. "no review" (when a reviewer only leaves review comments, but no summary) + 4. "approved" changes + 5. "previous" reviews (when a reviewer made an earlier review and it's now out-dated) -![reviews collapsed](https://user-images.githubusercontent.com/378023/46926357-62a72780-d06b-11e8-9344-23389d1c727c.png) +#### Create a new review -### Create a new Review +##### `+` Button Hovering along the gutter within a pull request diff region in a `TextEditor` or a `PullRequestDetailItem` reveals a `+` icon. Clicking the `+` icon reveals a new comment box, which may be used to submit a single comment or start a multi-comment review: @@ -88,14 +102,16 @@ Hovering along the gutter within a pull request diff region in a `TextEditor` or * Clicking "Add single comment" submits a diff comment and does not create a draft review. * Clicking "Start a review" creates a draft review and attaches the authored comment to it. +##### Pending comments + ![pending review](https://user-images.githubusercontent.com/378023/46927357-e06d3200-d06f-11e8-9eae-b4c289fe16ae.png) * If a draft review is already in progress, the "Start a review" button reads "Add review comment". * And an additional row is added with options to "Start a new conversation" or "Finish your review". -#### Submit a new Review +##### Submit a review -Clicking "Finish your review" or "Review Changes" in the footer... +Clicking "Finish your review" from a comment or clicking "Review Changes" in the footer... ![reviews panel](https://user-images.githubusercontent.com/378023/46536010-17ad4780-c8e8-11e8-8338-338bb592efc5.png) @@ -109,44 +125,37 @@ Clicking "Finish your review" or "Review Changes" in the footer... * Choosing "Cancel" dismisses the review and any comments made. If there are local review comments that will be lost, a confirmation prompt is shown first. * Choosing "Submit review" submits the drafted review to GitHub. +##### Resolve a comment + ![resolve a review](https://user-images.githubusercontent.com/378023/46927875-c08b3d80-d072-11e8-978b-024111312d79.png) * Review comments can be resolved by clicking on the "Resolve conversation" buttons. If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. -### Single file diff - -Clicking on the `<>` icon in a review comment switches from the multi-file diff to the entire file with diffs. If possible, the scrollposition is retained. This allows to quickly get more context about the code. - -![single file diff](https://user-images.githubusercontent.com/378023/46928308-e9accd80-d074-11e8-8de3-a16140e74907.png) - -Clicking the `file+-` icon, switches back to the multi-file diff. +#### Context and navigation -** :question: Open question:** Should there be a way to switch to the file without a diff? Should the inline comment still be shown only an icon or nothing? +Review comments are shown in 3 different places. The comments themeselves have the same functionality, but allow the comment to be seen in a different context, depending on different use cases. For example "reviewing a pull request", "addressing feedback", "editing the entire file". +Files | Reviews | Single file +--- | --- | --- +![files](https://user-images.githubusercontent.com/378023/46932382-6bf3bc80-d08a-11e8-83ce-af2ec99c3610.png) | ![reviews](https://user-images.githubusercontent.com/378023/46535563-c81a4c00-c8e6-11e8-9c0b-6ea575556101.png) | ![single file](https://user-images.githubusercontent.com/378023/46928308-e9accd80-d074-11e8-8de3-a16140e74907.png) ---- - -:point_down: TODO - ---- - -### Comment decorations +In order to navigate between comments or switch context, each comment has the following controlls: -Within the multi-file diff view or a TextEditor, a block decoration is used to show the comment content at the corresponding position within the file content. +![image](https://user-images.githubusercontent.com/378023/46934191-c6444b80-d091-11e8-9405-b93bd2aecc90.png) -* The comment's position is calculated from the position acquired by the GitHub API response, modified based on the git diff of that file (following renames) between the owning review's attached commit and the current state of the working copy (including any local modifications). Once created, the associated marker will also track unsaved modifications to the file in real time. -* The up and down arrow buttons navigate to the next and previous review comments. -* For comment decorations in the `PullRequestDetailItem`, clicking the "code" (`<>`) button opens the corresponding file in a TextEditor and scrolls to the review comment decoration there. - * If the current pull request is not checked out, the "code" button is disabled, and a tooltip prompts the user to check out the pull request to edit the source. -* For comment decorations within a `TextEditor`, clicking the "diff" button opens the corresponding `PullRequestDetailItem` and scrolls to focus the equivalent comment. +* Clicking on the `<>` button in a review comment shows the comment in the entire file. If possible, the scroll-position is retained. This allows to quickly get more context about the code. + * If the current pull request is not checked out, the `<>` button is disabled, and a tooltip prompts the user to check out the pull request to edit the source. +* Clicking on the "sandwich" button shows the comment under the "Reviews" tab. +* Clicking on the "file-+" button (not shown in above screenshot) shows the comment under the "Files" tab. +* The up and down arrow buttons navigate to the next and previous unresolved review comments. * Reaction emoji may be added to each comment with the "emoji" button. Existing emoji reaction tallies are included beneath each comment. + ## Drawbacks This adds a substantial amount of complexity to the UI, which is only justified for users that use GitHub pull request reviews. -Rendering pull request comments within TextEditors can be intrusive: if there are many, or if your reviewers are particularly verbose, they could easily crowd out the code that you're trying to write and obscure your context. ## Rationale and alternatives @@ -162,46 +171,33 @@ We discussed displaying review summary information in the GitHub panel in a ["Cu ### Questions I expect to address before this is merged -Can we access "draft" reviews from the GitHub API, to unify them between Atom and GitHub? - -* _Yes, the `reviews` object includes it in a `PENDING` state._ - -How do we represent the resolution of a comment thread? Where can we reveal this progress through each review, and of all required reviews? - -* _We'll show a progress bar on a sticky header at the top of the `PullRequestDetailItem`._ - -Are there any design choices we can make to lessen the emotional weight of a "requests changes" review? Peer review has the most value when it discovers issues for the pull request author to address, but accepting criticism is a vulnerable moment. - -* _Choosing phrasing and iconography carefully for "recommend changes"._ - -Similarly, are there any ways we can encourage empathy within the review authoring process? Can we encourage reviewers to make positive comments or demonstrate humility and open-mindedness? - -* _Emoji reactions on comments :cake: :tada:_ -* _Enable integration with Teletype for smoother jumping to a synchronous review_ +* Can we access "draft" reviews from the GitHub API, to unify them between Atom and GitHub? + * _Yes, the `reviews` object includes it in a `PENDING` state._ +* How do we represent the resolution of a comment thread? Where can we reveal this progress through each review, and of all required reviews? + * _We'll show a progress bar on a sticky header at the top of the `PullRequestDetailItem`._ +* Are there any design choices we can make to lessen the emotional weight of a "requests changes" review? Peer review has the most value when it discovers issues for the pull request author to address, but accepting criticism is a vulnerable moment. + * _Choosing phrasing and iconography carefully for "recommend changes"._ +* Similarly, are there any ways we can encourage empathy within the review authoring process? Can we encourage reviewers to make positive comments or demonstrate humility and open-mindedness? + * _Emoji reactions on comments :cake: :tada:_ + * _Enable integration with Teletype for smoother jumping to a synchronous review_ ### Questions I expect to resolve throughout the implementation process -When there are working directory changes or local commits on the PR branch, how do we clearly indicate them within the diff view? Do we need to make them visually distinct from the PR changes? Things might get confusing for the user when the diff in the editor gets out of sync with the diff on dotcom. For example: a pull request author reads a comment pointing out a typo in an added line. The author edits text within the multi-file diff which modifies the working directory. Should this line now be styled differently to indicate that it has deviated from the original diff? - -Review comment positioning within live TextEditors will be a tricky problem to address satisfactorily. What are the edge cases we need to handle there? - -* _Review comments on deleted lines._ -* _Review comments on deleted files._ - -The GraphQL API paths we need to interact with all involve multiple levels of pagination: pull requests, pull request reviews, review comments. How do we handle these within Relay? Or do we interact directly with GraphQL requests? - -How do we handle comment threads? - -When editing diffs: - -* Do we edit the underlying buffer or file directly, or do we mark the `PullRequestDetailItem` as "modified" and require a "save" action to persist changes? -* Do we disallow edits of removed lines, or do we re-introduce the removed line as an addition on modification? +* When there are working directory changes or local commits on the PR branch, how do we clearly indicate them within the diff view? Do we need to make them visually distinct from the PR changes? Things might get confusing for the user when the diff in the editor gets out of sync with the diff on dotcom. For example: a pull request author reads a comment pointing out a typo in an added line. The author edits text within the multi-file diff which modifies the working directory. Should this line now be styled differently to indicate that it has deviated from the original diff? +* Review comment positioning within live TextEditors will be a tricky problem to address satisfactorily. What are the edge cases we need to handle there? + * _Review comments on deleted lines._ + * _Review comments on deleted files._ +* The GraphQL API paths we need to interact with all involve multiple levels of pagination: pull requests, pull request reviews, review comments. How do we handle these within Relay? Or do we interact directly with GraphQL requests? +* How do we handle comment threads? +* When editing diffs: + * Do we edit the underlying buffer or file directly, or do we mark the `PullRequestDetailItem` as "modified" and require a "save" action to persist changes? + * Do we disallow edits of removed lines, or do we re-introduce the removed line as an addition on modification? +* When clicking on the `<>` button, should there be a way to turn of the diff? Or when opening the same file from the tree-view, should we show review comments? Or only an icon in the gutter? ### Questions I consider out of scope of this RFC -What other pull request information can we add to the GitHub pane item? - -How can we notify users when new information, including reviews, is available, preferably without being intrusive or disruptive? +* What other pull request information can we add to the GitHub pane item? +* How can we notify users when new information, including reviews, is available, preferably without being intrusive or disruptive? ## Implementation phases From 4577b03ee1b7f394034af7fa1ef3cd2e3602ca8a Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 15 Oct 2018 17:19:05 +0900 Subject: [PATCH 0527/4053] Add alternative --- docs/rfcs/003-pull-request-review.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index 093c448a65..d20e6cc6ef 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -159,6 +159,8 @@ This adds a substantial amount of complexity to the UI, which is only justified ## Rationale and alternatives +#### First iteration + Our original design looked and felt very dotcom-esque: ![changes-tab](https://user-images.githubusercontent.com/378023/46287431-6e9bdf80-c5bd-11e8-99eb-f3f81ba64e81.png) @@ -167,6 +169,19 @@ We decided to switch to an editor-first approach and build the code review exper We discussed displaying review summary information in the GitHub panel in a ["Current pull request tile"](https://github.com/atom/github/blob/2ab74b59873c3b5bccac7ef679795eb483b335cf/docs/rfcs/XXX-pull-request-review.md#current-pull-request-tile). The current design encapsulates all of the PR information and functionality within a `PullRequestDetailItem`. Keeping the GitHub panel free of PR details for a specific PR rids us of the problem of having to keep it updated when the user switches active repos (which can feel jarring). This also avoids confusing the user by showing PR details for different PRs (imagine the checked out PR info in the panel and a pane item with PR info for a separate repo). We also free up space in the GitHub panel, making it less busy/overwhelming and leaving room for other information we might want to provide there in the future (like associated issues, say). +#### Second iteration + +Our 2nd iteration made the changes of a PR be the main focus when opening a `PullRequestDetailItem`. + +![filter](https://user-images.githubusercontent.com/7910250/46391711-1df6b600-c693-11e8-87f3-ad4cdbe8ebd8.png) + +It was a great improvement, but filtering the diff with radio buttons and checkboxes felt confusing and overwhelming. Our next iteration then had the following goals: + +- Bring back the sub-navigation, but make it look less .com-y. +- Keep using an editable editor for the diffs, but add some padding. +- Introduce a "Reviews" footer to all sub-views to allow creating/submit a review, no matter where you are. + + ## Unresolved questions ### Questions I expect to address before this is merged From bed10a44993a2b79274b9e077374788405343239 Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 15 Oct 2018 17:28:27 +0900 Subject: [PATCH 0528/4053] Fix typos --- docs/rfcs/003-pull-request-review.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index d20e6cc6ef..c614f552bb 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -18,7 +18,7 @@ Peer review is also a critical part of the path to acceptance for pull requests ### Review information in Pull Request list -Review progress is indicated for open pull requests listed in the GitHub panel. The pull request corresponding to the checked out branch gets special treatment in it's own section at the top of the list. +Review progress is indicated for open pull requests listed in the GitHub panel. The pull request corresponding to the checked out branch gets special treatment in its own section at the top of the list. ![image](https://user-images.githubusercontent.com/378023/46524357-89bf6580-c8c3-11e8-8e2d-ea02d5a1f278.png) @@ -49,7 +49,7 @@ A panel at the bottom of the pane shows the progress for resolved review comment ![reviews panel](https://user-images.githubusercontent.com/378023/46536010-17ad4780-c8e8-11e8-8338-338bb592efc5.png) -This panel is persistent throught all sub-views. It allows creating a reviews no matter where you are. Below shown with the existing sub-views: +This panel is persistent throughout all sub-views. It allows creating a reviews no matter where you are. Below shown with the existing sub-views: Overview | Commits | Build Status --- | --- | --- @@ -120,7 +120,7 @@ Clicking "Finish your review" from a comment or clicking "Review Changes" in the ![submit review](https://user-images.githubusercontent.com/378023/46927736-ef54e400-d071-11e8-99d9-0ea1001fc50d.png) * The review summary is a TextEditor that may be used to compose a summary comment. -* Files with peding review comments are listed and make it possible to navigate between them. +* Files with pending review comments are listed and make it possible to navigate between them. * A review can be marked as "Comment", "Approve" or "Recommend changes" (.com's "Request changes"). * Choosing "Cancel" dismisses the review and any comments made. If there are local review comments that will be lost, a confirmation prompt is shown first. * Choosing "Submit review" submits the drafted review to GitHub. @@ -134,13 +134,13 @@ Clicking "Finish your review" from a comment or clicking "Review Changes" in the #### Context and navigation -Review comments are shown in 3 different places. The comments themeselves have the same functionality, but allow the comment to be seen in a different context, depending on different use cases. For example "reviewing a pull request", "addressing feedback", "editing the entire file". +Review comments are shown in 3 different places. The comments themselves have the same functionality, but allow the comment to be seen in a different context, depending on different use cases. For example "reviewing a pull request", "addressing feedback", "editing the entire file". Files | Reviews | Single file --- | --- | --- ![files](https://user-images.githubusercontent.com/378023/46932382-6bf3bc80-d08a-11e8-83ce-af2ec99c3610.png) | ![reviews](https://user-images.githubusercontent.com/378023/46535563-c81a4c00-c8e6-11e8-9c0b-6ea575556101.png) | ![single file](https://user-images.githubusercontent.com/378023/46928308-e9accd80-d074-11e8-8de3-a16140e74907.png) -In order to navigate between comments or switch context, each comment has the following controlls: +In order to navigate between comments or switch context, each comment has the following controls: ![image](https://user-images.githubusercontent.com/378023/46934191-c6444b80-d091-11e8-9405-b93bd2aecc90.png) @@ -189,7 +189,7 @@ It was a great improvement, but filtering the diff with radio buttons and checkb * Can we access "draft" reviews from the GitHub API, to unify them between Atom and GitHub? * _Yes, the `reviews` object includes it in a `PENDING` state._ * How do we represent the resolution of a comment thread? Where can we reveal this progress through each review, and of all required reviews? - * _We'll show a progress bar on a sticky header at the top of the `PullRequestDetailItem`._ + * _We'll show a progress bar in the footer of the `PullRequestDetailItem`._ * Are there any design choices we can make to lessen the emotional weight of a "requests changes" review? Peer review has the most value when it discovers issues for the pull request author to address, but accepting criticism is a vulnerable moment. * _Choosing phrasing and iconography carefully for "recommend changes"._ * Similarly, are there any ways we can encourage empathy within the review authoring process? Can we encourage reviewers to make positive comments or demonstrate humility and open-mindedness? From 6a71850041b0ed6138918e527493e52766b04fb7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 15 Oct 2018 11:54:20 -0400 Subject: [PATCH 0529/4053] Links :link: --- CONTRIBUTING.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00bab29694..9d071b7764 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Feel free to [open an issue](https://github.com/atom/github/issues) if you want If you're working on the GitHub package day-to-day, it's useful to have a development environment configured to use the latest and greatest source. -1. Run an [Atom nightly build]() if you can. Occasionally, we depend on upstream changes in Atom that have not yet percolated through to stable builds. This will also help us notice any changes in Atom core that cause regressions. It may also be convenient to create shell aliases from `atom` to `atom-nightly` and `apm` to `apm-nightly`. +1. Run an [Atom nightly build](https://github.com/atom/atom-nightly-releases) if you can. Occasionally, we depend on upstream changes in Atom that have not yet percolated through to stable builds. This will also help us notice any changes in Atom core that cause regressions. It may also be convenient to create shell aliases from `atom` to `atom-nightly` and `apm` to `apm-nightly`. 2. Install the GitHub package from its git URL: ```sh @@ -47,7 +47,7 @@ atom-nightly --enable-electron-logging --test test/ #### Flakes -Occasionally, a test unrelated to your changes may fail sporadically. We file issues for these with the ["flaky-test" label]() and add a retry statement: +Occasionally, a test unrelated to your changes may fail sporadically. We file issues for these with the ["flaky-test" label](https://github.com/atom/github/issues?q=is%3Aissue+is%3Aopen+label%3Aflaky-test) and add a retry statement: ```js it('passes sometimes and fails sometimes', function() { @@ -76,13 +76,13 @@ it.stress(100, 'seems to break sometimes', function() { ### Style and formatting -We enforce style consistency with eslint and the [fbjs-opensource]() ruleset. Our CI will automatically verify that pull requests conform to the existing ruleset. If you wish to check your changes against our rules before you submit a pull request, run: +We enforce style consistency with eslint and the [fbjs-opensource](https://github.com/facebook/fbjs/tree/master/packages/eslint-config-fbjs-opensource) ruleset. Our CI will automatically verify that pull requests conform to the existing ruleset. If you wish to check your changes against our rules before you submit a pull request, run: ```sh npm run lint ``` -It's often more convenient to have Atom automatically lint and correct your source as you edit. To set this up, you'll need to install a frontend and a backend linter packages. I use [linter-eslint]() as a backend and [atom-ide-ui]() as a frontend. +It's often more convenient to have Atom automatically lint and correct your source as you edit. To set this up, you'll need to install a frontend and a backend linter packages. I use [linter-eslint](https://atom.io/packages/linter-eslint) as a backend and [atom-ide-ui](https://atom.io/packages/atom-ide-ui) as a frontend. ```sh apm-nightly install atom-ide-ui linter-eslint @@ -90,7 +90,7 @@ apm-nightly install atom-ide-ui linter-eslint ### Coverage -Code coverage by our specs is measured by [istanbul]() and reported to [Coveralls](). Links to coverage information will be available in a pull request comment and a status check. While we don't _enforce_ full coverage, we do encourage submissions to not regress our coverage percentage whenever feasible. +Code coverage by our specs is measured by [istanbul](https://istanbul.js.org/) and reported to [Coveralls](https://coveralls.io/github/atom/github?branch=master). Links to coverage information will be available in a pull request comment and a status check. While we don't _enforce_ full coverage, we do encourage submissions to not regress our coverage percentage whenever feasible. If you wish to preview coverage data locally, run one of: @@ -105,7 +105,7 @@ npm run test:coverage:html npm run test:coverage ``` -Generating lcov data allows you to integrate an Atom package like [atom-lcov]() to see covered and uncovered source lines and branches with editor annotations. +Generating lcov data allows you to integrate an Atom package like [atom-lcov](https://atom.io/packages/atom-lcov) to see covered and uncovered source lines and branches with editor annotations. If you prefer the graphical test runner, it may be altered to generate lcov coverage data by adding a command like the following to your `init.js` file: @@ -121,7 +121,7 @@ atom.commands.add('atom-workspace', { ### Snapshotting -To accelerate its launch time, Atom constructs a [v8 snapshot]() at build time that may be loaded much more efficiently than parsing source code from scratch. As a bundled core package, the GitHub package is included in this snapshot. A tool called [electron-link]() is used to pre-process all bundled source to prepare it for snapshot generation. This does introduce some constraints on the code constructs that may be used, however. While uncommon, it pays to be aware of the limitations this introduces. +To accelerate its launch time, Atom constructs a [v8 snapshot](http://blog.atom.io/2017/04/18/improving-startup-time.html) at build time that may be loaded much more efficiently than parsing source code from scratch. As a bundled core package, the GitHub package is included in this snapshot. A tool called [electron-link](https://github.com/atom/electron-link) is used to pre-process all bundled source to prepare it for snapshot generation. This does introduce some constraints on the code constructs that may be used, however. While uncommon, it pays to be aware of the limitations this introduces. The most commonly encountered hindrance is that you cannot reference DOM primitives, native modules, or Atom API constructs _at module require time_ - in other words, with a top-level `const` or `let` expression, or a function or the constructor of a class invoked from one: @@ -147,7 +147,7 @@ Introducing new third-party npm package dependencies (as non-`devDependencies`) We do have a CI job in our test matrix that verifies that a electron-link and snapshot creation succeed for each commit. -If any of these situations are _unavoidable_, individual modules _may_ be excluded from the snapshot generation process by adding them to the exclusion lists [within Atom's build scripts]() and [the GitHub package's snapshot testing script](). Use this solution very sparingly, though, as it impacts Atom's startup time and adds confusion. +If any of these situations are _unavoidable_, individual modules _may_ be excluded from the snapshot generation process by adding them to the exclusion lists [within Atom's build scripts](https://github.com/atom/atom/blob/d29bb96c8ea09e5d9af2eb5b060227d11be2b92a/script/lib/generate-startup-snapshot.js#L27-L68) and [the GitHub package's snapshot testing script](https://github.com/atom/github/blob/3703f571e41f22c7076243abaab1a610b5b37647/test/generation.snapshot.js#L38-L43). Use this solution very sparingly, though, as it impacts Atom's startup time and adds confusion. ## Technical contribution tips @@ -158,11 +158,11 @@ We have a growing body of documentation about the architecture and organization We use the following technologies: * [Atom API](https://atom.io/docs) to interact with the editor. -* [React]() is the framework that powers our view implementation. -* We interact with GitHub via its [GraphQL]() API. +* [React](https://reactjs.org/) is the framework that powers our view implementation. +* We interact with GitHub via its [GraphQL](https://graphql.org/) API. * [Relay](https://github.com/facebook/relay) is a layer of glue between React and GraphQL queries that handles responsibilities like query composition and caching. -* Our tests are written with [Mocha]() and [Chai](). We also use [Enzyme]() to assert against React behavior. -* We use a [custom Babel 6 transpiler pipeline]() to write modern source with JSX, `import` statements, and other constructs unavailable natively within Atom's Node.js version. +* Our tests are written with [Mocha](https://mochajs.org/) and [Chai](https://www.chaijs.com/) [_(with the "assert" style)_](https://www.chaijs.com/api/assert/). We also use [Enzyme](https://airbnb.io/enzyme/) to assert against React behavior. +* We use a [custom Babel 6 transpiler pipeline](https://github.com/atom/atom-babel6-transpiler) to write modern source with JSX, `import` statements, and other constructs unavailable natively within Atom's Node.js version. ### Updating the GraphQL Schema From f61e4ab37b12dc6dc386acf4e497c1fcf0b20201 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 15 Oct 2018 12:35:58 -0400 Subject: [PATCH 0530/4053] Links --- docs/core-team-process.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/core-team-process.md b/docs/core-team-process.md index a2cda3c437..5a09e207fd 100644 --- a/docs/core-team-process.md +++ b/docs/core-team-process.md @@ -14,9 +14,7 @@ We value: When we plan, we choose to pursue _a single task_ as a single team, rather than distributing tasks among ourselves from a queue and working on independent tasks in parallel. This is intended to increase the amount and quality of communication we can share in chat and in synchronous meetings: it's much easier to maintain an ongoing technical conversation when all participants share the mental context of a unified goal. -This does _not_ mean that we all pair program all the time. We do get value from pair programming but this is not always practical or desirable. Pair programming may be chosen independently from the methods below -- functionally, the pair becomes one "developer" in any of the descriptions. - -To implement this, we have several distinct approaches we can employ: +This does not mean that we all pair program all the time. We do get value from pair programming but this is not always practical or desirable. Pair programming may be chosen independently from the methods below -- functionally, the pair becomes one "developer" in any of the descriptions. ### 1. Seams @@ -75,5 +73,5 @@ In this method, each developer (or pair) tackles a single problem in serial duri In addition to these strategies, we can take advantage of other technologies to help us feel connected in an ambient way. -* We all open [Teletype]() portals as we work, even when not actively pairing, and share the URL in Slack. We join each other's portals in a window on a separate Atom window and watch each other's progress as a background process. +* We all open [Teletype](https://teletype.atom.io/) portals as we work, even when not actively pairing, and share the URL in Slack. We join each other's portals in a window on a separate Atom window and watch each other's progress as a background process. * We stream to the world on [Twitch](https://twitch.tv) as we work. We sometimes jump into each other's streams to chat or catch up. From 579821c4f063dcb506cbd046bd21e7b14e621c83 Mon Sep 17 00:00:00 2001 From: simurai Date: Tue, 16 Oct 2018 10:30:24 +0900 Subject: [PATCH 0531/4053] Some more edits --- docs/rfcs/003-pull-request-review.md | 45 +++++++++++++++------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/docs/rfcs/003-pull-request-review.md b/docs/rfcs/003-pull-request-review.md index c614f552bb..c299afb2cd 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/rfcs/003-pull-request-review.md @@ -16,21 +16,24 @@ Peer review is also a critical part of the path to acceptance for pull requests ## Explanation -### Review information in Pull Request list - -Review progress is indicated for open pull requests listed in the GitHub panel. The pull request corresponding to the checked out branch gets special treatment in its own section at the top of the list. +### Pull Request list ![image](https://user-images.githubusercontent.com/378023/46524357-89bf6580-c8c3-11e8-8e2d-ea02d5a1f278.png) -> :construction: This mockup is still WIP and probably will change. +* Review progress is indicated for open pull requests listed in the GitHub panel. +* The pull request corresponding to the checked out branch gets special treatment in its own section at the top of the list. + +![center pane](https://user-images.githubusercontent.com/378023/46985265-75c9fe00-d124-11e8-9b34-572cd1aaf701.png) -Clicking a pull request in the list opens a `PullRequestDetailItem` in the workspace center. +* Clicking a pull request in the list opens a `PullRequestDetailItem` in the workspace center. ### PullRequestDetailItem #### Header +![header](https://user-images.githubusercontent.com/378023/46536358-3829d180-c8e9-11e8-9167-3d1003ab566b.png) + At the top of each `PullRequestDetailItem` is a summary about the pull request, followed by the tabs to switch between different sub-views. - Overview @@ -39,26 +42,22 @@ At the top of each `PullRequestDetailItem` is a summary about the pull request, - Commits - Build Status -After the tabs users can search or filter. The default is to show all files, all authors, and unresolved comments. Filtering based on file type, author, search term makes it possible to narrow down a long list of diffs. Toggling comments or collapse files is also possible. - -![header](https://user-images.githubusercontent.com/378023/46536358-3829d180-c8e9-11e8-9167-3d1003ab566b.png) +Below the tabs is a "tools bar" for controls to toggle review comments or collapse files. #### Footer -A panel at the bottom of the pane shows the progress for resolved review comments. It also has a "Review Changes" button to create a new review. - ![reviews panel](https://user-images.githubusercontent.com/378023/46536010-17ad4780-c8e8-11e8-8338-338bb592efc5.png) -This panel is persistent throughout all sub-views. It allows creating a reviews no matter where you are. Below shown with the existing sub-views: +A panel at the bottom of the pane shows the progress for resolved review comments. It also has a "Review Changes" button to create a new review. This panel is persistent throughout all sub-views. It allows creating new reviews no matter where you are. Below examples with the existing sub-views: Overview | Commits | Build Status --- | --- | --- ![overview](https://user-images.githubusercontent.com/378023/46535907-ca30da80-c8e7-11e8-9401-2b8660d56c25.png) | ![commits](https://user-images.githubusercontent.com/378023/46535908-ca30da80-c8e7-11e8-87ca-01637f2554b6.png) | ![build status](https://user-images.githubusercontent.com/378023/46535909-cac97100-c8e7-11e8-8813-47fdaa3ece57.png) -### Files +### Files (tab) -Under the "Files" tab the full, multi-file diff associated with the pull request is displayed. This is akin to the "Files changed" tab on dotcom. +Clicking on the "Files" tab displays the full, multi-file diff associated with the pull request. This is akin to the "Files changed" tab on dotcom. ![files](https://user-images.githubusercontent.com/378023/46536560-d3bb4200-c8e9-11e8-9764-dca0b84245cf.png) @@ -72,9 +71,9 @@ Uncollapsed (default) | Collapsed * For large diffs, the files can be collapsed to get a better overview. -### Reviews +### Reviews (tab) -The "Reviews" tab shows all reviews. This is akin to the review summaries that appear on the "Conversation" tab on dotcom. In addition, each review also includes review comments and their diff. +Clicking on the "Reviews" tab shows all reviews of a pull request. This is akin to the review summaries that appear on the "Conversation" tab on dotcom. In addition, each review also includes review comments and their diff. ![reviews](https://user-images.githubusercontent.com/378023/46535563-c81a4c00-c8e6-11e8-9c0b-6ea575556101.png) @@ -83,13 +82,13 @@ Uncollapsed (default) | Collapsed ![reviews](https://user-images.githubusercontent.com/378023/46535563-c81a4c00-c8e6-11e8-9c0b-6ea575556101.png) | ![collapsed reviews](https://user-images.githubusercontent.com/378023/46926357-62a72780-d06b-11e8-9344-23389d1c727c.png) * Comments can be collapsed to get a better overview. -* Reviews get sorted by: +* Reviews get sorted by "urgency". Showing reviews that still need to get adressed at the top: 1. "recommended" changes 2. "commented" changes 3. "no review" (when a reviewer only leaves review comments, but no summary) 4. "approved" changes 5. "previous" reviews (when a reviewer made an earlier review and it's now out-dated) - +* Within each group, sorting is done by "newest first". #### Create a new review @@ -107,7 +106,7 @@ Hovering along the gutter within a pull request diff region in a `TextEditor` or ![pending review](https://user-images.githubusercontent.com/378023/46927357-e06d3200-d06f-11e8-9eae-b4c289fe16ae.png) * If a draft review is already in progress, the "Start a review" button reads "Add review comment". -* And an additional row is added with options to "Start a new conversation" or "Finish your review". +* An additional row is added with options to "Start a new conversation" or "Finish your review". ##### Submit a review @@ -129,8 +128,8 @@ Clicking "Finish your review" from a comment or clicking "Review Changes" in the ![resolve a review](https://user-images.githubusercontent.com/378023/46927875-c08b3d80-d072-11e8-978b-024111312d79.png) -* Review comments can be resolved by clicking on the "Resolve conversation" buttons. If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. - +* Review comments can be resolved by clicking on the "Resolve conversation" buttons. +* If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. #### Context and navigation @@ -151,6 +150,12 @@ In order to navigate between comments or switch context, each comment has the fo * The up and down arrow buttons navigate to the next and previous unresolved review comments. * Reaction emoji may be added to each comment with the "emoji" button. Existing emoji reaction tallies are included beneath each comment. +Another way to navigate between unresolved comments is to collapse all files first. Files that contain unresolved comments have a "[n] unresolved" button on the right, making it easy to find them. + +![files with unresolved comments](https://user-images.githubusercontent.com/378023/46986769-022bef00-d12c-11e8-8839-279fb0d03fb1.png) + +* Clicking that button uncollapses the file (if needed) and scrolls to the position of the comment. + ## Drawbacks From fc12a77bfa5b1a1e40e6ad012fac284f836758d4 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 16 Oct 2018 14:28:54 +0200 Subject: [PATCH 0532/4053] fix tests --- lib/controllers/root-controller.js | 2 +- test/controllers/root-controller.test.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index a26299f5ef..39b06c6de9 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -476,13 +476,13 @@ export default class RootController extends React.Component { this.setState({cloneDialogInProgress: true}); try { await this.props.cloneRepositoryForProjectPath(remoteUrl, projectPath); + addEvent('clone-repo', {package: 'github'}); } catch (e) { this.props.notificationManager.addError( `Unable to clone ${remoteUrl}`, {detail: e.stdErr, dismissable: true}, ); } finally { - addEvent('clone-repo', {package: 'github'}); this.setState({cloneDialogInProgress: false, cloneDialogActive: false}); } } diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 4c83ff1d51..f2da3d896e 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -342,17 +342,18 @@ describe('RootController', function() { assert.lengthOf(wrapper.find('Panel').find({location: 'modal'}).find('CloneDialog'), 1); }); - it('triggers the clone callback on accept and fires `clone-repo` event', function() { + it('triggers the clone callback on accept and fires `clone-repo` event', async function() { sinon.stub(reporterProxy, 'addEvent'); wrapper.instance().openCloneDialog(); wrapper.update(); const dialog = wrapper.find('CloneDialog'); - dialog.prop('didAccept')('git@github.com:atom/github.git', '/home/me/github'); + const promise = dialog.prop('didAccept')('git@github.com:atom/github.git', '/home/me/github'); resolveClone(); + await promise; assert.isTrue(cloneRepositoryForProjectPath.calledWith('git@github.com:atom/github.git', '/home/me/github')); - assert.isTrue(reporterProxy.addEvent.calledWith('clone-repo', {package: 'github'})) + await assert.isTrue(reporterProxy.addEvent.calledWith('clone-repo', {package: 'github'})); }); it('marks the clone dialog as in progress during clone', async function() { From 96d396934d7dd95f779ea3a68b4b12e4a06f58f3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 16 Oct 2018 15:28:02 -0400 Subject: [PATCH 0533/4053] Register missing commands --- lib/views/file-patch-view.js | 44 ++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 17609b2f85..4f5b38838a 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -184,16 +184,42 @@ export default class FilePatchView extends React.Component { } renderCommands() { + let stageModeCommand = null; + if (this.props.filePatch.didChangeExecutableMode()) { + const command = this.withSelectionMode({ + unstaged: () => 'github:stage-file-mode-change', + staged: () => 'github:unstage-file-mode-change', + }); + stageModeCommand = ; + } + + let stageSymlinkCommand = null; + if (this.props.filePatch.hasSymlink()) { + const command = this.withSelectionMode({ + unstaged: () => 'github:stage-symlink-change', + staged: () => 'github:unstage-symlink-change', + }); + stageSymlinkCommand = ; + } + return ( - - - - - - - - - + + + + + + + + + + + {stageModeCommand} + {stageSymlinkCommand} + + + + + ); } From c785d24a0da458bc70be993b85a5af6741fa34fc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 16 Oct 2018 15:46:02 -0400 Subject: [PATCH 0534/4053] More command shuffling --- lib/views/file-patch-view.js | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 4f5b38838a..0bbac27ffc 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -203,23 +203,18 @@ export default class FilePatchView extends React.Component { } return ( - - - - - - - - - - - {stageModeCommand} - {stageSymlinkCommand} - - - - - + + + + + + + + + + {stageModeCommand} + {stageSymlinkCommand} + ); } @@ -557,12 +552,6 @@ export default class FilePatchView extends React.Component { ); } - undoLastDiscardFromCommand = () => { - if (this.props.hasUndoHistory) { - this.props.undoLastDiscard({eventSource: {command: 'github:undo-last-discard-in-diff-view'}}); - } - } - undoLastDiscardFromCoreUndo = () => { if (this.props.hasUndoHistory) { this.props.undoLastDiscard({eventSource: {command: 'core:undo'}}); From 7a7f93b1c074ada2b1a2608f2d447e0242adda37 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 16 Oct 2018 15:53:14 -0400 Subject: [PATCH 0535/4053] withSelectionMode() is not used for unstaged/staged --- lib/views/file-patch-view.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 0bbac27ffc..504939bb8c 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -186,19 +186,17 @@ export default class FilePatchView extends React.Component { renderCommands() { let stageModeCommand = null; if (this.props.filePatch.didChangeExecutableMode()) { - const command = this.withSelectionMode({ - unstaged: () => 'github:stage-file-mode-change', - staged: () => 'github:unstage-file-mode-change', - }); + const command = this.props.stagingStatus === 'unstaged' + ? 'github:stage-file-mode-change' + : 'github:unstage-file-mode-change'; stageModeCommand = ; } let stageSymlinkCommand = null; if (this.props.filePatch.hasSymlink()) { - const command = this.withSelectionMode({ - unstaged: () => 'github:stage-symlink-change', - staged: () => 'github:unstage-symlink-change', - }); + const command = this.props.stagingStatus === 'unstaged' + ? 'github:stage-symlink-change' + : 'github:unstage-symlink-change'; stageSymlinkCommand = ; } From a389f6f78e2869dfc76225eb3ac8330627e4a24a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 16 Oct 2018 15:59:38 -0400 Subject: [PATCH 0536/4053] Pass hasUndoHistory from repo data --- lib/containers/file-patch-container.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/containers/file-patch-container.js b/lib/containers/file-patch-container.js index cc224d8063..3b3865f08c 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/file-patch-container.js @@ -52,8 +52,9 @@ export default class FilePatchContainer extends React.Component { return ( ); From 6264611aede4365a34feacaafabf2c4756a63d03 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 16 Oct 2018 16:31:15 -0400 Subject: [PATCH 0537/4053] Unify didDiscardSelection and discardSelectionFromCommand --- lib/views/file-patch-view.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 504939bb8c..074bb830a8 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -61,7 +61,7 @@ export default class FilePatchView extends React.Component { this, 'didMouseDownOnHeader', 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', 'didConfirm', 'didToggleSelectionMode', 'selectNextHunk', 'selectPreviousHunk', 'hunkSelectDown', 'hunkSelectUp', - 'didDiscardSelection', 'didOpenFile', 'didAddSelection', 'didChangeSelectionRange', 'didDestroySelection', + 'didOpenFile', 'didAddSelection', 'didChangeSelectionRange', 'didDestroySelection', 'oldLineNumberLabel', 'newLineNumberLabel', ); @@ -204,7 +204,7 @@ export default class FilePatchView extends React.Component { - + @@ -561,7 +561,11 @@ export default class FilePatchView extends React.Component { } discardSelectionFromCommand = () => { - this.discardSelection({eventSource: {command: 'github:discard-selected-lines'}}); + return this.props.discardRows( + this.props.selectedRows, + this.props.selectionMode, + {eventSource: {command: 'github:discard-selected-lines'}}, + ); } toggleHunkSelection(hunk, containsSelection) { @@ -738,10 +742,6 @@ export default class FilePatchView extends React.Component { return this.props.toggleRows(this.props.selectedRows, this.props.selectionMode); } - didDiscardSelection() { - return this.props.discardRows(this.props.selectedRows, this.props.selectionMode); - } - didToggleSelectionMode() { const selectedHunks = this.getSelectedHunks(); this.withSelectionMode({ From ec6c96b444171cc8ca89e53f28732fde3f7290e1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 16 Oct 2018 16:32:17 -0400 Subject: [PATCH 0538/4053] Cover the new instrumentation callbacks --- test/views/file-patch-view.test.js | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 7d805c7ef5..f0c70afd89 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -83,6 +83,15 @@ describe('FilePatchView', function() { assert.isTrue(wrapper.find('FilePatchHeaderView').exists()); }); + it('undoes the last discard from the file header button', function() { + const undoLastDiscard = sinon.spy(); + const wrapper = shallow(buildApp({undoLastDiscard})); + + wrapper.find('FilePatchHeaderView').prop('undoLastDiscard')(); + + assert.isTrue(undoLastDiscard.calledWith({eventSource: 'button'})); + }); + it('renders the file patch within an editor', function() { const wrapper = mount(buildApp()); @@ -836,6 +845,36 @@ describe('FilePatchView', function() { assert.isTrue(toggleRows.called); }); + it('undoes the last discard', function() { + const undoLastDiscard = sinon.spy(); + const wrapper = mount(buildApp({undoLastDiscard, hasUndoHistory: true})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'core:undo'); + + assert.isTrue(undoLastDiscard.calledWith({eventSource: {command: 'core:undo'}})); + }); + + it('does nothing when there is no last discard to undo', function() { + const undoLastDiscard = sinon.spy(); + const wrapper = mount(buildApp({undoLastDiscard, hasUndoHistory: false})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'core:undo'); + + assert.isFalse(undoLastDiscard.called); + }); + + it('discards selected rows', function() { + const discardRows = sinon.spy(); + const wrapper = mount(buildApp({discardRows, selectedRows: new Set([1, 2]), selectionMode: 'line'})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:discard-selected-lines'); + + assert.isTrue(discardRows.called); + assert.sameMembers(Array.from(discardRows.lastCall.args[0]), [1, 2]); + assert.strictEqual(discardRows.lastCall.args[1], 'line'); + assert.deepEqual(discardRows.lastCall.args[2], {eventSource: {command: 'github:discard-selected-lines'}}); + }); + it('toggles the patch selection mode from line to hunk', function() { const selectedRowsChanged = sinon.spy(); const selectedRows = new Set([2]); From 5c784ae63cc23c090ceebc6e48d8011bb76d042c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 16 Oct 2018 16:32:41 -0400 Subject: [PATCH 0539/4053] Unused code --- lib/views/file-patch-view.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 074bb830a8..f6e824e844 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -792,18 +792,6 @@ export default class FilePatchView extends React.Component { }); } - hunkSelectDown() { - this.refEditor.map(editor => { - return null; - }); - } - - hunkSelectUp() { - this.refEditor.map(editor => { - return null; - }); - } - didOpenFile() { const cursors = []; From c239435fdcc75ce940fc910b7a852dd8deafa7ea Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 17 Oct 2018 08:18:24 -0400 Subject: [PATCH 0540/4053] Update test/models/patch/file-patch.test.js --- test/models/patch/file-patch.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 98a066f570..c0a794c3e3 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -248,6 +248,8 @@ describe('FilePatch', function() { assert.isFalse(new FilePatch(executableFile, executableFile, emptyPatch).didChangeExecutableMode()); assert.isFalse(new FilePatch(nullFile, nonExecutableFile).didChangeExecutableMode()); assert.isFalse(new FilePatch(nullFile, executableFile).didChangeExecutableMode()); + assert.isFalse(new FilePatch(nonExecutableFile, nullFile).didChangeExecutableMode()); + assert.isFalse(new FilePatch(executableFile, nullFile).didChangeExecutableMode()); }); it('detects changes in symlink mode', function() { From bd7fb597a3c94c7d13499d4d67461bfe8ab98380 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 17 Oct 2018 08:18:50 -0400 Subject: [PATCH 0541/4053] Update test/models/patch/file-patch.test.js --- test/models/patch/file-patch.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index c0a794c3e3..a592803c92 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -262,6 +262,8 @@ describe('FilePatch', function() { assert.isFalse(new FilePatch(symlinkFile, symlinkFile, emptyPatch).hasTypechange()); assert.isFalse(new FilePatch(nullFile, nonSymlinkFile).hasTypechange()); assert.isFalse(new FilePatch(nullFile, symlinkFile).hasTypechange()); + assert.isFalse(new FilePatch(nonSymlinkFile, nullFile).hasTypechange()); + assert.isFalse(new FilePatch(symlinkFile, nullFile).hasTypechange()); }); it('detects when either file has a symlink destination', function() { From 81c36cf677f08605adac6a57d44e061fe553839e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 17 Oct 2018 08:19:05 -0400 Subject: [PATCH 0542/4053] Update test/models/patch/file-patch.test.js --- test/models/patch/file-patch.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index a592803c92..ceef206d65 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -276,6 +276,8 @@ describe('FilePatch', function() { assert.isTrue(new FilePatch(symlinkFile, symlinkFile, emptyPatch).hasSymlink()); assert.isFalse(new FilePatch(nullFile, nonSymlinkFile).hasSymlink()); assert.isTrue(new FilePatch(nullFile, symlinkFile).hasSymlink()); + assert.isFalse(new FilePatch(nonSymlinkFile, nullFile).hasSymlink()); + assert.isTrue(new FilePatch(symlinkFile, nullFile).hasSymlink()); }); }); From 8e7e39f5a42ceff3d05ca7c0a0e179a2fbfb3e09 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 17 Oct 2018 08:22:12 -0400 Subject: [PATCH 0543/4053] Remove autobind call for deleted method --- lib/views/file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index f6e824e844..8736496e21 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -60,7 +60,7 @@ export default class FilePatchView extends React.Component { autobind( this, 'didMouseDownOnHeader', 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', - 'didConfirm', 'didToggleSelectionMode', 'selectNextHunk', 'selectPreviousHunk', 'hunkSelectDown', 'hunkSelectUp', + 'didConfirm', 'didToggleSelectionMode', 'selectNextHunk', 'selectPreviousHunk', 'didOpenFile', 'didAddSelection', 'didChangeSelectionRange', 'didDestroySelection', 'oldLineNumberLabel', 'newLineNumberLabel', ); From 43de24a88e1761b7f27d5e331ed5a2e8736da91e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 17 Oct 2018 08:22:54 -0400 Subject: [PATCH 0544/4053] Port #1667 on top of the rewrite --- lib/items/file-patch-item.js | 2 +- test/items/file-patch-item.test.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/items/file-patch-item.js b/lib/items/file-patch-item.js index 40f6a58dfa..b6e56347dc 100644 --- a/lib/items/file-patch-item.js +++ b/lib/items/file-patch-item.js @@ -29,7 +29,7 @@ export default class FilePatchItem extends React.Component { static buildURI(relPath, workingDirectory, stagingStatus) { return 'atom-github://file-patch/' + - relPath + + encodeURIComponent(relPath) + `?workdir=${encodeURIComponent(workingDirectory)}` + `&stagingStatus=${encodeURIComponent(stagingStatus)}`; } diff --git a/test/items/file-patch-item.test.js b/test/items/file-patch-item.test.js index 76cb23d8f8..00467f1d2f 100644 --- a/test/items/file-patch-item.test.js +++ b/test/items/file-patch-item.test.js @@ -106,6 +106,19 @@ describe('FilePatchItem', function() { }); }); + describe('buildURI', function() { + it('correctly uri encodes all components', function() { + const filePathWithSpecialChars = '???.txt'; + const stagingStatus = 'staged'; + const workdirPath = '/???/!!!'; + + const uri = FilePatchItem.buildURI(filePathWithSpecialChars, workdirPath, stagingStatus); + assert.include(uri, encodeURIComponent(filePathWithSpecialChars)); + assert.include(uri, encodeURIComponent(workdirPath)); + assert.include(uri, encodeURIComponent(stagingStatus)); + }); + }); + it('terminates pending state', async function() { const wrapper = mount(buildPaneApp()); From f95a53f76cea17c6a6247b3793426574f8739797 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 17 Oct 2018 16:50:56 +0200 Subject: [PATCH 0545/4053] add unit tests for context menu actions reporting --- test/controllers/root-controller.test.js | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index f2da3d896e..d2a100aee4 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -1088,4 +1088,45 @@ describe('RootController', function() { }); }); }); + + describe('context commands trigger event reporting', function() { + let wrapper; + + beforeEach(async function() { + const repository = await buildRepository(await cloneRepository('multiple-commits')); + app = React.cloneElement(app, { + repository, + startOpen: true, + startRevealed: true, + }); + wrapper = mount(app); + sinon.stub(reporterProxy, 'addEvent'); + }); + + it('sends an event when a command is triggered via a context menu', function() { + commandRegistry.dispatch( + wrapper.find('CommitView').getDOMNode(), + 'github:toggle-expanded-commit-message-editor', + [{contextCommand: true}], + ); + assert.isTrue(reporterProxy.addEvent.called); + }); + + it('does not send an event when a command is triggered in other ways', function() { + commandRegistry.dispatch( + wrapper.find('CommitView').getDOMNode(), + 'github:toggle-expanded-commit-message-editor', + ); + assert.isFalse(reporterProxy.addEvent.called); + }); + + it('does not send an event when a command not starting with github: is triggered via a context menu', function() { + commandRegistry.dispatch( + wrapper.find('CommitView').getDOMNode(), + 'core:copy', + [{contextCommand: true}], + ); + assert.isFalse(reporterProxy.addEvent.called); + }); + }); }); From 668541d4d2daf22555149bf35cfd7276ec0a93be Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 17 Oct 2018 17:51:47 +0200 Subject: [PATCH 0546/4053] more detailed spec --- test/controllers/root-controller.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index d2a100aee4..b3d3645d2f 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -1109,7 +1109,11 @@ describe('RootController', function() { 'github:toggle-expanded-commit-message-editor', [{contextCommand: true}], ); - assert.isTrue(reporterProxy.addEvent.called); + assert.isTrue(reporterProxy.addEvent.calledWith( + 'context-menu-action', { + package: 'github', + command: 'github:toggle-expanded-commit-message-editor', + })); }); it('does not send an event when a command is triggered in other ways', function() { From d6dfc4b89a893dea2b720896123ecea94f94722a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 08:38:00 -0400 Subject: [PATCH 0547/4053] Assert patch old and new files more strictly --- test/models/patch/file-patch.test.js | 42 +++++++++++++++------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index ceef206d65..8355fb56e5 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -343,10 +343,8 @@ describe('FilePatch', function() { const stagedPatch = filePatch.getStagePatchForLines(new Set([1, 3])); assert.strictEqual(stagedPatch.getStatus(), 'modified'); - assert.strictEqual(stagedPatch.getOldPath(), 'file.txt'); - assert.strictEqual(stagedPatch.getOldMode(), '100644'); - assert.strictEqual(stagedPatch.getNewPath(), 'file.txt'); - assert.strictEqual(stagedPatch.getNewMode(), '100644'); + assert.strictEqual(stagedPatch.getOldFile(), oldFile); + assert.strictEqual(stagedPatch.getNewFile(), newFile); assert.strictEqual(stagedPatch.getBuffer().getText(), '0000\n0001\n0003\n0004\n'); assertInFilePatch(stagedPatch).hunks( { @@ -364,7 +362,7 @@ describe('FilePatch', function() { }); describe('staging lines from deleted files', function() { - let deletionPatch; + let oldFile, deletionPatch; beforeEach(function() { const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); @@ -379,7 +377,7 @@ describe('FilePatch', function() { }), ]; const patch = new Patch({status: 'deleted', hunks, buffer, layers}); - const oldFile = new File({path: 'file.txt', mode: '100644'}); + oldFile = new File({path: 'file.txt', mode: '100644'}); deletionPatch = new FilePatch(oldFile, nullFile, patch); }); @@ -387,10 +385,8 @@ describe('FilePatch', function() { const stagedPatch = deletionPatch.getStagePatchForLines(new Set([1, 2])); assert.strictEqual(stagedPatch.getStatus(), 'modified'); - assert.strictEqual(stagedPatch.getOldPath(), 'file.txt'); - assert.strictEqual(stagedPatch.getOldMode(), '100644'); - assert.strictEqual(stagedPatch.getNewPath(), 'file.txt'); - assert.strictEqual(stagedPatch.getNewMode(), '100644'); + assert.strictEqual(stagedPatch.getOldFile(), oldFile); + assert.strictEqual(stagedPatch.getNewFile(), oldFile); assert.strictEqual(stagedPatch.getBuffer().getText(), '0000\n0001\n0002\n'); assertInFilePatch(stagedPatch).hunks( { @@ -408,8 +404,7 @@ describe('FilePatch', function() { it('handles staging all lines, leaving nothing unstaged', function() { const stagedPatch = deletionPatch.getStagePatchForLines(new Set([1, 2, 3])); assert.strictEqual(stagedPatch.getStatus(), 'deleted'); - assert.strictEqual(stagedPatch.getOldPath(), 'file.txt'); - assert.strictEqual(stagedPatch.getOldMode(), '100644'); + assert.strictEqual(stagedPatch.getOldFile(), oldFile); assert.isFalse(stagedPatch.getNewFile().isPresent()); assert.strictEqual(stagedPatch.getBuffer().getText(), '0000\n0001\n0002\n'); assertInFilePatch(stagedPatch).hunks( @@ -437,12 +432,12 @@ describe('FilePatch', function() { }), ]; const patch = new Patch({status: 'deleted', hunks, buffer, layers}); - const oldFile = new File({path: 'file.txt', mode: '100644'}); + oldFile = new File({path: 'file.txt', mode: '100644'}); const newFile = new File({path: 'file.txt', mode: '120000'}); const replacePatch = new FilePatch(oldFile, newFile, patch); const stagedPatch = replacePatch.getStagePatchForLines(new Set([0, 1, 2])); - assert.isTrue(stagedPatch.getOldFile().isPresent()); + assert.strictEqual(stagedPatch.getOldFile(), oldFile); assert.isFalse(stagedPatch.getNewFile().isPresent()); }); }); @@ -478,6 +473,8 @@ describe('FilePatch', function() { const stagedPatch = filePatch.getStagePatchForHunk(hunks[1]); assert.strictEqual(stagedPatch.getBuffer().getText(), '0003\n0004\n0005\n'); + assert.strictEqual(stagedPatch.getOldFile(), oldFile); + assert.strictEqual(stagedPatch.getNewFile(), newFile); assertInFilePatch(stagedPatch).hunks( { startRow: 0, @@ -515,10 +512,8 @@ describe('FilePatch', function() { const unstagedPatch = filePatch.getUnstagePatchForLines(new Set([1, 3])); assert.strictEqual(unstagedPatch.getStatus(), 'modified'); - assert.strictEqual(unstagedPatch.getOldPath(), 'file.txt'); - assert.strictEqual(unstagedPatch.getOldMode(), '100644'); - assert.strictEqual(unstagedPatch.getNewPath(), 'file.txt'); - assert.strictEqual(unstagedPatch.getNewMode(), '100644'); + assert.strictEqual(unstagedPatch.getOldFile(), newFile); + assert.strictEqual(unstagedPatch.getNewFile(), newFile); assert.strictEqual(unstagedPatch.getBuffer().getText(), '0000\n0001\n0002\n0003\n0004\n'); assertInFilePatch(unstagedPatch).hunks( { @@ -559,6 +554,8 @@ describe('FilePatch', function() { it('handles unstaging part of the file', function() { const unstagePatch = addedFilePatch.getUnstagePatchForLines(new Set([2])); assert.strictEqual(unstagePatch.getStatus(), 'modified'); + assert.strictEqual(unstagePatch.getOldFile(), newFile); + assert.strictEqual(unstagePatch.getNewFile(), newFile); assertInFilePatch(unstagePatch).hunks( { startRow: 0, @@ -575,6 +572,8 @@ describe('FilePatch', function() { it('handles unstaging all lines, leaving nothing staged', function() { const unstagePatch = addedFilePatch.getUnstagePatchForLines(new Set([0, 1, 2])); assert.strictEqual(unstagePatch.getStatus(), 'deleted'); + assert.strictEqual(unstagePatch.getOldFile(), newFile); + assert.isFalse(unstagePatch.getNewFile().isPresent()); assertInFilePatch(unstagePatch).hunks( { startRow: 0, @@ -587,11 +586,12 @@ describe('FilePatch', function() { ); }); - it('unsets the oldFile when a symlink is deleted and a file is created in its place', function() { + it('unsets the newFile when a symlink is deleted and a file is created in its place', function() { const oldSymlink = new File({path: 'file.txt', mode: '120000', symlink: 'wat.txt'}); const patch = new FilePatch(oldSymlink, newFile, addedPatch); const unstagePatch = patch.getUnstagePatchForLines(new Set([0, 1, 2])); - assert.isFalse(unstagePatch.getOldFile().isPresent()); + assert.strictEqual(unstagePatch.getOldFile(), newFile); + assert.isFalse(unstagePatch.getNewFile().isPresent()); assertInFilePatch(unstagePatch).hunks( { startRow: 0, @@ -636,6 +636,8 @@ describe('FilePatch', function() { const unstagedPatch = filePatch.getUnstagePatchForHunk(hunks[0]); assert.strictEqual(unstagedPatch.getBuffer().getText(), '0000\n0001\n0002\n'); + assert.strictEqual(unstagedPatch.getOldFile(), newFile); + assert.strictEqual(unstagedPatch.getNewFile(), newFile); assertInFilePatch(unstagedPatch).hunks( { startRow: 0, From 63045ba589bf9d90d54e2ed90d5a9970aa9268b8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 08:38:49 -0400 Subject: [PATCH 0548/4053] Correct (maybe) unstage patch generation --- lib/models/patch/file-patch.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 0f73c35cd4..d8e7dcb48d 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -193,22 +193,20 @@ export default class FilePatch { getUnstagePatchForLines(selectedLineSet) { if (this.patch.getChangedLineCount() === selectedLineSet.size) { - const patch = this.patch.getFullUnstagedPatch(); - if (this.hasTypechange() && this.getStatus() === 'added') { - // handle special case when a file was created after a symlink was deleted. - // In order to unstage the file creation, we must ensure that the unstage patch has no new file, - // so when the patch is applied to the index, there file will be removed from the index. - return this.clone({oldFile: nullFile, patch}); - } else { - return this.clone({patch}); - } + // Ensure that newFile is null if the patch is an addition because we're deleting the entire file from the index. + // If a symlink was deleted and replaced by a non-symlink file, we don't want the symlink entry to muck up the + // patch. + return this.clone({ + oldFile: this.getNewFile(), + newFile: this.getStatus() === 'added' ? nullFile : this.getNewFile(), + patch: this.patch.getFullUnstagedPatch(), + }); } else { - const patch = this.patch.getUnstagePatchForLines(selectedLineSet); - if (this.getStatus() === 'added') { - return this.clone({oldFile: this.getNewFile(), patch}); - } else { - return this.clone({patch}); - } + return this.clone({ + oldFile: this.getNewFile(), + newFile: this.getNewFile(), + patch: this.patch.getUnstagePatchForLines(selectedLineSet), + }); } } From ac5e3cc19d6523a42e5f0e3be0038a8c8b7ab249 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 12:02:45 -0400 Subject: [PATCH 0549/4053] Skeleton for integration tests against patches --- test/integration/file-patch.test.js | 208 ++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 test/integration/file-patch.test.js diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js new file mode 100644 index 0000000000..a7b34532ae --- /dev/null +++ b/test/integration/file-patch.test.js @@ -0,0 +1,208 @@ +import fs from 'fs-extra'; +import path from 'path'; +import until from 'test-until'; + +import {setup, teardown} from './helpers'; +import GitShellOutStrategy from '../../lib/git-shell-out-strategy'; + +describe('integration: file patches', function() { + let context, wrapper, atomEnv; + let workspace; + let commands, workspaceElement; + let repoRoot, git; + + beforeEach(async function() { + context = await setup(this.currentTest, { + initialRoots: ['three-files'], + }); + + wrapper = context.wrapper; + atomEnv = context.atomEnv; + commands = atomEnv.commands; + workspace = atomEnv.workspace; + + repoRoot = atomEnv.project.getPaths()[0]; + git = new GitShellOutStrategy(repoRoot); + + workspaceElement = atomEnv.views.getView(workspace); + + // Open the git tab + await commands.dispatch(workspaceElement, 'github:toggle-git-tab-focus'); + wrapper.update(); + }); + + afterEach(async function() { + await teardown(context); + }); + + function repoPath(...parts) { + return path.join(repoRoot, ...parts); + } + + async function clickFileInGitTab(stagingStatus, relativePath) { + let listItem = null; + + await until(() => { + listItem = wrapper + .update() + .find(`.github-StagingView-${stagingStatus} .github-FilePatchListView-item`) + .filterWhere(w => w.find('.github-FilePatchListView-path').text() === relativePath); + return listItem.exists(); + }, `Unable to find list item for path ${relativePath}`); + + listItem.simulate('mousedown', {button: 0, persist() {}}); + window.dispatchEvent(new MouseEvent('mouseup')); + + await assert.async.isTrue(wrapper.update().find('.github-FilePatchView').exists()); + } + + function getPatchEditor() { + return wrapper + .find('.github-FilePatchView') + .find('AtomTextEditor') + .instance() + .getModel(); + } + + function patchContent(...rows) { + const aliases = new Map([ + ['added', 'github-FilePatchView-line--added'], + ['deleted', 'github-FilePatchView-line--deleted'], + ['nonewline', 'github-FilePatchView-line--nonewline'], + ['selected', 'github-FilePatchView-line--selected'], + ]); + const knownClasses = new Set(aliases.values()); + + let actualRowText = []; + const differentRows = new Set(); + const actualClassesByRow = new Map(); + const missingClassesByRow = new Map(); + const unexpectedClassesByRow = new Map(); + + return until(() => { + // Determine the CSS classes applied to each screen line within the patch editor. This is gnarly, but based on + // the logic that TextEditorComponent::queryDecorationsToRender() actually uses to determine what classes to + // apply when rendering line elements. + const editor = getPatchEditor(); + const decorationsByMarker = editor.decorationManager.decorationPropertiesByMarkerForScreenRowRange(0, Infinity); + actualClassesByRow.clear(); + for (const [marker, decorations] of decorationsByMarker) { + const rowNumbers = marker.getScreenRange().getRows(); + + for (const decoration of decorations) { + if (decoration.type !== 'line') { + continue; + } + + for (const row of rowNumbers) { + const classes = actualClassesByRow.get(row) || []; + classes.push(decoration.class); + actualClassesByRow.set(row, classes); + } + } + } + + actualRowText = []; + differentRows.clear(); + missingClassesByRow.clear(); + unexpectedClassesByRow.clear(); + let match = true; + + for (let i = 0; i < rows.length; i++) { + const [expectedText, ...givenClasses] = rows[i]; + const expectedClasses = givenClasses.map(givenClass => aliases.get(givenClass) || givenClass); + + const actualText = editor.lineTextForScreenRow(i); + const actualClasses = new Set(actualClassesByRow.get(i) || []); + + actualRowText[i] = actualText; + + if (actualText !== expectedText) { + // The patch text for this screen row differs. + differentRows.add(i); + match = false; + } + + const missingClasses = expectedClasses.filter(expectedClass => !actualClasses.delete(expectedClass)); + if (missingClasses.length > 0) { + // An expected class was not present on this screen row. + missingClassesByRow.set(i, missingClasses); + match = false; + } + + const unexpectedClasses = Array.from(actualClasses).filter(remainingClass => knownClasses.has(remainingClass)); + if (unexpectedClasses.length > 0) { + // A known class that was not expected was present on this screen row. + unexpectedClassesByRow.set(i, unexpectedClasses); + match = false; + } + } + + return match; + }, 'waiting for the updated file patch to arrive').catch(e => { + let diagnosticOutput = ''; + for (let i = 0; i < actualRowText.length; i++) { + diagnosticOutput += differentRows.has(i) ? '! ' : ' '; + diagnosticOutput += actualRowText[i]; + + const annotations = []; + annotations.push(...actualClassesByRow.get(i) || []); + for (const missingClass of (missingClassesByRow.get(i) || [])) { + annotations.push(`-"${missingClass}"`); + } + for (const unexpectedClass of (unexpectedClassesByRow.get(i) || [])) { + annotations.push(`x"${unexpectedClass}"`); + } + if (annotations.length > 0) { + diagnosticOutput += ' '; + diagnosticOutput += annotations.join(' '); + } + + diagnosticOutput += '\n'; + } + + // eslint-disable-next-line no-console + console.error('Unexpected patch contents:\n', diagnosticOutput); + + throw e; + }); + } + + describe('with an added file', function() { + beforeEach(async function() { + await fs.writeFile(repoPath('added-file.txt'), '0000\n0001\n0002\n0003\n0004\n0005\n', {encoding: 'utf8'}); + await clickFileInGitTab('unstaged', 'added-file.txt'); + }); + + describe('unstaged', function() { + it('may be partially staged', async function() { + // Stage lines two and three + getPatchEditor().setSelectedBufferRange([[2, 1], [3, 3]]); + await wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); + + await patchContent( + ['0000', 'added'], + ['0001', 'added'], + ['0002'], + ['0003'], + ['0004', 'added', 'selected'], + ['0005', 'added'], + ); + }); + + it('may be completed staged'); + + it('may discard lines'); + }); + + describe('staged', function() { + beforeEach(async function() { + await git.stageFile('added-file.txt'); + }); + + it('may be partially unstaged'); + + it('may be completely unstaged'); + }); + }); +}); From 74f7aa863397bdfbcfe2767465af25384587be98 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 13:14:05 -0400 Subject: [PATCH 0550/4053] Wait for the correct FilePatchItem to open and load --- test/integration/file-patch.test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index a7b34532ae..93c70753dd 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -48,12 +48,16 @@ describe('integration: file patches', function() { .find(`.github-StagingView-${stagingStatus} .github-FilePatchListView-item`) .filterWhere(w => w.find('.github-FilePatchListView-path').text() === relativePath); return listItem.exists(); - }, `Unable to find list item for path ${relativePath}`); + }, `list item for path ${relativePath} (${stagingStatus}) appears`); listItem.simulate('mousedown', {button: 0, persist() {}}); window.dispatchEvent(new MouseEvent('mouseup')); - await assert.async.isTrue(wrapper.update().find('.github-FilePatchView').exists()); + const itemSelector = `FilePatchItem[relPath="${relativePath}"][stagingStatus="${stagingStatus}"]`; + await until( + () => wrapper.update().find(itemSelector).find('.github-FilePatchView').exists(), + `File patch pane item for ${relativePath} arrives and loads`, + ); } function getPatchEditor() { From b81c5bc9f935525f53600b9d0fb9c0ba3fd1f2c6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 13:14:25 -0400 Subject: [PATCH 0551/4053] Ensure the editor does not contain *more* rows than expected --- test/integration/file-patch.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 93c70753dd..cc57c2c54c 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -112,8 +112,8 @@ describe('integration: file patches', function() { unexpectedClassesByRow.clear(); let match = true; - for (let i = 0; i < rows.length; i++) { - const [expectedText, ...givenClasses] = rows[i]; + for (let i = 0; i < Math.max(rows.length, editor.getLastScreenRow()); i++) { + const [expectedText, ...givenClasses] = rows[i] || ['']; const expectedClasses = givenClasses.map(givenClass => aliases.get(givenClass) || givenClass); const actualText = editor.lineTextForScreenRow(i); From 9e38dc15dbd5ef3c72e9f9bb0bc23cf607cb9d08 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 13:17:23 -0400 Subject: [PATCH 0552/4053] Assert the staged side of that patch too --- test/integration/file-patch.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index cc57c2c54c..f9d443ae6a 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -192,6 +192,12 @@ describe('integration: file patches', function() { ['0004', 'added', 'selected'], ['0005', 'added'], ); + + await clickFileInGitTab('staged', 'added-file.txt'); + await patchContent( + ['0002', 'added', 'selected'], + ['0003', 'added', 'selected'], + ); }); it('may be completed staged'); From 3166bbbe0d65f1c08ac3f3ddc2e7df41f2e0a69c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 13:39:12 -0400 Subject: [PATCH 0553/4053] No point in awaiting that --- test/integration/file-patch.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index f9d443ae6a..d7a9c51009 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -182,7 +182,7 @@ describe('integration: file patches', function() { it('may be partially staged', async function() { // Stage lines two and three getPatchEditor().setSelectedBufferRange([[2, 1], [3, 3]]); - await wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); + wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); await patchContent( ['0000', 'added'], From a0f069405e5d7ea5ffb8267c42dc30b7ead75154 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 13:39:47 -0400 Subject: [PATCH 0554/4053] Stage a file completely --- test/integration/file-patch.test.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index d7a9c51009..ea5702bad9 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -200,7 +200,20 @@ describe('integration: file patches', function() { ); }); - it('may be completed staged'); + it('may be completed staged', async function() { + getPatchEditor().selectAll(); + wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); + + await clickFileInGitTab('staged', 'added-file.txt'); + await patchContent( + ['0000', 'added', 'selected'], + ['0001', 'added', 'selected'], + ['0002', 'added', 'selected'], + ['0003', 'added', 'selected'], + ['0004', 'added', 'selected'], + ['0005', 'added', 'selected'], + ); + }); it('may discard lines'); }); From c7a62c1a508af52d7c08e74ae4ffd470e96dfe32 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 13:45:18 -0400 Subject: [PATCH 0555/4053] getPatchEditor() and patchContent() are more specific --- test/integration/file-patch.test.js | 32 +++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index ea5702bad9..b359528cf0 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -60,15 +60,20 @@ describe('integration: file patches', function() { ); } - function getPatchEditor() { - return wrapper + function getPatchEditor(stagingStatus, relativePath) { + const component = wrapper + .find(`FilePatchItem[relPath="${relativePath}"][stagingStatus="${stagingStatus}"]`) .find('.github-FilePatchView') - .find('AtomTextEditor') - .instance() - .getModel(); + .find('AtomTextEditor'); + + if (!component.exists()) { + return null; + } + + return component.instance().getModel(); } - function patchContent(...rows) { + function patchContent(stagingStatus, relativePath, ...rows) { const aliases = new Map([ ['added', 'github-FilePatchView-line--added'], ['deleted', 'github-FilePatchView-line--deleted'], @@ -87,7 +92,12 @@ describe('integration: file patches', function() { // Determine the CSS classes applied to each screen line within the patch editor. This is gnarly, but based on // the logic that TextEditorComponent::queryDecorationsToRender() actually uses to determine what classes to // apply when rendering line elements. - const editor = getPatchEditor(); + const editor = getPatchEditor(stagingStatus, relativePath); + if (editor === null) { + actualRowText = ['Unable to find patch item']; + return false; + } + const decorationsByMarker = editor.decorationManager.decorationPropertiesByMarkerForScreenRowRange(0, Infinity); actualClassesByRow.clear(); for (const [marker, decorations] of decorationsByMarker) { @@ -181,10 +191,11 @@ describe('integration: file patches', function() { describe('unstaged', function() { it('may be partially staged', async function() { // Stage lines two and three - getPatchEditor().setSelectedBufferRange([[2, 1], [3, 3]]); + getPatchEditor('unstaged', 'added-file.txt').setSelectedBufferRange([[2, 1], [3, 3]]); wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); await patchContent( + 'unstaged', 'added-file.txt', ['0000', 'added'], ['0001', 'added'], ['0002'], @@ -195,17 +206,19 @@ describe('integration: file patches', function() { await clickFileInGitTab('staged', 'added-file.txt'); await patchContent( + 'staged', 'added-file.txt', ['0002', 'added', 'selected'], ['0003', 'added', 'selected'], ); }); it('may be completed staged', async function() { - getPatchEditor().selectAll(); + getPatchEditor('unstaged', 'added-file.txt').selectAll(); wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); await clickFileInGitTab('staged', 'added-file.txt'); await patchContent( + 'staged', 'added-file.txt', ['0000', 'added', 'selected'], ['0001', 'added', 'selected'], ['0002', 'added', 'selected'], @@ -221,6 +234,7 @@ describe('integration: file patches', function() { describe('staged', function() { beforeEach(async function() { await git.stageFile('added-file.txt'); + await clickFileInGitTab('staged', 'added-file.txt'); }); it('may be partially unstaged'); From 01fa8bfc419092d619aa79eda3941a1bb7809fe3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 13:51:30 -0400 Subject: [PATCH 0556/4053] Test for discarding lines --- test/integration/file-patch.test.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index b359528cf0..26c83224d4 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -228,7 +228,20 @@ describe('integration: file patches', function() { ); }); - it('may discard lines'); + it('may discard lines', async function() { + getPatchEditor('unstaged', 'added-file.txt').setSelectedBufferRange([[1, 0], [3, 3]]); + wrapper.find('.github-HunkHeaderView-discardButton').simulate('click'); + + await patchContent( + 'unstaged', 'added-file.txt', + ['0000', 'added'], + ['0004', 'added', 'selected'], + ['0005', 'added'], + ); + + const editor = await workspace.open(repoPath('added-file.txt')); + assert.strictEqual(editor.getText(), '0000\n0004\n0005\n'); + }); }); describe('staged', function() { From ce63d24a1df802b380601f5e6331022e325baabc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 15:19:03 -0400 Subject: [PATCH 0557/4053] Unstaged file action tests --- test/integration/file-patch.test.js | 42 ++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 26c83224d4..15c536d7db 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -246,13 +246,49 @@ describe('integration: file patches', function() { describe('staged', function() { beforeEach(async function() { - await git.stageFile('added-file.txt'); + await git.stageFiles(['added-file.txt']); await clickFileInGitTab('staged', 'added-file.txt'); }); - it('may be partially unstaged'); + it('may be partially unstaged', async function() { + getPatchEditor('staged', 'added-file.txt').setSelectedBufferRange([[3, 0], [4, 3]]); + wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); + + await patchContent( + 'staged', 'added-file.txt', + ['0000', 'added'], + ['0001', 'added'], + ['0002', 'added'], + ['0005', 'added', 'selected'], + ); + + await clickFileInGitTab('unstaged', 'added-file.txt'); + await patchContent( + 'unstaged', 'added-file.txt', + ['0000'], + ['0001'], + ['0002'], + ['0003', 'added', 'selected'], + ['0004', 'added', 'selected'], + ['0005'], + ); + }); + + it('may be completely unstaged', async function() { + getPatchEditor('staged', 'added-file.txt').selectAll(); + wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); - it('may be completely unstaged'); + await clickFileInGitTab('unstaged', 'added-file.txt'); + await patchContent( + 'unstaged', 'added-file.txt', + ['0000', 'added', 'selected'], + ['0001', 'added', 'selected'], + ['0002', 'added', 'selected'], + ['0003', 'added', 'selected'], + ['0004', 'added', 'selected'], + ['0005', 'added', 'selected'], + ); + }); }); }); }); From 95554b6f25460061ccd69a130d497f3ee98f216a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 18 Oct 2018 15:59:33 -0400 Subject: [PATCH 0558/4053] Stub other cases to cover --- test/integration/file-patch.test.js | 66 ++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 15c536d7db..eb84ed9130 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -5,7 +5,7 @@ import until from 'test-until'; import {setup, teardown} from './helpers'; import GitShellOutStrategy from '../../lib/git-shell-out-strategy'; -describe('integration: file patches', function() { +describe.only('integration: file patches', function() { let context, wrapper, atomEnv; let workspace; let commands, workspaceElement; @@ -291,4 +291,68 @@ describe('integration: file patches', function() { }); }); }); + + describe('with a removed file', function() { + describe('unstaged', function() { + it('may be partially staged'); + + it('may be completely staged'); + + it('may discard lines'); + }); + + describe('staged', function() { + it('may be partially unstaged'); + + it('may be partially staged'); + }); + }); + + describe('with a symlink that used to be a file', function() { + describe('unstaged', function() { + it('may stage the content deletion without the symlink creation'); + + it('may stage the content deletion and the symlink creation'); + }); + + describe('staged', function() { + it('may unstage the content deletion and the symlink creation'); + }); + }); + + describe('with a file that used to be a symlink', function() { + describe('unstaged', function() { + it('may stage the symlink deletion without the content addition'); + + it('may stage the content addition and the symlink deletion'); + }); + + describe('staged', function() { + it('may unstage the content addition and the symlink creation'); + + it('may unstage the content addition without the symlink creation'); + + it('may unstage the symlink creation without the content addition'); + }); + }); + + describe('with a modified file', function() { + describe('unstaged', function() { + it('may be partially staged'); + + it('may be completely staged'); + + it('may discard lines'); + + it('may stage an executable mode change'); + }); + + describe('staged', function() { + it('may be partially unstaged'); + + it('may be partially staged'); + + it('may unstage an executable mode change'); + }); + }); }); From 7fa7b9f197bcd1cb76e8ca08119049e117d25533 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 19 Oct 2018 14:54:11 -0400 Subject: [PATCH 0559/4053] Refactor common Linux build steps into a template file --- .vsts.yml | 53 +++++++----------------- script/azure-pipelines/linux-install.yml | 16 +++++++ 2 files changed, 32 insertions(+), 37 deletions(-) create mode 100644 script/azure-pipelines/linux-install.yml diff --git a/.vsts.yml b/.vsts.yml index fe25b4e8bb..8a8e8daf84 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -5,28 +5,23 @@ jobs: strategy: matrix: dev: - ATOM_CHANNEL: dev - ATOM_NAME: atom-dev + atom_channel: dev + atom_name: atom-dev beta: - ATOM_CHANNEL: beta - ATOM_NAME: atom-beta + atom_channel: beta + atom_name: atom-beta stable: - ATOM_CHANNEL: stable - ATOM_NAME: atom + atom_channel: stable + atom_name: atom variables: display: ":99" steps: + - template: script/azure-pipelines/linux-install.yml + parameters: + atom_channel: $(atom_channel) + atom_name: $(atom_name) - bash: | - curl -s -L "https://atom.io/download/deb?channel=${ATOM_CHANNEL}" \ - -H 'Accept: application/octet-stream' \ - -o 'atom-amd64.deb' - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 - sudo apt-get update && sudo apt-get install -yyq libgconf-2-4 build-essential git libsecret-1-dev - dpkg-deb -x atom-amd64.deb /tmp/atom - displayName: install Atom - - bash: /tmp/atom/usr/share/${ATOM_NAME}/resources/app/apm/bin/apm ci - displayName: install dependencies - - bash: /tmp/atom/usr/bin/${ATOM_NAME} --test test/ + "/tmp/atom/usr/bin/${ATOM_NAME}" --test test/ displayName: run tests env: TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml @@ -37,7 +32,7 @@ jobs: inputs: testResultsFormat: JUnit testResultsFiles: $(Agent.HomeDirectory)/test-results.xml - testRunTitle: Linux $(ATOM_CHANNEL) + testRunTitle: Linux $(atom_channel) condition: succeededOrFailed() - job: MacOS @@ -158,17 +153,9 @@ jobs: vmImage: ubuntu-16.04 variables: display: ":99" + ATOM_CHANNEL: dev steps: - - bash: | - curl -s -L "https://atom.io/download/deb?channel=dev" \ - -H 'Accept: application/octet-stream' \ - -o 'atom-amd64.deb' - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 - sudo apt-get update && sudo apt-get install -yyq libgconf-2-4 build-essential git libsecret-1-dev - dpkg-deb -x atom-amd64.deb /tmp/atom - displayName: install Atom - - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci - displayName: install dependencies + - template: script/azure-pipelines/linux-install.yml - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/node_modules/.bin/npm run lint displayName: run the linter @@ -177,17 +164,9 @@ jobs: vmImage: ubuntu-16.04 variables: display: ":99" + ATOM_CHANNEL: dev steps: - - bash: | - curl -s -L "https://atom.io/download/deb?channel=dev" \ - -H 'Accept: application/octet-stream' \ - -o 'atom-amd64.deb' - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 - sudo apt-get update && sudo apt-get install -yyq libgconf-2-4 build-essential git libsecret-1-dev - dpkg-deb -x atom-amd64.deb /tmp/atom - displayName: install Atom - - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/bin/apm ci - displayName: install dependencies + - template: script/azure-pipelines/linux-install.yml - bash: /tmp/atom/usr/bin/atom-dev --test test/ displayName: run tests env: diff --git a/script/azure-pipelines/linux-install.yml b/script/azure-pipelines/linux-install.yml new file mode 100644 index 0000000000..5dd0abe7a8 --- /dev/null +++ b/script/azure-pipelines/linux-install.yml @@ -0,0 +1,16 @@ +parameters: + atom_channel: dev + atom_name: atom-dev + +steps: +- bash: | + curl -s -L "https://atom.io/download/deb?channel=${{ parameters.atom_channel }}" \ + -H 'Accept: application/octet-stream' \ + -o 'atom-amd64.deb' + /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 + sudo apt-get update && sudo apt-get install -yyq libgconf-2-4 build-essential git libsecret-1-dev + dpkg-deb -x atom-amd64.deb /tmp/atom + displayName: install Atom +- bash: | + "/tmp/atom/usr/share/${{ parameters.atom_name }}/resources/app/apm/bin/apm" ci + displayName: install dependencies From 25c5e164d6ec866691224c0083eabd10bffb1eea Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 19 Oct 2018 15:00:28 -0400 Subject: [PATCH 0560/4053] Refactor out common MacOS steps --- .vsts.yml | 29 ++++++++++-------------- script/azure-pipelines/macos-install.yml | 16 +++++++++++++ 2 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 script/azure-pipelines/macos-install.yml diff --git a/.vsts.yml b/.vsts.yml index 8a8e8daf84..f90212f83e 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -41,26 +41,21 @@ jobs: strategy: matrix: dev: - ATOM_CHANNEL: dev - ATOM_APP: Atom Dev.app + atom_channel: dev + atom_app: Atom Dev.app beta: - ATOM_CHANNEL: beta - ATOM_APP: Atom Beta.app + atom_channel: beta + atom_app: Atom Beta.app stable: - ATOM_CHANNEL: stable - ATOM_APP: Atom.app + atom_channel: stable + atom_app: Atom.app steps: + - template: script/azure-pipelines/macos-install.yml + parameters: + atom_channel: $(atom_channel) + atom_app: $(atom_app) - bash: | - set -x - curl -s -L "https://atom.io/download/mac?channel=${ATOM_CHANNEL}" \ - -H 'Accept: application/octet-stream' \ - -o "atom.zip" - mkdir -p /tmp/atom - unzip -q atom.zip -d /tmp/atom - displayName: install Atom - - bash: '"/tmp/atom/${ATOM_APP}/Contents/Resources/app/apm/bin/apm" ci' - displayName: install dependencies - - bash: '"/tmp/atom/${ATOM_APP}/Contents/Resources/app/atom.sh" --test test/' + "/tmp/atom/${ATOM_APP}/Contents/Resources/app/atom.sh" --test test/ displayName: run tests env: TEST_JUNIT_XML_PATH: $(Agent.HomeDirectory)/test-results.xml @@ -71,7 +66,7 @@ jobs: inputs: testResultsFormat: JUnit testResultsFiles: $(Agent.HomeDirectory)/test-results.xml - testRunTitle: MacOS $(ATOM_CHANNEL) + testRunTitle: MacOS $(atom_channel) condition: succeededOrFailed() - job: Windows diff --git a/script/azure-pipelines/macos-install.yml b/script/azure-pipelines/macos-install.yml new file mode 100644 index 0000000000..2f2f3e16e1 --- /dev/null +++ b/script/azure-pipelines/macos-install.yml @@ -0,0 +1,16 @@ +parameters: + atom_channel: dev + atom_app: Atom Dev.app + +steps: +- bash: | + set -x + curl -s -L "https://atom.io/download/mac?channel=${{ parameters.atom_channel }}" \ + -H 'Accept: application/octet-stream' \ + -o "atom.zip" + mkdir -p /tmp/atom + unzip -q atom.zip -d /tmp/atom + displayName: install Atom +- bash: | + "/tmp/atom/${{ parameters.atom_app }}/Contents/Resources/app/apm/bin/apm" ci + displayName: install dependencies From 179af466ffeff3501abe9ae45336f14cc68b3ae8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 19 Oct 2018 15:09:17 -0400 Subject: [PATCH 0561/4053] Refactor out Windows build steps --- .vsts.yml | 50 ++++++++-------------- script/azure-pipelines/windows-install.yml | 28 ++++++++++++ 2 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 script/azure-pipelines/windows-install.yml diff --git a/.vsts.yml b/.vsts.yml index f90212f83e..cd698fc984 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -75,39 +75,19 @@ jobs: strategy: matrix: dev: - ATOM_CHANNEL: dev - ATOM_DIRECTORY: Atom Dev + atom_channel: dev + atom_directory: Atom Dev beta: - ATOM_CHANNEL: beta - ATOM_DIRECTORY: Atom Beta + atom_channel: beta + atom_directory: Atom Beta stable: - ATOM_CHANNEL: stable - ATOM_DIRECTORY: Atom + atom_channel: stable + atom_directory: Atom steps: - - powershell: | - Set-StrictMode -Version Latest - $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" - - Write-Host "Downloading latest Atom release" - $source = "https://atom.io/download/windows_zip?channel=$env:ATOM_CHANNEL" - $destination = "atom.zip" - - (New-Object System.Net.WebClient).DownloadFile($source, $destination) - Expand-Archive -Path "atom.zip" -DestinationPath $script:ATOMROOT - displayName: install Atom - - powershell: | - Set-StrictMode -Version Latest - $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" - $script:APM_SCRIPT_PATH = "$script:ATOMROOT\$env:ATOM_DIRECTORY\resources\app\apm\bin\apm.cmd" - - Write-Host "Installing package dependencies" - & "$script:APM_SCRIPT_PATH" ci - if ($LASTEXITCODE -ne 0) { - Write-Host "Dependency installation failed" - $host.SetShouldExit($LASTEXITCODE) - exit - } - displayName: install dependencies + - template: script/azure-pipelines/windows-install.yml + parameters: + atom_channel: $(atom_channel) + atom_directory: $(atom_directory) - powershell: | Set-StrictMode -Version Latest $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" @@ -140,7 +120,7 @@ jobs: inputs: testResultsFormat: JUnit testResultsFiles: $(Agent.HomeDirectory)/test-results.xml - testRunTitle: Windows $(ATOM_CHANNEL) + testRunTitle: Windows $(atom_channel) condition: succeededOrFailed() - job: Lint @@ -148,9 +128,11 @@ jobs: vmImage: ubuntu-16.04 variables: display: ":99" - ATOM_CHANNEL: dev + atom_channel: dev steps: - template: script/azure-pipelines/linux-install.yml + parameters: + atom_channel: $(atom_channel) - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/node_modules/.bin/npm run lint displayName: run the linter @@ -159,9 +141,11 @@ jobs: vmImage: ubuntu-16.04 variables: display: ":99" - ATOM_CHANNEL: dev + atom_channel: dev steps: - template: script/azure-pipelines/linux-install.yml + parameters: + atom_channel: $(atom_channel) - bash: /tmp/atom/usr/bin/atom-dev --test test/ displayName: run tests env: diff --git a/script/azure-pipelines/windows-install.yml b/script/azure-pipelines/windows-install.yml new file mode 100644 index 0000000000..16b7dd72b1 --- /dev/null +++ b/script/azure-pipelines/windows-install.yml @@ -0,0 +1,28 @@ +parameters: + atom_channel: dev + atom_directory: Atom Dev + +steps: +- powershell: | + Set-StrictMode -Version Latest + $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" + + Write-Host "Downloading latest Atom release" + $source = "https://atom.io/download/windows_zip?channel=${{ parameters.atom_channel }}" + $destination = "atom.zip" + + (New-Object System.Net.WebClient).DownloadFile($source, $destination) + Expand-Archive -Path "atom.zip" -DestinationPath $script:ATOMROOT + displayName: install Atom +- powershell: | + Set-StrictMode -Version Latest + $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" + $script:APM_SCRIPT_PATH = "$script:ATOMROOT\${{ parameters.atom_directory }}\resources\app\apm\bin\apm.cmd" + + & "$script:APM_SCRIPT_PATH" ci + if ($LASTEXITCODE -ne 0) { + Write-Host "Dependency installation failed" + $host.SetShouldExit($LASTEXITCODE) + exit + } + displayName: install dependencies From e70b2f0ff45c8611e2a1d106443bcf87803583e8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 19 Oct 2018 15:39:23 -0400 Subject: [PATCH 0562/4053] Explicitly pass template variables --- .vsts.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vsts.yml b/.vsts.yml index cd698fc984..ba38b30287 100644 --- a/.vsts.yml +++ b/.vsts.yml @@ -133,6 +133,7 @@ jobs: - template: script/azure-pipelines/linux-install.yml parameters: atom_channel: $(atom_channel) + atom_name: atom-dev - bash: /tmp/atom/usr/share/atom-dev/resources/app/apm/node_modules/.bin/npm run lint displayName: run the linter @@ -146,6 +147,7 @@ jobs: - template: script/azure-pipelines/linux-install.yml parameters: atom_channel: $(atom_channel) + atom_name: atom-dev - bash: /tmp/atom/usr/bin/atom-dev --test test/ displayName: run tests env: From 861410d02e9a84e98582a605a2603bfdee08a0b1 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 19 Oct 2018 14:49:52 -0700 Subject: [PATCH 0563/4053] Update how-we-work to capture RFC process modifications discussed Co-Authored-By: Ash Wilson Co-Authored-By: Vanessa Yuen --- docs/how-we-work.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/how-we-work.md b/docs/how-we-work.md index 54b763f54a..6b50fc4f5b 100644 --- a/docs/how-we-work.md +++ b/docs/how-we-work.md @@ -72,6 +72,12 @@ Major, cross-cutting refactoring efforts fit within this category. Our goals wit To introduce brand-new functionality into the package, follow this guide. +##### On using RFCs + +We use a lightweight RFC (request for comments) process to ensure that folks have an opportunity to weigh in on design, alternatives, drawbacks, questions, and concerns. The RFC is meant to be a living document that will be modified over the duration of development as things evolve. It provides a quick and easily scannable summary of what was discussed and decided. + +Development work on the feature may start at any point once the RFC pull request has been opened with a description of the feature. The RFC is merged once the feature work is merged. + ##### Process 1. On a feature branch, write a proposal as a markdown document beneath [`docs/rfcs`](/docs/rfcs) in this repository. Copy the [template](/docs/rfcs/000-template.md) to begin. Open a pull request. The RFC document should include: @@ -80,16 +86,15 @@ To introduce brand-new functionality into the package, follow this guide. * A specification of when the feature will be considered "done"; * Unresolved questions or possible follow-on work; * A sequence of discrete phases that can be used to realize the full feature; - * The acceptance criteria for the RFC itself, as chosen by your current understanding of its scope and impact. Some options you may use here include _(a)_ you're satisfied with its state; _(b)_ the pull request has collected a predetermined number of :+1: votes from core team members; or _(c)_ unanimous :+1: votes from the full core team. -2. @-mention @simurai on the open pull request for design input. Begin hashing out mock-ups, look and feel, specific user interaction details, and decide on a high-level direction for the feature. -3. The RFC's author is responsible for recognizing when its acceptance criteria have been met and merging its pull request. :rainbow: _Our intent here is to give the feature's advocate the ability to cut [bikeshedding](https://en.wiktionary.org/wiki/bikeshedding) short and accept responsibility for guiding it forward._ -4. Work on the RFC's implementation is performed in one or more pull requests. Remember to add each pull request to the current sprint project. +1. @-mention @simurai on the open pull request for design input. Begin hashing out mock-ups, look and feel, specific user interaction details, and decide on a high-level direction for the feature. +1. Feature development may begin at any point after the RFC pull request has been opened. +1. Work on the RFC's implementation is performed in one or more pull requests. Try to break out work into smaller pull requests as much as possible to ship incremental changes. Remember to add each pull request to the current sprint project. * Consider gating your work behind a feature flag or a configuration option. * Write tests for your new work. * Optionally [request reviewers](#how-we-review) if you want feedback. Ping @simurai for ongoing UI/UX considerations if appropriate. * Merge your pull request yourself when CI is green and any reviewers you have requested have approved the PR. * As the design evolves and opinions change, modify the existing RFC to stay accurate. -5. When the feature is complete, update the RFC to a "completed" state. +1. When the feature is complete, update the RFC to a "completed" state and merge it. For any outstanding work that didn't get implemented, open issues or start new RFCs. ### Expansions or retractions of package scope From bc1b2eb43571dad81c8209fd7e16339f9a4a8be4 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 19 Oct 2018 15:26:20 -0700 Subject: [PATCH 0564/4053] State what goals of RFC are and are not Co-Authored-By: Ash Wilson Co-Authored-By: Vanessa Yuen --- docs/how-we-work.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/how-we-work.md b/docs/how-we-work.md index 6b50fc4f5b..5d8917697f 100644 --- a/docs/how-we-work.md +++ b/docs/how-we-work.md @@ -74,10 +74,14 @@ To introduce brand-new functionality into the package, follow this guide. ##### On using RFCs -We use a lightweight RFC (request for comments) process to ensure that folks have an opportunity to weigh in on design, alternatives, drawbacks, questions, and concerns. The RFC is meant to be a living document that will be modified over the duration of development as things evolve. It provides a quick and easily scannable summary of what was discussed and decided. +We use a lightweight RFC (request for comments) process to ensure that folks have an opportunity to weigh in on design, alternatives, drawbacks, questions, and concerns. It provides a quick and easily scannable summary of what was discussed and decided. + +The goal is to suss out important considerations and valuable ideas as early as possible and encourage more holistic / bigger picture thinking. The goal is NOT to flesh out the perfect design or come to complete consensus before we start building. Development work on the feature may start at any point once the RFC pull request has been opened with a description of the feature. The RFC is merged once the feature work is merged. +The RFC is meant to be a living document that will be modified over the duration of development as things evolve, new information is discovered, and UXR is conducted. + ##### Process 1. On a feature branch, write a proposal as a markdown document beneath [`docs/rfcs`](/docs/rfcs) in this repository. Copy the [template](/docs/rfcs/000-template.md) to begin. Open a pull request. The RFC document should include: From 4c3b0f3b7cdd4cb862479a9cd5949bf0d1b12451 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 19 Oct 2018 15:34:14 -0700 Subject: [PATCH 0565/4053] Add section for blog post feature blurb draft --- docs/rfcs/000-template.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/rfcs/000-template.md b/docs/rfcs/000-template.md index 3e332c5b5f..ac2b885296 100644 --- a/docs/rfcs/000-template.md +++ b/docs/rfcs/000-template.md @@ -41,3 +41,9 @@ Why should we *not* do this? - Can this functionality be introduced in multiple, distinct, self-contained pull requests? - A specification for when the feature is considered "done." + +## Blog post feature blurb draft + +- When this feature is shipped, what would we like to say or show in our Atom release blog post (example: http://blog.atom.io/2018/07/31/atom-1-29.html) +- Feel free to drop ideas and gifs here during development +- Once development is complete, write a blurb draft for the release coordinator to copy/paste into the Atom release blog From 0ad0ca12291c6ee220051c73ddacd1be90cd0db0 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 19 Oct 2018 15:39:34 -0700 Subject: [PATCH 0566/4053] Rename blog post draft section --- docs/rfcs/000-template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rfcs/000-template.md b/docs/rfcs/000-template.md index ac2b885296..576ef91628 100644 --- a/docs/rfcs/000-template.md +++ b/docs/rfcs/000-template.md @@ -42,8 +42,8 @@ Why should we *not* do this? - Can this functionality be introduced in multiple, distinct, self-contained pull requests? - A specification for when the feature is considered "done." -## Blog post feature blurb draft +## Feature description for Atom release blog post - When this feature is shipped, what would we like to say or show in our Atom release blog post (example: http://blog.atom.io/2018/07/31/atom-1-29.html) - Feel free to drop ideas and gifs here during development -- Once development is complete, write a blurb draft for the release coordinator to copy/paste into the Atom release blog +- Once development is complete, write a blurb for the release coordinator to copy/paste into the Atom release blog From 9643eda4874f8132195f59126b9fa90502bbe02d Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 19 Oct 2018 17:29:02 -0700 Subject: [PATCH 0567/4053] Prepare 0.21.0-0 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 96e5e60cab..1f42fef462 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.20.0", + "version": "0.21.0-0", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From fd6be233d2459970d56896f5080159643a77a459 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 22 Oct 2018 18:19:53 +0200 Subject: [PATCH 0568/4053] RFCs are hard --- docs/rfcs/004-multi-file-diff.md | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 docs/rfcs/004-multi-file-diff.md diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md new file mode 100644 index 0000000000..e971384512 --- /dev/null +++ b/docs/rfcs/004-multi-file-diff.md @@ -0,0 +1,67 @@ +# Feature title + +## Status + +Proposed + +## Summary + +Users can select multiple files in unstaged changes and staged changes pane and see the diffs of selected files in one view, akin to the [`Files changed` tab in pull requests on github.com](https://github.com/atom/github/pull/1747/files). + +## Motivation + +So that users can view a set of changes with more context before staging or committing those changes. + +The ability to display multiple diffs in one view will also serve as a building block for the following planned features: +- [commit pane item](#1655) where it shows all changes in a single commit +- [new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) that shows all changed files proposed in a PR + + +## Explanation + +#### Unstaged Changes +- User can `cmd+click` and select multiple files from the list of unstaged changes, and the pane on the left (see multi-file diff section below) will show diffs of the selected files. That pane will continue to reflect any further selecting/unselecting on the Unstaged Changes pane. +- Once there is at least one file selected, `Stage All` button should be worded as `Stage Selected`. + +#### Staged Changes +- Same behavior as Unstaged Changes pane. +- Once there is at least one file selected, `Unstage All` button should be worded as `Unstage Selected`. + +#### Multi-file diff view +_(note: The following is a summary of what we would like the UX to achieve, but I don't have a clear visuals of what that looks like yet.)_ +- Shows diffs of multiple files as a stack. +- Each diff should show up as its own block, and the current functionality should remain independent of each block. +- It should be easy to jump quickly to a specific file you care about, or back to the file list to get to another file. Dotcom does so by creating a `jump to` drop down. +- As user scrolls through a long list of diffs, there should be a sticky heading which remains visible showing the filename of the diff being viewed. + +#### Mock-ups coming soon + +## Drawbacks + +- `cmd-click` to select multiple files might not be as universally known as we assume, so that might affect discoverability of this feature. +- There might be performance concerns having to render many diffs at once. + +## Rationale and alternatives + +- Why is this approach the best in the space of possible approaches? +- What other approaches have been considered and what is the rationale for not choosing them? +- What is the impact of not doing this? + +## Unresolved questions + +- What unresolved questions do you expect to resolve through the implementation of this feature before it is released in a new version of the package? + +TBD +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? + +TBD + +## Implementation phases + +- Can this functionality be introduced in multiple, distinct, self-contained pull requests? + +TBD + +- A specification for when the feature is considered "done." + +TBD From d6267ccffec432cc56b7eb27b38c7ec0f9e99a3b Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 22 Oct 2018 18:37:32 +0200 Subject: [PATCH 0569/4053] YOLO --- docs/rfcs/004-multi-file-diff.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index e971384512..d7d214decf 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -6,7 +6,7 @@ Proposed ## Summary -Users can select multiple files in unstaged changes and staged changes pane and see the diffs of selected files in one view, akin to the [`Files changed` tab in pull requests on github.com](https://github.com/atom/github/pull/1747/files). +Users can select multiple files in unstaged changes and staged changes pane and see the diffs of selected files in one view, akin to the [`Files changed` tab in pull requests on github.com](https://github.com/atom/github/pull/1752/files). ## Motivation @@ -19,11 +19,11 @@ The ability to display multiple diffs in one view will also serve as a building ## Explanation -#### Unstaged Changes +#### Unstaged Changes pane - User can `cmd+click` and select multiple files from the list of unstaged changes, and the pane on the left (see multi-file diff section below) will show diffs of the selected files. That pane will continue to reflect any further selecting/unselecting on the Unstaged Changes pane. - Once there is at least one file selected, `Stage All` button should be worded as `Stage Selected`. -#### Staged Changes +#### Staged Changes pane - Same behavior as Unstaged Changes pane. - Once there is at least one file selected, `Unstage All` button should be worded as `Unstage Selected`. @@ -52,16 +52,13 @@ _(note: The following is a summary of what we would like the UX to achieve, but - What unresolved questions do you expect to resolve through the implementation of this feature before it is released in a new version of the package? TBD + - What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? TBD ## Implementation phases - -- Can this functionality be introduced in multiple, distinct, self-contained pull requests? - TBD -- A specification for when the feature is considered "done." - +## Definition of done TBD From cf7b2bb8def9bd93732e862c8db8a37fdb21b9c7 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 22 Oct 2018 18:52:34 +0200 Subject: [PATCH 0570/4053] *sobbing intensifies* --- docs/rfcs/004-multi-file-diff.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index d7d214decf..82bb6d2b65 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -1,4 +1,4 @@ -# Feature title +# Multi-file Diffs ## Status @@ -19,6 +19,8 @@ The ability to display multiple diffs in one view will also serve as a building ## Explanation +#### Mock-ups coming soon + #### Unstaged Changes pane - User can `cmd+click` and select multiple files from the list of unstaged changes, and the pane on the left (see multi-file diff section below) will show diffs of the selected files. That pane will continue to reflect any further selecting/unselecting on the Unstaged Changes pane. - Once there is at least one file selected, `Stage All` button should be worded as `Stage Selected`. @@ -29,12 +31,12 @@ The ability to display multiple diffs in one view will also serve as a building #### Multi-file diff view _(note: The following is a summary of what we would like the UX to achieve, but I don't have a clear visuals of what that looks like yet.)_ + - Shows diffs of multiple files as a stack. - Each diff should show up as its own block, and the current functionality should remain independent of each block. - It should be easy to jump quickly to a specific file you care about, or back to the file list to get to another file. Dotcom does so by creating a `jump to` drop down. - As user scrolls through a long list of diffs, there should be a sticky heading which remains visible showing the filename of the diff being viewed. -#### Mock-ups coming soon ## Drawbacks @@ -43,9 +45,7 @@ _(note: The following is a summary of what we would like the UX to achieve, but ## Rationale and alternatives -- Why is this approach the best in the space of possible approaches? -- What other approaches have been considered and what is the rationale for not choosing them? -- What is the impact of not doing this? +An alternative would be to _not_ implement multi-file diff, as other editors like VS Code also only has per-file diff at the time of writing. However, not implementing this would imply that [the proposed new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) will have to find another solution to display all changes in a PR. ## Unresolved questions From 3d35e6a72f15bfc220653f4f06fa6316591c5406 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 22 Oct 2018 19:33:00 +0200 Subject: [PATCH 0571/4053] wordsoup --- docs/rfcs/004-multi-file-diff.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 82bb6d2b65..eedb9e46fc 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -6,11 +6,11 @@ Proposed ## Summary -Users can select multiple files in unstaged changes and staged changes pane and see the diffs of selected files in one view, akin to the [`Files changed` tab in pull requests on github.com](https://github.com/atom/github/pull/1752/files). +Users can select multiple files in unstaged changes and staged changes pane and see the diffs of selected files in one view, akin to the [`Files changed` tab in pull requests on github.com](https://github.com/atom/github/pull/1753/files). ## Motivation -So that users can view a set of changes with more context before staging or committing those changes. +So that users can view a full set of changes with more context before staging or committing those changes. The ability to display multiple diffs in one view will also serve as a building block for the following planned features: - [commit pane item](#1655) where it shows all changes in a single commit @@ -45,7 +45,7 @@ _(note: The following is a summary of what we would like the UX to achieve, but ## Rationale and alternatives -An alternative would be to _not_ implement multi-file diff, as other editors like VS Code also only has per-file diff at the time of writing. However, not implementing this would imply that [the proposed new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) will have to find another solution to display all changes in a PR. +An alternative would be to _not_ implement multi-file diff, as other editors like VS Code also only has per-file diff at the time of writing. However, not implementing this would imply that [the proposed new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) will have to find another solution to display all changes in a PR. ## Unresolved questions From 310b417ccd09bc2fc7463ab1569d5f5683fc8eed Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 22 Oct 2018 11:37:32 -0700 Subject: [PATCH 0572/4053] testing --- docs/rfcs/004-multi-file-diff.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index eedb9e46fc..9f85cc923d 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -21,6 +21,7 @@ The ability to display multiple diffs in one view will also serve as a building #### Mock-ups coming soon + #### Unstaged Changes pane - User can `cmd+click` and select multiple files from the list of unstaged changes, and the pane on the left (see multi-file diff section below) will show diffs of the selected files. That pane will continue to reflect any further selecting/unselecting on the Unstaged Changes pane. - Once there is at least one file selected, `Stage All` button should be worded as `Stage Selected`. From cf066089042bb27f6b94f63bc8d051208575af54 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 22 Oct 2018 11:44:59 -0700 Subject: [PATCH 0573/4053] Prepare 0.21.0 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f42fef462..ede587d9d8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.21.0-0", + "version": "0.21.0", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From 519678d1ad676869a506c04cf76099dc96f539fd Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 22 Oct 2018 14:01:12 -0700 Subject: [PATCH 0574/4053] Revert "Feature: Add commit template support" --- lib/get-repo-pipeline-manager.js | 3 +- lib/git-shell-out-strategy.js | 27 -------------- lib/models/repository-states/present.js | 14 -------- lib/models/repository-states/state.js | 4 --- lib/models/repository.js | 1 - test/controllers/commit-controller.test.js | 33 ------------------ test/fixtures/repo-commit-template/a.txt | 1 - test/fixtures/repo-commit-template/b.txt | 1 - test/fixtures/repo-commit-template/c.txt | 1 - .../repo-commit-template/dot-git/HEAD | 1 - .../repo-commit-template/dot-git/config | 9 ----- .../repo-commit-template/dot-git/description | 1 - .../repo-commit-template/dot-git/index | Bin 634 -> 0 bytes .../repo-commit-template/dot-git/info/exclude | 6 ---- .../repo-commit-template/dot-git/logs/HEAD | 2 -- .../dot-git/logs/refs/heads/master | 2 -- .../25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 | Bin 19 -> 0 bytes .../3f/af8f2e3b6247c9d7e86d3849612cba95e94af4 | Bin 179 -> 0 bytes .../4b/825dc642cb6eb9a060e54bf8d69288fbee4904 | Bin 15 -> 0 bytes .../57/16ca5987cbf97d6bb54920bea6adde242d87e6 | Bin 19 -> 0 bytes .../76/018072e09c5d31c8c6e3113b8aa0fe625195ca | Bin 19 -> 0 bytes .../d4/d73b42443436ee23eebd43174645bb4b9eaf31 | 2 -- .../ea/e629e0574add4da0f67056fd3ec128c18b2c47 | Bin 102 -> 0 bytes .../ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 | Bin 151 -> 0 bytes .../f9/14a98b83588057c160f6a29b8c0a0f54e44548 | Bin 50 -> 0 bytes .../dot-git/refs/heads/master | 1 - .../repo-commit-template/gitmessage.txt | 1 - .../repo-commit-template/subdir-1/a.txt | 1 - .../repo-commit-template/subdir-1/b.txt | 1 - .../repo-commit-template/subdir-1/c.txt | 1 - test/git-strategies.test.js | 26 -------------- test/helpers.js | 10 +----- 32 files changed, 2 insertions(+), 147 deletions(-) delete mode 100644 test/fixtures/repo-commit-template/a.txt delete mode 100644 test/fixtures/repo-commit-template/b.txt delete mode 100644 test/fixtures/repo-commit-template/c.txt delete mode 100644 test/fixtures/repo-commit-template/dot-git/HEAD delete mode 100644 test/fixtures/repo-commit-template/dot-git/config delete mode 100644 test/fixtures/repo-commit-template/dot-git/description delete mode 100644 test/fixtures/repo-commit-template/dot-git/index delete mode 100644 test/fixtures/repo-commit-template/dot-git/info/exclude delete mode 100644 test/fixtures/repo-commit-template/dot-git/logs/HEAD delete mode 100644 test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/3f/af8f2e3b6247c9d7e86d3849612cba95e94af4 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/76/018072e09c5d31c8c6e3113b8aa0fe625195ca delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/d4/d73b42443436ee23eebd43174645bb4b9eaf31 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/ea/e629e0574add4da0f67056fd3ec128c18b2c47 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 delete mode 100644 test/fixtures/repo-commit-template/dot-git/refs/heads/master delete mode 100644 test/fixtures/repo-commit-template/gitmessage.txt delete mode 100644 test/fixtures/repo-commit-template/subdir-1/a.txt delete mode 100644 test/fixtures/repo-commit-template/subdir-1/b.txt delete mode 100644 test/fixtures/repo-commit-template/subdir-1/c.txt diff --git a/lib/get-repo-pipeline-manager.js b/lib/get-repo-pipeline-manager.js index 0e3bddc549..489eea22a1 100644 --- a/lib/get-repo-pipeline-manager.js +++ b/lib/get-repo-pipeline-manager.js @@ -210,8 +210,7 @@ export default function({confirm, notificationManager, workspace}) { commitPipeline.addMiddleware('failed-to-commit-error', async (next, repository) => { try { const result = await next(); - const message = await repository.getCommitMessageFromTemplate(); - repository.setCommitMessage(message || ''); + repository.setCommitMessage(''); destroyFilePatchPaneItems({onlyStaged: true}, workspace); return result; } catch (error) { diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index a1bd858bb3..2b67539a46 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -415,33 +415,6 @@ export default class GitShellOutStrategy { return this.exec(args, {writeOperation: true}); } - async getCommitMessageFromTemplate() { - let templatePath = await this.getConfig('commit.template'); - if (!templatePath) { - return ''; - } - - /** - * Get absolute path from git path - * https://git-scm.com/docs/git-config#git-config-pathname - */ - const homeDir = os.homedir(); - const regex = new RegExp('^~([^/]*)/'); - templatePath = templatePath.trim().replace(regex, (_, user) => { - return `${user ? path.join(path.dirname(homeDir), user) : homeDir}/`; - }); - - if (!path.isAbsolute(templatePath)) { - templatePath = path.join(this.workingDir, templatePath); - } - - if (!await fileExists(templatePath)) { - return ''; - } - const message = await fs.readFile(templatePath, {encoding: 'utf8'}); - return message.trim(); - } - unstageFiles(paths, commit = 'HEAD') { if (paths.length === 0) { return Promise.resolve(null); } const args = ['reset', commit, '--'].concat(paths.map(toGitPathSep)); diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 5c1f7a4a92..4e6d421d73 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -41,7 +41,6 @@ export default class Present extends State { this.operationStates = new OperationStates({didUpdate: this.didUpdate.bind(this)}); this.commitMessage = ''; - this.setCommitMessageFromTemplate(); if (history) { this.discardHistory.updateHistory(history); @@ -56,19 +55,6 @@ export default class Present extends State { return this.commitMessage; } - async setCommitMessageFromTemplate() { - const message = await this.getCommitMessageFromTemplate(); - if (!message) { - return; - } - this.setCommitMessage(message); - this.didUpdate(); - } - - async getCommitMessageFromTemplate() { - return await this.git().getCommitMessageFromTemplate(); - } - getOperationStates() { return this.operationStates; } diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index afdd3c2c59..9c66928e7b 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -373,10 +373,6 @@ export default class State { return ''; } - getCommitMessageFromTemplate() { - return unsupportedOperationPromise(this, 'getCommitMessageFromTemplate'); - } - // Cache getCache() { diff --git a/lib/models/repository.js b/lib/models/repository.js index 0d80e05950..ab50db5f52 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -358,7 +358,6 @@ const delegates = [ 'setCommitMessage', 'getCommitMessage', - 'getCommitMessageFromTemplate', 'getCache', ]; diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 0bcc6679f7..a214d0e081 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -60,9 +60,6 @@ describe('CommitController', function() { const repository1 = await buildRepository(workdirPath1); const workdirPath2 = await cloneRepository('three-files'); const repository2 = await buildRepository(workdirPath2); - const workdirPath3 = await cloneRepository('commit-template'); - const repository3 = await buildRepository(workdirPath3); - const templateCommitMessage = await repository3.git.getCommitMessageFromTemplate(); app = React.cloneElement(app, {repository: repository1}); const wrapper = shallow(app, {disableLifecycleMethods: true}); @@ -77,23 +74,8 @@ describe('CommitController', function() { wrapper.setProps({repository: repository1}); assert.equal(wrapper.instance().getCommitMessage(), 'message 1'); - wrapper.setProps({repository: repository3}); - await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); }); - - describe('when commit.template config is set', function() { - it('populates the commit message with the template', async function() { - const workdirPath = await cloneRepository('commit-template'); - const repository = await buildRepository(workdirPath); - const templateCommitMessage = await repository.git.getCommitMessageFromTemplate(); - app = React.cloneElement(app, {repository}); - const wrapper = shallow(app, {disableLifecycleMethods: true}); - await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); - }); - }); - - describe('the passed commit message', function() { let repository; @@ -158,21 +140,6 @@ describe('CommitController', function() { assert.strictEqual(repository.getCommitMessage(), ''); }); - it('reload the commit messages from commit template', async function() { - const repoPath = await cloneRepository('commit-template'); - const repo = await buildRepositoryWithPipeline(repoPath, {confirm, notificationManager, workspace}); - const templateCommitMessage = await repo.git.getCommitMessageFromTemplate(); - const commitStub = sinon.stub().callsFake((...args) => repo.commit(...args)); - const app2 = React.cloneElement(app, {repository: repo, commit: commitStub}); - - await fs.writeFile(path.join(repoPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); - await repo.git.exec(['add', '.']); - - const wrapper = shallow(app2, {disableLifecycleMethods: true}); - await wrapper.instance().commit('some message'); - assert.strictEqual(repo.getCommitMessage(), templateCommitMessage); - }); - it('sets the verbatim flag when committing from the mini editor', async function() { await fs.writeFile(path.join(workdirPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); await repository.git.exec(['add', '.']); diff --git a/test/fixtures/repo-commit-template/a.txt b/test/fixtures/repo-commit-template/a.txt deleted file mode 100644 index 257cc5642c..0000000000 --- a/test/fixtures/repo-commit-template/a.txt +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/test/fixtures/repo-commit-template/b.txt b/test/fixtures/repo-commit-template/b.txt deleted file mode 100644 index 5716ca5987..0000000000 --- a/test/fixtures/repo-commit-template/b.txt +++ /dev/null @@ -1 +0,0 @@ -bar diff --git a/test/fixtures/repo-commit-template/c.txt b/test/fixtures/repo-commit-template/c.txt deleted file mode 100644 index 76018072e0..0000000000 --- a/test/fixtures/repo-commit-template/c.txt +++ /dev/null @@ -1 +0,0 @@ -baz diff --git a/test/fixtures/repo-commit-template/dot-git/HEAD b/test/fixtures/repo-commit-template/dot-git/HEAD deleted file mode 100644 index cb089cd89a..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/master diff --git a/test/fixtures/repo-commit-template/dot-git/config b/test/fixtures/repo-commit-template/dot-git/config deleted file mode 100644 index 52b216917a..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/config +++ /dev/null @@ -1,9 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = true - bare = false - logallrefupdates = true - ignorecase = true - precomposeunicode = true -[commit] - template = gitmessage.txt diff --git a/test/fixtures/repo-commit-template/dot-git/description b/test/fixtures/repo-commit-template/dot-git/description deleted file mode 100644 index 498b267a8c..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/fixtures/repo-commit-template/dot-git/index b/test/fixtures/repo-commit-template/dot-git/index deleted file mode 100644 index 064b3f3adc4f9d0fc3c482a70cba225911936212..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 634 zcmZ?q402{*U|<4b_UO&Q%Y@^<9)i(~3=Av`wqHva7#f!_Ffe`vsu2NV7S)=gDLNY$ zgnZ~ZVXr&IF6^(}zL^ZHiFzd!K&3zc)}O7U2&19ql%ksxE_N!i{q)b;?5&;(`|E#?bbH1q(We#6@W=U>padBdLD$GETdqm}?yoJ$FcdbP?4{JE_6_+NZ zWESZf>cayJY>t-jH5d&wZymaMSi=ip9z5W{=9vBa45Ojutw%QxYq%lIg9l(pkgF>& zTCEt&6%4uFC-e%>Jbb|T{O6xvmd?Lk_3(4+6quJ7j1>&HUOm%%5bkx?cfq#;V9GeC eaj;v*ecSuJ?D7jr_FNVZiJvO}^4Q|L0U7{%+uH#E diff --git a/test/fixtures/repo-commit-template/dot-git/info/exclude b/test/fixtures/repo-commit-template/dot-git/info/exclude deleted file mode 100644 index a5196d1be8..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/test/fixtures/repo-commit-template/dot-git/logs/HEAD b/test/fixtures/repo-commit-template/dot-git/logs/HEAD deleted file mode 100644 index 678e774168..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/logs/HEAD +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 d4d73b42443436ee23eebd43174645bb4b9eaf31 Gaurav Chikhale 1538479109 +0530 commit (initial): Initial commit -d4d73b42443436ee23eebd43174645bb4b9eaf31 3faf8f2e3b6247c9d7e86d3849612cba95e94af4 Gaurav Chikhale 1538479155 +0530 commit: Add git message diff --git a/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master b/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master deleted file mode 100644 index 678e774168..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 d4d73b42443436ee23eebd43174645bb4b9eaf31 Gaurav Chikhale 1538479109 +0530 commit (initial): Initial commit -d4d73b42443436ee23eebd43174645bb4b9eaf31 3faf8f2e3b6247c9d7e86d3849612cba95e94af4 Gaurav Chikhale 1538479155 +0530 commit: Add git message diff --git a/test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 b/test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 deleted file mode 100644 index bdcf704c9e663f3a11b3146b1b455bc2581b4761..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19 acmb^)#C@5-_ugq9xq9V=;Nnfiq+2m1Fl`G#EQFf(CW zmZ?J54cP;pr6gc%1 I09Vl}{eDC-*#H0l diff --git a/test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 b/test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 deleted file mode 100644 index a90cc1ccb50a8c244093ae8c44b4903ad0394260..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmV;I0BHYs0V^p=O;s>7H)Aj~FfcPQQApG)sVHGktvQ;avvEPlhn^Gmx>M}J{@U%E z3005;RuC?BDzg3b&)V#*o(lVxt-YtB+x`ryAQ`NnjIp8U!JJsb6UQD4T6Zn@mlQbl z6jVWaW=U>padBdLDo&Lq20)-tT$+@US)^;o@amc7gK)3Az6-t;0G)DB<6yUrI{@-l FOLuJZM@Ikv diff --git a/test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 b/test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 deleted file mode 100644 index 6e37521f9b2536eebd27c18ce54b0f62bd26e992..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50 zcmV-20L}k+0V^p=O;s>9WiT-S0)^tzq?F7eT| Date: Mon, 22 Oct 2018 15:14:52 -0700 Subject: [PATCH 0575/4053] State that we don't need to do QA for changes to tests or metrics --- docs/how-we-work.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-we-work.md b/docs/how-we-work.md index 54b763f54a..45b8dba9bc 100644 --- a/docs/how-we-work.md +++ b/docs/how-we-work.md @@ -126,7 +126,7 @@ At the end of each development sprint: * :boom: _If the build fails,_ correct any bugs and begin again at (1) with a new prerelease version. 4. Run `apm uninstall github` and `apm uninstall --dev github` to ensure that you don't have any [locally installed atom/github versions](/CONTRIBUTING.md#living-on-the-edge) that would override the bundled one. 5. _In your atom/atom repository:_ Push your branch to atom/atom and open a pull request to start running CI. -6. Create a [QA issue](https://github.com/atom/github/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Aquality) in the atom/github repository. Its title should be "_prerelease version_ QA Review" and it should have the "quality" label applied. Populate the issue body with a checklist containing the pull requests that were included in this release; these should be the ones in the "Merged" column of the project board. Omit pull requests that don't have verification steps (like renames, refactoring, or dependency upgrades, for example). Add a final entry for a clean CI check on the atom/atom pull request. +6. Create a [QA issue](https://github.com/atom/github/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Aquality) in the atom/github repository. Its title should be "_prerelease version_ QA Review" and it should have the "quality" label applied. Populate the issue body with a checklist containing the pull requests that were included in this release; these should be the ones in the "Merged" column of the project board. Omit pull requests that don't have verification steps (like renames, refactoring, adding tests or metrics, or dependency upgrades, for example). Add a final entry for a clean CI check on the atom/atom pull request. 7. Use your `atom-dev` build to verify each and check it off the list. * :boom: _If verification fails,_ note the failure in an issue comment. Close the issue. Correct the failure with more work in the current sprint board, then begin again at (1). * :white_check_mark: _Otherwise,_ comment in and close the issue, then continue. From 864f52a1dcac3119655242cf2cd9fcd3db68f4a7 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 23 Oct 2018 00:23:42 +0000 Subject: [PATCH 0576/4053] fix(package): update event-kit to version 2.5.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ede587d9d8..84d7750bcd 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "classnames": "2.2.6", "compare-sets": "1.0.1", "dugite": "^1.78.0", - "event-kit": "2.5.1", + "event-kit": "2.5.2", "fs-extra": "4.0.3", "graphql": "0.13.2", "keytar": "4.2.1", From f311be970512fc00ddb04c890df0bdde3e91f8a5 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 23 Oct 2018 00:23:46 +0000 Subject: [PATCH 0577/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10c7967d5e..4d8a036b99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.20.0", + "version": "0.21.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3185,9 +3185,9 @@ } }, "event-kit": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.5.1.tgz", - "integrity": "sha512-frzENdbgPE8VQwBhMXWC8U7/qs80HpENLp4/QA8dhltAhQUuBJVgRn9HOjTF9BVpqqTvx3pScK9rm3oRBooTFA==" + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.5.2.tgz", + "integrity": "sha512-1w3eEk45CstP8gzQtJdxhNl6kmvT+3dsGMK31VX7Wmt1/hlwS+s2yJY7SeVRhyhhx2W8neomdBfSZ9ACJ9eNeg==" }, "execa": { "version": "0.7.0", From 2dc76202d2a1a336bc3147a6a63ce5a65d4b3505 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 22 Oct 2018 19:51:03 -0700 Subject: [PATCH 0578/4053] Add drawbacks to only having single-file diffs --- docs/rfcs/004-multi-file-diff.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 9f85cc923d..8fcd5d2b2f 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -46,7 +46,7 @@ _(note: The following is a summary of what we would like the UX to achieve, but ## Rationale and alternatives -An alternative would be to _not_ implement multi-file diff, as other editors like VS Code also only has per-file diff at the time of writing. However, not implementing this would imply that [the proposed new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) will have to find another solution to display all changes in a PR. +An alternative would be to _not_ implement multi-file diff, as other editors like VS Code also only has per-file diff at the time of writing. However, not implementing this would imply that [the proposed new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) will have to find another solution to display all changes in a PR. Additionally users would have to do a lot more clicking to view all of their changes. Imagine there was a variable rename and only 10 lines are changed, but they are each in a different file. It'd be a bit of a pain to click through to view each one. Also, if we didn't implement multi-file diffs then we couldn't show commit contents since they often include changes across multiple files. ## Unresolved questions From 896a95b83759962c7c7172a2754f0a69de726c6b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 22 Oct 2018 19:56:26 -0700 Subject: [PATCH 0579/4053] Add collapsable file diffs to out-of-scope section --- docs/rfcs/004-multi-file-diff.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 8fcd5d2b2f..1fc4078781 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -37,6 +37,8 @@ _(note: The following is a summary of what we would like the UX to achieve, but - Each diff should show up as its own block, and the current functionality should remain independent of each block. - It should be easy to jump quickly to a specific file you care about, or back to the file list to get to another file. Dotcom does so by creating a `jump to` drop down. - As user scrolls through a long list of diffs, there should be a sticky heading which remains visible showing the filename of the diff being viewed. +Nice-to-have UX that doesn't necessarily need to be implemented +- Each file diff can be collapsed (this can potentially be ) ## Drawbacks @@ -56,7 +58,7 @@ TBD - What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? -TBD +It would be cool if each diff was collapsable. Especially for when we start using the multi-file diff for code review and the reviewers may want to hide the contents of a file once they're done addressing the changes in it. "Collapse/Expand All" capabilities would be nice as well. I don't see this as a critical feature for this particular RFC. ## Implementation phases TBD From 21dff7609ed3d61852017b857d3c43c4e3838d98 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 22 Oct 2018 19:58:20 -0700 Subject: [PATCH 0580/4053] Add unresolved question of how to construct multi-file diff --- docs/rfcs/004-multi-file-diff.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 1fc4078781..7a963635ae 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -54,11 +54,11 @@ An alternative would be to _not_ implement multi-file diff, as other editors lik - What unresolved questions do you expect to resolve through the implementation of this feature before it is released in a new version of the package? -TBD +How exactly do we construct the multi-file diffs? Do we have one TextEditor component that has different sections for each file. Or do we create a new type of pane item that contains multiple TextEditor components stacked on top of one another, one for each file diff... If we do the former we could probably get something shipped sooner (we could just get the diff of the staged changes from Git, add a special decoration for file headers, and present all the changes in one editor). But to pave the way for a more complex code review UX I think taking extra time to do the latter will serve us well. For example, I can imagine reviewers wanting to collapse some files, or mark them as "Done", in which case it would be easier if we treated each diff as its own component. - What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? -It would be cool if each diff was collapsable. Especially for when we start using the multi-file diff for code review and the reviewers may want to hide the contents of a file once they're done addressing the changes in it. "Collapse/Expand All" capabilities would be nice as well. I don't see this as a critical feature for this particular RFC. +It would be cool if each diff was collapsable. Especially for when we start using the multi-file diff for code review and the reviewers may want to hide the contents of a file once they're done addressing the changes in it. "Collapse/Expand All" capabilities would be nice as well. I don't see this as a critical feature for this particular RFC. ## Implementation phases TBD From 7f8fe683757003a0b4ee358d42f606eb8faeb691 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 22 Oct 2018 20:18:48 -0700 Subject: [PATCH 0581/4053] Prepare 0.22.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10c7967d5e..5e16f975d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.20.0", + "version": "0.22.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ede587d9d8..1219e79f38 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.21.0", + "version": "0.22.0", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From a21fec9e7a9d99e50fb45de17eb72684badc0d43 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 22 Oct 2018 21:48:18 -0700 Subject: [PATCH 0582/4053] Add that community members need only fill out the first few sections --- docs/how-we-work.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/how-we-work.md b/docs/how-we-work.md index 5d8917697f..e79ced1a1d 100644 --- a/docs/how-we-work.md +++ b/docs/how-we-work.md @@ -82,6 +82,8 @@ Development work on the feature may start at any point once the RFC pull request The RFC is meant to be a living document that will be modified over the duration of development as things evolve, new information is discovered, and UXR is conducted. +_We encourage community members wanting to contribute new features to follow this process._ This will help our team collaborate with you and give us an opportunity to provide valuable feedback that could inform your development process. You can run your idea by us by simply filling out the first three sections of the RFC template (summary, motivation, and explanation). Feel free to leave the rest blank -- more info would be welcome but is not necessary. + ##### Process 1. On a feature branch, write a proposal as a markdown document beneath [`docs/rfcs`](/docs/rfcs) in this repository. Copy the [template](/docs/rfcs/000-template.md) to begin. Open a pull request. The RFC document should include: @@ -100,6 +102,12 @@ The RFC is meant to be a living document that will be modified over the duration * As the design evolves and opinions change, modify the existing RFC to stay accurate. 1. When the feature is complete, update the RFC to a "completed" state and merge it. For any outstanding work that didn't get implemented, open issues or start new RFCs. +##### FAQ + +Q: Why not just use issues? +In the past we've used issues for these discussions and the issue body quickly becomes outdated by discussion in comments and it's hard to + + ### Expansions or retractions of package scope As a team, we maintain a [shared understanding](/docs/vision) of what we will and will not build as part of this package, which we use to guide our decisions about accepting new features. Like everything else, this understanding is itself fluid. From e49076c06e7bfa435e53d6ee2506db33c138e37d Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 22 Oct 2018 23:27:10 -0700 Subject: [PATCH 0583/4053] Prepare 0.22.1 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5e16f975d6..be9d333a4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.22.0", + "version": "0.22.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1219e79f38..204de7ee06 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.22.0", + "version": "0.22.1", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From 04ffaee660ffd0affe062e7bb6a6060bd1911485 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 23 Oct 2018 08:54:51 -0400 Subject: [PATCH 0584/4053] :fire: dot only --- test/integration/file-patch.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index eb84ed9130..1f145aa17d 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -5,7 +5,7 @@ import until from 'test-until'; import {setup, teardown} from './helpers'; import GitShellOutStrategy from '../../lib/git-shell-out-strategy'; -describe.only('integration: file patches', function() { +describe('integration: file patches', function() { let context, wrapper, atomEnv; let workspace; let commands, workspaceElement; From 3f0a5953819ad74023c434600d456cc73023afa9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 23 Oct 2018 09:01:13 -0400 Subject: [PATCH 0585/4053] 0.22.1-0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 622ad09574..22e168a4f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.22.1", + "version": "0.22.1-0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 67ec395635..3165e887bb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.22.1", + "version": "0.22.1-0", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From 8aabc93a36d20dfb47d6ef1d72c7ffb79c46c104 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 23 Oct 2018 14:38:27 -0400 Subject: [PATCH 0586/4053] Multi-line file repo had unstaged changes that were never used --- .../repo-multi-line-file/dot-git/COMMIT_EDITMSG | 14 ++++++++++++++ .../fixtures/repo-multi-line-file/dot-git/index | Bin 137 -> 137 bytes .../repo-multi-line-file/dot-git/logs/HEAD | 2 ++ .../dot-git/logs/refs/heads/master | 2 ++ .../19/667a5767adb6bedf5727edcabad32d9acf6971 | Bin 0 -> 821 bytes .../37/8bd015ac86c912494cf40ad63fdcd1c56701a3 | Bin 0 -> 54 bytes .../4b/6321ac2475fe40a0d7cee2a51521dace462b4a | Bin 0 -> 821 bytes .../e8/7ccc0178bfca803e5035b6f5b1fdf33580ad78 | Bin 0 -> 231 bytes .../dot-git/refs/heads/master | 2 +- 9 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/repo-multi-line-file/dot-git/objects/19/667a5767adb6bedf5727edcabad32d9acf6971 create mode 100644 test/fixtures/repo-multi-line-file/dot-git/objects/37/8bd015ac86c912494cf40ad63fdcd1c56701a3 create mode 100644 test/fixtures/repo-multi-line-file/dot-git/objects/4b/6321ac2475fe40a0d7cee2a51521dace462b4a create mode 100644 test/fixtures/repo-multi-line-file/dot-git/objects/e8/7ccc0178bfca803e5035b6f5b1fdf33580ad78 diff --git a/test/fixtures/repo-multi-line-file/dot-git/COMMIT_EDITMSG b/test/fixtures/repo-multi-line-file/dot-git/COMMIT_EDITMSG index 9823e78d64..ff91329b89 100644 --- a/test/fixtures/repo-multi-line-file/dot-git/COMMIT_EDITMSG +++ b/test/fixtures/repo-multi-line-file/dot-git/COMMIT_EDITMSG @@ -1 +1,15 @@ Initial Commit + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# +# Author: Antonio Scandurra +# Date: Tue Jun 21 17:27:26 2016 +0200 +# +# On branch master +# +# Initial commit +# +# Changes to be committed: +# new file: sample.js +# diff --git a/test/fixtures/repo-multi-line-file/dot-git/index b/test/fixtures/repo-multi-line-file/dot-git/index index 4198cd52f6e4e4c001c5ba737b8b7d6c155df90b..3c6f0e6a817d03b6e66bc3df6956bff64d8454f4 100644 GIT binary patch literal 137 zcmZ?q402{*U|<4b#%Q_y!lJj|PKD8o3=AwR=|Ybg7#f!VrN08zhydw@FKW&(R_s63 zU>9Jz?d!(BpG_OqRxofDC*~I9r0QiAGlT@Wx&qZoG8iftaG7^s5M9%DQpnTi3)eOK gI~R|pGcFF&yeE+N{mo6KIPuhVPgc#=K7VEk0LH^IbN~PV literal 137 zcmZ?q402{*U|<4b#_-I9EFjGQqZt_(SQw@~31(nuTmqE-3X~E7(lf)dp4IM 1466522846 +0200 commit (initial): Initial Commit ac394ed46f1703bb854619d5b30b69b0eac9c680 8b007bbc61755815fc091906f27e3e0cfe942788 Antonio Scandurra 1466523772 +0200 commit (amend): Initial Commit +8b007bbc61755815fc091906f27e3e0cfe942788 19667a5767adb6bedf5727edcabad32d9acf6971 Ash Wilson 1540304624 -0400 commit (amend): Initial Commit +19667a5767adb6bedf5727edcabad32d9acf6971 4b6321ac2475fe40a0d7cee2a51521dace462b4a Ash Wilson 1540304634 -0400 commit (amend): Initial Commit diff --git a/test/fixtures/repo-multi-line-file/dot-git/logs/refs/heads/master b/test/fixtures/repo-multi-line-file/dot-git/logs/refs/heads/master index 25f4046f2a..d560db5b7f 100644 --- a/test/fixtures/repo-multi-line-file/dot-git/logs/refs/heads/master +++ b/test/fixtures/repo-multi-line-file/dot-git/logs/refs/heads/master @@ -1,2 +1,4 @@ 0000000000000000000000000000000000000000 ac394ed46f1703bb854619d5b30b69b0eac9c680 Antonio Scandurra 1466522846 +0200 commit (initial): Initial Commit ac394ed46f1703bb854619d5b30b69b0eac9c680 8b007bbc61755815fc091906f27e3e0cfe942788 Antonio Scandurra 1466523772 +0200 commit (amend): Initial Commit +8b007bbc61755815fc091906f27e3e0cfe942788 19667a5767adb6bedf5727edcabad32d9acf6971 Ash Wilson 1540304624 -0400 commit (amend): Initial Commit +19667a5767adb6bedf5727edcabad32d9acf6971 4b6321ac2475fe40a0d7cee2a51521dace462b4a Ash Wilson 1540304634 -0400 commit (amend): Initial Commit diff --git a/test/fixtures/repo-multi-line-file/dot-git/objects/19/667a5767adb6bedf5727edcabad32d9acf6971 b/test/fixtures/repo-multi-line-file/dot-git/objects/19/667a5767adb6bedf5727edcabad32d9acf6971 new file mode 100644 index 0000000000000000000000000000000000000000..70496f91ace1f9b26a178a12443c66bd41e1394e GIT binary patch literal 821 zcmV-51Iqk(0c}&suB%oM%$lz_d+Wt;n5FlkFdDeF?`1TwV=`cdW5Z$m`ZzbrB2s$M zQkPWSttwT9x~|56A{qD_9*P36G@Vg=%JLk=3Zj?@T*1G4LUIBPi{zg049#T4ok%8J zp#exeMtzNdnz~`34Z)tvUW1B;Oq1ST=R65Jm1m2 zaRLyaw#{9ZBw3N9T(33l>aK>ZELCXXjTUI8OpP2b9_(GDOYQu1o-dM8Huk?b0eW}n z!%C9++L>!=CO_}99o(7XP)$QHVQ=&{=>%b*o~$U`Q0aB{at+i8_cZzBQ5^;Rkm z=xWSB?RE>dJ(6)$d1TO7md2)2X&gTSNpBl$h#1LsP5b+5wb%~!{DOP}u)Kb=3bseH zZFyd%`~E)W2hyL^%NlIs0&hyXQ}Dx9>|vfOz1}xMASPTHSp_ z=}R@evXTu=2~`Uw=|P_lADI^yrF-5b3$Aq}UottKk#bIL;Y0xId@oTlh*;-Zd2O5V zY7gx}W*m zF1(&neAO?TrF{lR)7TAq%n)0!4;?vVix|zjO||s#R`Tnnq^rFtv|wp&;XFG3{ed_L zAD7r(@q^hAXRsSzBgxS})PFkRR$CLOA%jr=XaT1|a literal 0 HcmV?d00001 diff --git a/test/fixtures/repo-multi-line-file/dot-git/objects/37/8bd015ac86c912494cf40ad63fdcd1c56701a3 b/test/fixtures/repo-multi-line-file/dot-git/objects/37/8bd015ac86c912494cf40ad63fdcd1c56701a3 new file mode 100644 index 0000000000000000000000000000000000000000..84937ce28dee7e1e07f14ec79c7d0ed0210b92fa GIT binary patch literal 54 zcmV-60LlM&0V^p=O;s>9XD~D{Ff%bxC{D~R$Vt`9DrR_5bB3{E|EUJM0Ml(>H~#%> M+OW0)0ARZkvJrz8OaK4? literal 0 HcmV?d00001 diff --git a/test/fixtures/repo-multi-line-file/dot-git/objects/4b/6321ac2475fe40a0d7cee2a51521dace462b4a b/test/fixtures/repo-multi-line-file/dot-git/objects/4b/6321ac2475fe40a0d7cee2a51521dace462b4a new file mode 100644 index 0000000000000000000000000000000000000000..40fe14c2f03eb85b035889bcec3e9c072f2ec6b4 GIT binary patch literal 821 zcmV-51Iqk(0c}&suB%oM%$lz_d-P%sv!(Z<0A?`0W-yas2Z!lk1~b?ge|?-AWf3X8 z>*}s9snmsruEPs}6iffMOho}0LCkUraw!sdw1+giXHm&gY0ficj&g`NUZ7yg5b0y7 zhY3jiGW2)=9!mTCF-na)2D#(!3LsUO+zXz2a`kmY%frbU(qBt=sc@j*YjOYu1{ z*C4{}JoMn_+@*89emDQ6b1cPBEYGmu2gQEq)xDZ=1%CWpvZ@~~;OGwUj=ClJ!A1SX z2|xhs9^bMg$%-W98dg=ydl{~>)>m`c7?EMC%*dD3r+aVEr1t52Jy%JoZtlN00X`E{ ztUJ;vrb7@*qlyQLzrT5>A`^eMYDUFdnS8ZR2nCL71#Zb3Wl|p49*{h~F#)tMVcM+y z6!LSxo7w%Q4A;-K6GNjpAPpUc9lijNKU;f!5fbOm7c5@8A@1vu`H9Yws73&sX=d_TAV%Slf&PpQ*IsW=tC}DC~B%`Ifr%3>4W(I|!!Dnyn z;O{xPxRV-w?Coc}$>+YOF*S{47xcS0yO<|hyFi^okUb7{8q>=_fHIe%jkkI@7NXe* z`Mn^qtwxRHTt>PS8QHiL{u5R3JiwEYzOq7?NH=YJDe z93!#*ZmQ*@5#h9idz(}EO6tX| zMtnw1lYYtrSQH%MA*>mj?cHa<&g;8vA6Ltq0Y)OMRJ0 zKei+7RPUy0JB6Wp6v{i)^(Wo literal 0 HcmV?d00001 diff --git a/test/fixtures/repo-multi-line-file/dot-git/objects/e8/7ccc0178bfca803e5035b6f5b1fdf33580ad78 b/test/fixtures/repo-multi-line-file/dot-git/objects/e8/7ccc0178bfca803e5035b6f5b1fdf33580ad78 new file mode 100644 index 0000000000000000000000000000000000000000..046b95ca27fd190acba9a73966438debb3c0ee7b GIT binary patch literal 231 zcmVNDWT-w<73jtY(zcx zyza@k6JFMvC*f=$d`2xkg7btuhG+$8XBo%$3aA51Op`o^IsoVatbDXa+L07?d#n=( zFDZJ9f_1nhHNZ&1q%SU8RZ>uTAjTVr=)JVmq?sYZ$M*(akE-6gLjO|Q$c5bj&yOf6 zZxvapSN1Qwn?t-7vv!I>v6;%Tt+xMvt3oX?7t32=IV^9}nnX{mH2ZlKyfolE({J4> hoqx%1-uKtGpC)rZ!X4&Ii5R+qaAE)e literal 0 HcmV?d00001 diff --git a/test/fixtures/repo-multi-line-file/dot-git/refs/heads/master b/test/fixtures/repo-multi-line-file/dot-git/refs/heads/master index 1d731ba5d3..e63fc8d2d6 100644 --- a/test/fixtures/repo-multi-line-file/dot-git/refs/heads/master +++ b/test/fixtures/repo-multi-line-file/dot-git/refs/heads/master @@ -1 +1 @@ -8b007bbc61755815fc091906f27e3e0cfe942788 +4b6321ac2475fe40a0d7cee2a51521dace462b4a From f109d08c039331c290f04abe96d30741005affa1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 23 Oct 2018 14:42:05 -0400 Subject: [PATCH 0587/4053] Remove unused Mocha test argument to integration helper --- test/integration/checkout-pr.test.js | 2 +- test/integration/helpers.js | 4 ++-- test/integration/launch.test.js | 14 +++++++------- test/integration/open.test.js | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/integration/checkout-pr.test.js b/test/integration/checkout-pr.test.js index 9bd6229948..158b9f1d94 100644 --- a/test/integration/checkout-pr.test.js +++ b/test/integration/checkout-pr.test.js @@ -12,7 +12,7 @@ describe('check out a pull request', function() { let context, wrapper, atomEnv, workspaceElement, git, idGen, repositoryID; beforeEach(async function() { - context = await setup(this.currentTest, { + context = await setup({ initialRoots: ['three-files'], }); wrapper = context.wrapper; diff --git a/test/integration/helpers.js b/test/integration/helpers.js index 7add61d254..2c458ea46f 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -13,7 +13,7 @@ import metadata from '../../package.json'; * Usage: * ```js * beforeEach(async function() { - * context = await setup(this.currentTest); + * context = await setup(); * wrapper = context.wrapper; * }) * @@ -31,7 +31,7 @@ import metadata from '../../package.json'; * is defined. * * state - Simulate package state serialized by a previous Atom run. */ -export async function setup(currentTest, options = {}) { +export async function setup(options = {}) { const opts = { initialRoots: [], isolateConfigDir: options.initAtomEnv !== undefined, diff --git a/test/integration/launch.test.js b/test/integration/launch.test.js index 6f0f18c6c7..0425fc2aab 100644 --- a/test/integration/launch.test.js +++ b/test/integration/launch.test.js @@ -13,7 +13,7 @@ describe('Package initialization', function() { }); it('reveals the tabs on first run when the welcome package has been dismissed', async function() { - context = await setup(this.currentTest, { + context = await setup({ initConfigDir: configDirPath => fs.remove(path.join(configDirPath, 'github.cson')), initAtomEnv: env => env.config.set('welcome.showOnStartup', false), }); @@ -28,7 +28,7 @@ describe('Package initialization', function() { }); it('renders but does not reveal the tabs on first run when the welcome package has not been dismissed', async function() { - context = await setup(this.currentTest, { + context = await setup({ initConfigDir: configDirPath => fs.remove(path.join(configDirPath, 'github.cson')), initAtomEnv: env => env.config.set('welcome.showOnStartup', true), }); @@ -43,7 +43,7 @@ describe('Package initialization', function() { }); it('renders but does not reveal the tabs on new projects after the first run', async function() { - context = await setup(this.currentTest, { + context = await setup({ initConfigDir: configDirPath => fs.writeFile(path.join(configDirPath, 'github.cson'), '#', {encoding: 'utf8'}), state: {}, }); @@ -63,7 +63,7 @@ describe('Package initialization', function() { state: {newProject: false}, }; - const prevContext = await setup(this.currentTest, nonFirstRun); + const prevContext = await setup(nonFirstRun); const prevWorkspace = prevContext.atomEnv.workspace; await prevWorkspace.open(GitHubTabItem.buildURI(), {searchAllPanes: true}); @@ -73,7 +73,7 @@ describe('Package initialization', function() { await teardown(prevContext); - context = await setup(this.currentTest, nonFirstRun); + context = await setup(nonFirstRun); await context.atomEnv.deserialize(prevState); const paneItemURIs = context.atomEnv.workspace.getPaneItems().map(i => i.getURI()); @@ -88,7 +88,7 @@ describe('Package initialization', function() { const getPaneItemURIs = ctx => ctx.atomEnv.workspace.getPaneItems().map(i => i.getURI()); - const prevContext = await setup(this.currentTest, { + const prevContext = await setup({ initConfigDir: configDirPath => fs.writeFile(path.join(configDirPath, 'github.cson'), '#', {encoding: 'utf8'}), state: {firstRun: true}, }); @@ -106,7 +106,7 @@ describe('Package initialization', function() { const prevState = prevContext.atomEnv.serialize(); await teardown(prevContext); - context = await setup(this.currentTest, { + context = await setup({ initConfigDir: configDirPath => fs.writeFile(path.join(configDirPath, 'github.cson'), '#', {encoding: 'utf8'}), state: {firstRun: false}, }); diff --git a/test/integration/open.test.js b/test/integration/open.test.js index d573e0b8f3..e2e9e4e703 100644 --- a/test/integration/open.test.js +++ b/test/integration/open.test.js @@ -3,11 +3,11 @@ import path from 'path'; import {setup, teardown} from './helpers'; -describe('opening and closing tabs', function() { +describe('integration: opening and closing tabs', function() { let context, wrapper, atomEnv, commands, workspaceElement; beforeEach(async function() { - context = await setup(this.currentTest, { + context = await setup({ initialRoots: ['three-files'], initConfigDir: configDirPath => fs.writeFile(path.join(configDirPath, 'github.cson'), ''), state: {newProject: false}, From 4d3ab87d15987d027b980e10847caa100b7594da Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 23 Oct 2018 16:06:05 -0400 Subject: [PATCH 0588/4053] Integration tests for operations on removed files --- test/integration/file-patch.test.js | 119 +++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 11 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 1f145aa17d..14a1357d0e 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -11,9 +11,13 @@ describe('integration: file patches', function() { let commands, workspaceElement; let repoRoot, git; - beforeEach(async function() { - context = await setup(this.currentTest, { - initialRoots: ['three-files'], + afterEach(async function() { + await teardown(context); + }); + + async function useFixture(fixtureName) { + context = await setup({ + initialRoots: [fixtureName], }); wrapper = context.wrapper; @@ -29,11 +33,7 @@ describe('integration: file patches', function() { // Open the git tab await commands.dispatch(workspaceElement, 'github:toggle-git-tab-focus'); wrapper.update(); - }); - - afterEach(async function() { - await teardown(context); - }); + } function repoPath(...parts) { return path.join(repoRoot, ...parts); @@ -184,6 +184,7 @@ describe('integration: file patches', function() { describe('with an added file', function() { beforeEach(async function() { + await useFixture('three-files'); await fs.writeFile(repoPath('added-file.txt'), '0000\n0001\n0002\n0003\n0004\n0005\n', {encoding: 'utf8'}); await clickFileInGitTab('unstaged', 'added-file.txt'); }); @@ -293,12 +294,108 @@ describe('integration: file patches', function() { }); describe('with a removed file', function() { + beforeEach(async function() { + await useFixture('multi-line-file'); + + await fs.remove(repoPath('sample.js')); + }); + describe('unstaged', function() { - it('may be partially staged'); + beforeEach(async function() { + await clickFileInGitTab('unstaged', 'sample.js'); + }); - it('may be completely staged'); + it('may be partially staged', async function() { + getPatchEditor('unstaged', 'sample.js').setSelectedBufferRange([[4, 0], [7, 5]]); + wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); - it('may discard lines'); + await patchContent( + 'unstaged', 'sample.js', + ['const quicksort = function() {', 'deleted'], + [' const sort = function(items) {', 'deleted'], + [' if (items.length <= 1) { return items; }', 'deleted'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted'], + [' return sort(left).concat(pivot).concat(sort(right));', 'deleted', 'selected'], + [' };', 'deleted'], + ['', 'deleted'], + [' return sort(Array.apply(this, arguments));', 'deleted'], + ['};', 'deleted'], + ); + + await clickFileInGitTab('staged', 'sample.js'); + + await patchContent( + 'staged', 'sample.js', + [' const sort = function(items) {'], + [' if (items.length <= 1) { return items; }'], + [' let pivot = items.shift(), current, left = [], right = [];'], + [' while (items.length > 0) {', 'deleted', 'selected'], + [' current = items.shift();', 'deleted', 'selected'], + [' current < pivot ? left.push(current) : right.push(current);', 'deleted', 'selected'], + [' }', 'deleted', 'selected'], + [' return sort(left).concat(pivot).concat(sort(right));'], + [' };'], + [''], + ); + }); + + it('may be completely staged', async function() { + getPatchEditor('unstaged', 'sample.js').setSelectedBufferRange([[0, 0], [12, 2]]); + wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); + + await clickFileInGitTab('staged', 'sample.js'); + + await patchContent( + 'staged', 'sample.js', + ['const quicksort = function() {', 'deleted', 'selected'], + [' const sort = function(items) {', 'deleted', 'selected'], + [' if (items.length <= 1) { return items; }', 'deleted', 'selected'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted', 'selected'], + [' while (items.length > 0) {', 'deleted', 'selected'], + [' current = items.shift();', 'deleted', 'selected'], + [' current < pivot ? left.push(current) : right.push(current);', 'deleted', 'selected'], + [' }', 'deleted', 'selected'], + [' return sort(left).concat(pivot).concat(sort(right));', 'deleted', 'selected'], + [' };', 'deleted', 'selected'], + ['', 'deleted', 'selected'], + [' return sort(Array.apply(this, arguments));', 'deleted', 'selected'], + ['};', 'deleted', 'selected'], + ); + }); + + it('may discard lines', async function() { + getPatchEditor('unstaged', 'sample.js').setSelectedBufferRanges([ + [[1, 0], [2, 0]], + [[7, 0], [8, 1]], + ]); + wrapper.find('.github-HunkHeaderView-discardButton').simulate('click'); + + await patchContent( + 'unstaged', 'sample.js', + ['const quicksort = function() {', 'deleted', 'selected'], + [' const sort = function(items) {'], + [' if (items.length <= 1) { return items; }'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted', 'selected'], + [' while (items.length > 0) {', 'deleted', 'selected'], + [' current = items.shift();', 'deleted', 'selected'], + [' current < pivot ? left.push(current) : right.push(current);', 'deleted', 'selected'], + [' }'], + [' return sort(left).concat(pivot).concat(sort(right));'], + [' };', 'deleted', 'selected'], + ['', 'deleted', 'selected'], + [' return sort(Array.apply(this, arguments));', 'deleted', 'selected'], + ['};', 'deleted', 'selected'], + ); + + const editor = await workspace.open(repoPath('sample.js')); + assert.strictEqual( + editor.getText(), + ' const sort = function(items) {\n' + + ' if (items.length <= 1) { return items; }\n' + + ' }\n' + + ' return sort(left).concat(pivot).concat(sort(right));\n', + ); + }); }); describe('staged', function() { From 91e75abda7d4b420d60fe05d2c2aea88a5da402f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 23 Oct 2018 16:08:37 -0400 Subject: [PATCH 0589/4053] Restore some missing tests in the FilePatch model --- test/models/patch/file-patch.test.js | 55 ++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 8355fb56e5..41cf72206a 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -604,6 +604,61 @@ describe('FilePatch', function() { ); }); }); + + describe('unstaging lines from a removed file', function() { + let oldFile, removedFilePatch; + + beforeEach(function() { + const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + const layers = buildLayers(buffer); + const hunks = [ + new Hunk({ + oldStartRow: 1, oldRowCount: 0, newStartRow: 1, newRowCount: 3, + marker: markRange(layers.hunk, 0, 2), + regions: [ + new Deletion(markRange(layers.deletion, 0, 2)), + ], + }), + ]; + oldFile = new File({path: 'file.txt', mode: '100644'}); + const removedPatch = new Patch({status: 'deleted', hunks, buffer, layers}); + removedFilePatch = new FilePatch(oldFile, nullFile, removedPatch); + }); + + it('handles unstaging part of the file', function() { + const discardPatch = removedFilePatch.getUnstagePatchForLines(new Set([1])); + assert.strictEqual(discardPatch.getStatus(), 'added'); + assert.strictEqual(discardPatch.getOldFile(), nullFile); + assert.strictEqual(discardPatch.getNewFile(), oldFile); + assertInFilePatch(discardPatch).hunks( + { + startRow: 0, + endRow: 0, + header: '@@ -1,0 +1,1 @@', + regions: [ + {kind: 'addition', string: '+0001\n', range: [[0, 0], [0, 4]]}, + ], + }, + ); + }); + + it('handles unstaging the entire file', function() { + const discardPatch = removedFilePatch.getUnstagePatchForLines(new Set([0, 1, 2])); + assert.strictEqual(discardPatch.getStatus(), 'added'); + assert.strictEqual(discardPatch.getOldFile(), nullFile); + assert.strictEqual(discardPatch.getNewFile(), oldFile); + assertInFilePatch(discardPatch).hunks( + { + startRow: 0, + endRow: 2, + header: '@@ -1,0 +1,3 @@', + regions: [ + {kind: 'addition', string: '+0000\n+0001\n+0002\n', range: [[0, 0], [2, 4]]}, + ], + }, + ); + }); + }); }); it('unstages an entire hunk at once', function() { From d0b6dee3fda06f695508ad90d6deb85022077e4d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 23 Oct 2018 16:08:59 -0400 Subject: [PATCH 0590/4053] Some iteration on setting old|newFile and status in generated patches --- lib/models/patch/file-patch.js | 26 +++++++++++++++++--------- lib/models/patch/patch.js | 4 +++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index d8e7dcb48d..aab728a9e0 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -193,18 +193,26 @@ export default class FilePatch { getUnstagePatchForLines(selectedLineSet) { if (this.patch.getChangedLineCount() === selectedLineSet.size) { - // Ensure that newFile is null if the patch is an addition because we're deleting the entire file from the index. - // If a symlink was deleted and replaced by a non-symlink file, we don't want the symlink entry to muck up the - // patch. - return this.clone({ - oldFile: this.getNewFile(), - newFile: this.getStatus() === 'added' ? nullFile : this.getNewFile(), - patch: this.patch.getFullUnstagedPatch(), - }); + const nonNullFile = this.getNewFile().isPresent() ? this.getNewFile() : this.getOldFile(); + let oldFile = this.getNewFile(); + let newFile = this.getNewFile(); + + if (this.getStatus() === 'added') { + // Ensure that newFile is null if the patch is an addition because we're deleting the entire file from the + // index. If a symlink was deleted and replaced by a non-symlink file, we don't want the symlink entry to muck + // up the patch. + oldFile = nonNullFile; + newFile = nullFile; + } else if (this.getStatus() === 'deleted') { + oldFile = nullFile; + newFile = nonNullFile; + } + + return this.clone({oldFile, newFile, patch: this.patch.getFullUnstagedPatch()}); } else { return this.clone({ oldFile: this.getNewFile(), - newFile: this.getNewFile(), + newFile: this.getNewFile().isPresent() ? this.getNewFile() : this.getOldFile(), patch: this.patch.getUnstagePatchForLines(selectedLineSet), }); } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 7f2afd4b44..eb1f4bb0ed 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -253,6 +253,8 @@ export default class Patch { let status = this.getStatus(); if (this.getStatus() === 'added') { status = wholeFile ? 'deleted' : 'modified'; + } else if (this.getStatus() === 'deleted') { + status = 'added'; } return this.clone({hunks, status, buffer: builder.getBuffer(), layers: builder.getLayers()}); @@ -290,7 +292,7 @@ export default class Patch { newRowDelta += newHunk.getNewRowCount() - newHunk.getOldRowCount(); return newHunk; }); - const status = this.getStatus() === 'added' ? 'deleted' : this.getStatus(); + const status = this.getStatus() === 'added' ? 'deleted' : 'added'; return this.clone({hunks, status, buffer, layers}); } From d23817ccc9057988a60e212a5d6a5c103285f26d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 23 Oct 2018 16:09:33 -0400 Subject: [PATCH 0591/4053] dot only party --- test/integration/file-patch.test.js | 2 +- test/models/patch/file-patch.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 14a1357d0e..ef9c2285a3 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -5,7 +5,7 @@ import until from 'test-until'; import {setup, teardown} from './helpers'; import GitShellOutStrategy from '../../lib/git-shell-out-strategy'; -describe('integration: file patches', function() { +describe.only('integration: file patches', function() { let context, wrapper, atomEnv; let workspace; let commands, workspaceElement; diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 41cf72206a..de2f8bed44 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -489,7 +489,7 @@ describe('FilePatch', function() { ); }); - describe('getUnstagePatchForLines()', function() { + describe.only('getUnstagePatchForLines()', function() { it('returns a new FilePatch that unstages only the specified lines', function() { const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n'}); const layers = buildLayers(buffer); From f5ccd16fecbaaa67cf4853e324b69e4a7a25f3f5 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 23 Oct 2018 14:57:05 -0700 Subject: [PATCH 0592/4053] Revert "Merge pull request #1754 from atom/revert-1713-feature/commit-template" This reverts commit c96687a82186a1bf2bb827d35ec6baf262c62941, reversing changes made to 4bc7fdec69ba63dc1db0f436cbcc645bcbd9671b. --- lib/get-repo-pipeline-manager.js | 3 +- lib/git-shell-out-strategy.js | 27 ++++++++++++++ lib/models/repository-states/present.js | 14 ++++++++ lib/models/repository-states/state.js | 4 +++ lib/models/repository.js | 1 + test/controllers/commit-controller.test.js | 33 ++++++++++++++++++ test/fixtures/repo-commit-template/a.txt | 1 + test/fixtures/repo-commit-template/b.txt | 1 + test/fixtures/repo-commit-template/c.txt | 1 + .../repo-commit-template/dot-git/HEAD | 1 + .../repo-commit-template/dot-git/config | 9 +++++ .../repo-commit-template/dot-git/description | 1 + .../repo-commit-template/dot-git/index | Bin 0 -> 634 bytes .../repo-commit-template/dot-git/info/exclude | 6 ++++ .../repo-commit-template/dot-git/logs/HEAD | 2 ++ .../dot-git/logs/refs/heads/master | 2 ++ .../25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 | Bin 0 -> 19 bytes .../3f/af8f2e3b6247c9d7e86d3849612cba95e94af4 | Bin 0 -> 179 bytes .../4b/825dc642cb6eb9a060e54bf8d69288fbee4904 | Bin 0 -> 15 bytes .../57/16ca5987cbf97d6bb54920bea6adde242d87e6 | Bin 0 -> 19 bytes .../76/018072e09c5d31c8c6e3113b8aa0fe625195ca | Bin 0 -> 19 bytes .../d4/d73b42443436ee23eebd43174645bb4b9eaf31 | 2 ++ .../ea/e629e0574add4da0f67056fd3ec128c18b2c47 | Bin 0 -> 102 bytes .../ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 | Bin 0 -> 151 bytes .../f9/14a98b83588057c160f6a29b8c0a0f54e44548 | Bin 0 -> 50 bytes .../dot-git/refs/heads/master | 1 + .../repo-commit-template/gitmessage.txt | 1 + .../repo-commit-template/subdir-1/a.txt | 1 + .../repo-commit-template/subdir-1/b.txt | 1 + .../repo-commit-template/subdir-1/c.txt | 1 + test/git-strategies.test.js | 26 ++++++++++++++ test/helpers.js | 10 +++++- 32 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/repo-commit-template/a.txt create mode 100644 test/fixtures/repo-commit-template/b.txt create mode 100644 test/fixtures/repo-commit-template/c.txt create mode 100644 test/fixtures/repo-commit-template/dot-git/HEAD create mode 100644 test/fixtures/repo-commit-template/dot-git/config create mode 100644 test/fixtures/repo-commit-template/dot-git/description create mode 100644 test/fixtures/repo-commit-template/dot-git/index create mode 100644 test/fixtures/repo-commit-template/dot-git/info/exclude create mode 100644 test/fixtures/repo-commit-template/dot-git/logs/HEAD create mode 100644 test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/3f/af8f2e3b6247c9d7e86d3849612cba95e94af4 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/76/018072e09c5d31c8c6e3113b8aa0fe625195ca create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/d4/d73b42443436ee23eebd43174645bb4b9eaf31 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/ea/e629e0574add4da0f67056fd3ec128c18b2c47 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 create mode 100644 test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 create mode 100644 test/fixtures/repo-commit-template/dot-git/refs/heads/master create mode 100644 test/fixtures/repo-commit-template/gitmessage.txt create mode 100644 test/fixtures/repo-commit-template/subdir-1/a.txt create mode 100644 test/fixtures/repo-commit-template/subdir-1/b.txt create mode 100644 test/fixtures/repo-commit-template/subdir-1/c.txt diff --git a/lib/get-repo-pipeline-manager.js b/lib/get-repo-pipeline-manager.js index 489eea22a1..0e3bddc549 100644 --- a/lib/get-repo-pipeline-manager.js +++ b/lib/get-repo-pipeline-manager.js @@ -210,7 +210,8 @@ export default function({confirm, notificationManager, workspace}) { commitPipeline.addMiddleware('failed-to-commit-error', async (next, repository) => { try { const result = await next(); - repository.setCommitMessage(''); + const message = await repository.getCommitMessageFromTemplate(); + repository.setCommitMessage(message || ''); destroyFilePatchPaneItems({onlyStaged: true}, workspace); return result; } catch (error) { diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 2b67539a46..a1bd858bb3 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -415,6 +415,33 @@ export default class GitShellOutStrategy { return this.exec(args, {writeOperation: true}); } + async getCommitMessageFromTemplate() { + let templatePath = await this.getConfig('commit.template'); + if (!templatePath) { + return ''; + } + + /** + * Get absolute path from git path + * https://git-scm.com/docs/git-config#git-config-pathname + */ + const homeDir = os.homedir(); + const regex = new RegExp('^~([^/]*)/'); + templatePath = templatePath.trim().replace(regex, (_, user) => { + return `${user ? path.join(path.dirname(homeDir), user) : homeDir}/`; + }); + + if (!path.isAbsolute(templatePath)) { + templatePath = path.join(this.workingDir, templatePath); + } + + if (!await fileExists(templatePath)) { + return ''; + } + const message = await fs.readFile(templatePath, {encoding: 'utf8'}); + return message.trim(); + } + unstageFiles(paths, commit = 'HEAD') { if (paths.length === 0) { return Promise.resolve(null); } const args = ['reset', commit, '--'].concat(paths.map(toGitPathSep)); diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 4e6d421d73..5c1f7a4a92 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -41,6 +41,7 @@ export default class Present extends State { this.operationStates = new OperationStates({didUpdate: this.didUpdate.bind(this)}); this.commitMessage = ''; + this.setCommitMessageFromTemplate(); if (history) { this.discardHistory.updateHistory(history); @@ -55,6 +56,19 @@ export default class Present extends State { return this.commitMessage; } + async setCommitMessageFromTemplate() { + const message = await this.getCommitMessageFromTemplate(); + if (!message) { + return; + } + this.setCommitMessage(message); + this.didUpdate(); + } + + async getCommitMessageFromTemplate() { + return await this.git().getCommitMessageFromTemplate(); + } + getOperationStates() { return this.operationStates; } diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 9c66928e7b..afdd3c2c59 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -373,6 +373,10 @@ export default class State { return ''; } + getCommitMessageFromTemplate() { + return unsupportedOperationPromise(this, 'getCommitMessageFromTemplate'); + } + // Cache getCache() { diff --git a/lib/models/repository.js b/lib/models/repository.js index ab50db5f52..0d80e05950 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -358,6 +358,7 @@ const delegates = [ 'setCommitMessage', 'getCommitMessage', + 'getCommitMessageFromTemplate', 'getCache', ]; diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index a214d0e081..0bcc6679f7 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -60,6 +60,9 @@ describe('CommitController', function() { const repository1 = await buildRepository(workdirPath1); const workdirPath2 = await cloneRepository('three-files'); const repository2 = await buildRepository(workdirPath2); + const workdirPath3 = await cloneRepository('commit-template'); + const repository3 = await buildRepository(workdirPath3); + const templateCommitMessage = await repository3.git.getCommitMessageFromTemplate(); app = React.cloneElement(app, {repository: repository1}); const wrapper = shallow(app, {disableLifecycleMethods: true}); @@ -74,8 +77,23 @@ describe('CommitController', function() { wrapper.setProps({repository: repository1}); assert.equal(wrapper.instance().getCommitMessage(), 'message 1'); + wrapper.setProps({repository: repository3}); + await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); }); + + describe('when commit.template config is set', function() { + it('populates the commit message with the template', async function() { + const workdirPath = await cloneRepository('commit-template'); + const repository = await buildRepository(workdirPath); + const templateCommitMessage = await repository.git.getCommitMessageFromTemplate(); + app = React.cloneElement(app, {repository}); + const wrapper = shallow(app, {disableLifecycleMethods: true}); + await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); + }); + }); + + describe('the passed commit message', function() { let repository; @@ -140,6 +158,21 @@ describe('CommitController', function() { assert.strictEqual(repository.getCommitMessage(), ''); }); + it('reload the commit messages from commit template', async function() { + const repoPath = await cloneRepository('commit-template'); + const repo = await buildRepositoryWithPipeline(repoPath, {confirm, notificationManager, workspace}); + const templateCommitMessage = await repo.git.getCommitMessageFromTemplate(); + const commitStub = sinon.stub().callsFake((...args) => repo.commit(...args)); + const app2 = React.cloneElement(app, {repository: repo, commit: commitStub}); + + await fs.writeFile(path.join(repoPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); + await repo.git.exec(['add', '.']); + + const wrapper = shallow(app2, {disableLifecycleMethods: true}); + await wrapper.instance().commit('some message'); + assert.strictEqual(repo.getCommitMessage(), templateCommitMessage); + }); + it('sets the verbatim flag when committing from the mini editor', async function() { await fs.writeFile(path.join(workdirPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); await repository.git.exec(['add', '.']); diff --git a/test/fixtures/repo-commit-template/a.txt b/test/fixtures/repo-commit-template/a.txt new file mode 100644 index 0000000000..257cc5642c --- /dev/null +++ b/test/fixtures/repo-commit-template/a.txt @@ -0,0 +1 @@ +foo diff --git a/test/fixtures/repo-commit-template/b.txt b/test/fixtures/repo-commit-template/b.txt new file mode 100644 index 0000000000..5716ca5987 --- /dev/null +++ b/test/fixtures/repo-commit-template/b.txt @@ -0,0 +1 @@ +bar diff --git a/test/fixtures/repo-commit-template/c.txt b/test/fixtures/repo-commit-template/c.txt new file mode 100644 index 0000000000..76018072e0 --- /dev/null +++ b/test/fixtures/repo-commit-template/c.txt @@ -0,0 +1 @@ +baz diff --git a/test/fixtures/repo-commit-template/dot-git/HEAD b/test/fixtures/repo-commit-template/dot-git/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/fixtures/repo-commit-template/dot-git/config b/test/fixtures/repo-commit-template/dot-git/config new file mode 100644 index 0000000000..52b216917a --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/config @@ -0,0 +1,9 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[commit] + template = gitmessage.txt diff --git a/test/fixtures/repo-commit-template/dot-git/description b/test/fixtures/repo-commit-template/dot-git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/fixtures/repo-commit-template/dot-git/index b/test/fixtures/repo-commit-template/dot-git/index new file mode 100644 index 0000000000000000000000000000000000000000..064b3f3adc4f9d0fc3c482a70cba225911936212 GIT binary patch literal 634 zcmZ?q402{*U|<4b_UO&Q%Y@^<9)i(~3=Av`wqHva7#f!_Ffe`vsu2NV7S)=gDLNY$ zgnZ~ZVXr&IF6^(}zL^ZHiFzd!K&3zc)}O7U2&19ql%ksxE_N!i{q)b;?5&;(`|E#?bbH1q(We#6@W=U>padBdLD$GETdqm}?yoJ$FcdbP?4{JE_6_+NZ zWESZf>cayJY>t-jH5d&wZymaMSi=ip9z5W{=9vBa45Ojutw%QxYq%lIg9l(pkgF>& zTCEt&6%4uFC-e%>Jbb|T{O6xvmd?Lk_3(4+6quJ7j1>&HUOm%%5bkx?cfq#;V9GeC eaj;v*ecSuJ?D7jr_FNVZiJvO}^4Q|L0U7{%+uH#E literal 0 HcmV?d00001 diff --git a/test/fixtures/repo-commit-template/dot-git/info/exclude b/test/fixtures/repo-commit-template/dot-git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/test/fixtures/repo-commit-template/dot-git/logs/HEAD b/test/fixtures/repo-commit-template/dot-git/logs/HEAD new file mode 100644 index 0000000000..678e774168 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 d4d73b42443436ee23eebd43174645bb4b9eaf31 Gaurav Chikhale 1538479109 +0530 commit (initial): Initial commit +d4d73b42443436ee23eebd43174645bb4b9eaf31 3faf8f2e3b6247c9d7e86d3849612cba95e94af4 Gaurav Chikhale 1538479155 +0530 commit: Add git message diff --git a/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master b/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master new file mode 100644 index 0000000000..678e774168 --- /dev/null +++ b/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 d4d73b42443436ee23eebd43174645bb4b9eaf31 Gaurav Chikhale 1538479109 +0530 commit (initial): Initial commit +d4d73b42443436ee23eebd43174645bb4b9eaf31 3faf8f2e3b6247c9d7e86d3849612cba95e94af4 Gaurav Chikhale 1538479155 +0530 commit: Add git message diff --git a/test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 b/test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 new file mode 100644 index 0000000000000000000000000000000000000000..bdcf704c9e663f3a11b3146b1b455bc2581b4761 GIT binary patch literal 19 acmb^)#C@5-_ugq9xq9V=;Nnfiq+2m1Fl`G#EQFf(CW zmZ?J54cP;pr6gc%1 I09Vl}{eDC-*#H0l literal 0 HcmV?d00001 diff --git a/test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 b/test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 new file mode 100644 index 0000000000000000000000000000000000000000..a90cc1ccb50a8c244093ae8c44b4903ad0394260 GIT binary patch literal 151 zcmV;I0BHYs0V^p=O;s>7H)Aj~FfcPQQApG)sVHGktvQ;avvEPlhn^Gmx>M}J{@U%E z3005;RuC?BDzg3b&)V#*o(lVxt-YtB+x`ryAQ`NnjIp8U!JJsb6UQD4T6Zn@mlQbl z6jVWaW=U>padBdLDo&Lq20)-tT$+@US)^;o@amc7gK)3Az6-t;0G)DB<6yUrI{@-l FOLuJZM@Ikv literal 0 HcmV?d00001 diff --git a/test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 b/test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 new file mode 100644 index 0000000000000000000000000000000000000000..6e37521f9b2536eebd27c18ce54b0f62bd26e992 GIT binary patch literal 50 zcmV-20L}k+0V^p=O;s>9WiT-S0)^tzq?F7eT| Date: Tue, 23 Oct 2018 15:42:11 -0700 Subject: [PATCH 0593/4053] :art: getCommitMessageFromTemplate tests. Return null if config not set Co-Authored-By: Tilde Ann Thurium --- lib/git-shell-out-strategy.js | 5 ++-- test/git-strategies.test.js | 49 +++++++++++++++++++++++------------ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index a1bd858bb3..aaa283a1ad 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -418,7 +418,7 @@ export default class GitShellOutStrategy { async getCommitMessageFromTemplate() { let templatePath = await this.getConfig('commit.template'); if (!templatePath) { - return ''; + return null; } /** @@ -426,6 +426,7 @@ export default class GitShellOutStrategy { * https://git-scm.com/docs/git-config#git-config-pathname */ const homeDir = os.homedir(); + // TODO: figure out what this is doing: const regex = new RegExp('^~([^/]*)/'); templatePath = templatePath.trim().replace(regex, (_, user) => { return `${user ? path.join(path.dirname(homeDir), user) : homeDir}/`; @@ -436,7 +437,7 @@ export default class GitShellOutStrategy { } if (!await fileExists(templatePath)) { - return ''; + throw new Error(`Invalid commit template path set in Git config: ${templatePath}`); } const message = await fs.readFile(templatePath, {encoding: 'utf8'}); return message.trim(); diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index c63fe9164b..c1f310fc95 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -87,29 +87,46 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); describe('getCommitMessageFromTemplate', function() { - it('supports repository root path', async function() { - const workingDirPath = await cloneRepository('commit-template'); + it('gets commit message from template', async function() { + const workingDirPath = await cloneRepository('three-files'); const git = createTestStrategy(workingDirPath); - const absTemplatePath = path.join(workingDirPath, 'gitmessage.txt'); - const commitTemplate = fs.readFileSync(absTemplatePath, 'utf8').trim(); - const message = await git.getCommitMessageFromTemplate(); - assert.equal(message, commitTemplate); + const templateText = 'some commit message'; + + const commitMsgTemplatePath = path.join(workingDirPath, '.gitmessage'); + await fs.writeFile(commitMsgTemplatePath, templateText, {encoding: 'utf8'}); + + await git.setConfig('commit.template', commitMsgTemplatePath); + assert.equal(await git.getCommitMessageFromTemplate(), templateText); }); - it('supports relative path', async function() { - const workingDirPath = await cloneRepository('commit-template'); + it('if config is not set return null', async function() { + const workingDirPath = await cloneRepository('three-files'); const git = createTestStrategy(workingDirPath); - const homeDir = os.homedir(); - const absTemplatePath = path.join(homeDir, '.gitMessageSample.txt'); - await fs.writeFile(absTemplatePath, 'some commit message', {encoding: 'utf8'}); - await git.exec(['config', '--local', 'commit.template', '~/.gitMessageSample.txt']); - const message = await git.getCommitMessageFromTemplate(); - assert.equal(message, 'some commit message'); - fs.removeSync(absTemplatePath); + assert.strictEqual(await git.getConfig('commit.template'), ''); + assert.isNull(await git.getCommitMessageFromTemplate()); }); - }); + it('if config is set but file does not exist throw an error', async function() { + const workingDirPath = await cloneRepository('three-files'); + const git = createTestStrategy(workingDirPath); + + const nonExistentCommitTemplatePath = path.join(workingDirPath, 'file-that-doesnt-exist'); + await git.setConfig('commit.template', nonExistentCommitTemplatePath); + await assert.isRejected( + git.getCommitMessageFromTemplate(), + `Invalid commit template path set in Git config: ${nonExistentCommitTemplatePath}`, + ); + }); + + it('test that ~ gets expanded correctly', async function() { + + }); + + it('test that relative path in repo is covered', async function() { + + }); + }); if (process.platform === 'win32') { describe('getStatusBundle()', function() { From 709ed0a7b609f20fb8cbe275a6cc1ee3439e3c3e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 24 Oct 2018 10:55:06 -0400 Subject: [PATCH 0594/4053] Unify unstage patch generation --- lib/models/patch/file-patch.js | 2 +- lib/models/patch/patch.js | 40 --------------------- test/models/patch/patch.test.js | 63 ++------------------------------- 3 files changed, 3 insertions(+), 102 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index aab728a9e0..53e55f167d 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -208,7 +208,7 @@ export default class FilePatch { newFile = nonNullFile; } - return this.clone({oldFile, newFile, patch: this.patch.getFullUnstagedPatch()}); + return this.clone({oldFile, newFile, patch: this.patch.getUnstagePatchForLines(selectedLineSet)}); } else { return this.clone({ oldFile: this.getNewFile(), diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index eb1f4bb0ed..2a924f85f2 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -260,42 +260,6 @@ export default class Patch { return this.clone({hunks, status, buffer: builder.getBuffer(), layers: builder.getLayers()}); } - getFullUnstagedPatch() { - let newRowDelta = 0; - const buffer = new TextBuffer({text: this.buffer.getText()}); - const layers = { - hunk: buffer.addMarkerLayer(), - unchanged: buffer.addMarkerLayer(), - addition: buffer.addMarkerLayer(), - deletion: buffer.addMarkerLayer(), - noNewline: buffer.addMarkerLayer(), - }; - - const hunks = this.getHunks().map(hunk => { - const regions = hunk.getRegions().map(region => { - const layer = region.when({ - unchanged: () => layers.unchanged, - addition: () => layers.addition, - deletion: () => layers.deletion, - nonewline: () => layers.noNewline, - }); - return region.invertIn(layer); - }); - const newHunk = new Hunk({ - oldStartRow: hunk.getNewStartRow(), - oldRowCount: hunk.getNewRowCount(), - newStartRow: hunk.getNewStartRow() + newRowDelta, - newRowCount: hunk.getOldRowCount(), - marker: layers.hunk.markRange(hunk.getRange()), - regions, - }); - newRowDelta += newHunk.getNewRowCount() - newHunk.getOldRowCount(); - return newHunk; - }); - const status = this.getStatus() === 'added' ? 'deleted' : 'added'; - return this.clone({hunks, status, buffer, layers}); - } - getFirstChangeRange() { const firstHunk = this.getHunks()[0]; if (!firstHunk) { @@ -502,10 +466,6 @@ class NullPatch { return this; } - getFullUnstagedPatch() { - return this; - } - getFirstChangeRange() { return [[0, 0], [0, 0]]; } diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index dc239dee61..0ee6dccbef 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -521,61 +521,6 @@ describe('Patch', function() { }); }); - it('unstages an entire patch at once', function() { - const patch = buildPatchFixture(); - const unstagedPatch = patch.getFullUnstagedPatch(); - - assert.notStrictEqual(unstagedPatch.getBuffer(), patch.getBuffer()); - assert.strictEqual(unstagedPatch.getBuffer().getText(), patch.getBuffer().getText()); - assertInPatch(unstagedPatch).hunks( - { - startRow: 0, - endRow: 6, - header: '@@ -3,5 +3,4 @@', - regions: [ - {kind: 'unchanged', string: ' 0000\n', range: [[0, 0], [0, 4]]}, - {kind: 'addition', string: '+0001\n+0002\n', range: [[1, 0], [2, 4]]}, - {kind: 'deletion', string: '-0003\n-0004\n-0005\n', range: [[3, 0], [5, 4]]}, - {kind: 'unchanged', string: ' 0006\n', range: [[6, 0], [6, 4]]}, - ], - }, - { - startRow: 7, - endRow: 18, - header: '@@ -13,7 +12,9 @@', - regions: [ - {kind: 'unchanged', string: ' 0007\n', range: [[7, 0], [7, 4]]}, - {kind: 'deletion', string: '-0008\n-0009\n', range: [[8, 0], [9, 4]]}, - {kind: 'unchanged', string: ' 0010\n 0011\n', range: [[10, 0], [11, 4]]}, - {kind: 'addition', string: '+0012\n+0013\n+0014\n+0015\n+0016\n', range: [[12, 0], [16, 4]]}, - {kind: 'deletion', string: '-0017\n', range: [[17, 0], [17, 4]]}, - {kind: 'unchanged', string: ' 0018\n', range: [[18, 0], [18, 4]]}, - ], - }, - { - startRow: 19, - endRow: 23, - header: '@@ -25,3 +26,4 @@', - regions: [ - {kind: 'unchanged', string: ' 0019\n', range: [[19, 0], [19, 4]]}, - {kind: 'deletion', string: '-0020\n', range: [[20, 0], [20, 4]]}, - {kind: 'addition', string: '+0021\n+0022\n', range: [[21, 0], [22, 4]]}, - {kind: 'unchanged', string: ' 0023\n', range: [[23, 0], [23, 4]]}, - ], - }, - { - startRow: 24, - endRow: 26, - header: '@@ -30,2 +32,1 @@', - regions: [ - {kind: 'unchanged', string: ' 0024\n', range: [[24, 0], [24, 4]]}, - {kind: 'deletion', string: '-0025\n', range: [[25, 0], [25, 4]]}, - {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[26, 0], [26, 26]]}, - ], - }, - ); - }); - it('returns a modification if original patch is an addition', function() { const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); const layers = buildLayers(buffer); @@ -622,17 +567,13 @@ describe('Patch', function() { ]; const patch = new Patch({status: 'added', hunks, buffer, layers}); - const unstagePatch0 = patch.getUnstagePatchForLines(new Set([0, 1, 2])); - assert.strictEqual(unstagePatch0.getStatus(), 'deleted'); - - const unstagePatch1 = patch.getFullUnstagedPatch(); - assert.strictEqual(unstagePatch1.getStatus(), 'deleted'); + const unstagePatch = patch.getUnstagePatchForLines(new Set([0, 1, 2])); + assert.strictEqual(unstagePatch.getStatus(), 'deleted'); }); it('returns a nullPatch as a nullPatch', function() { const nullPatch = Patch.createNull(); assert.strictEqual(nullPatch.getUnstagePatchForLines(new Set([1, 2, 3])), nullPatch); - assert.strictEqual(nullPatch.getFullUnstagedPatch(), nullPatch); }); }); From 8a8b2fdfed9dc062170df4c64e5ecf23ddc9ac88 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 24 Oct 2018 10:57:50 -0400 Subject: [PATCH 0595/4053] Remove unused method getFirstChangeRange() --- lib/models/patch/file-patch.js | 4 ---- test/models/patch/file-patch.test.js | 2 -- 2 files changed, 6 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 53e55f167d..f0001ade59 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -222,10 +222,6 @@ export default class FilePatch { return this.getUnstagePatchForLines(new Set(hunk.getBufferRows())); } - getFirstChangeRange() { - return this.getPatch().getFirstChangeRange(); - } - getNextSelectionRange(lastFilePatch, lastSelectedRows) { return this.getPatch().getNextSelectionRange(lastFilePatch.getPatch(), lastSelectedRows); } diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index de2f8bed44..4a00080858 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -42,8 +42,6 @@ describe('FilePatch', function() { assert.strictEqual(filePatch.getHunkAt(1), hunks[0]); - assert.deepEqual(filePatch.getFirstChangeRange(), [[1, 0], [1, Infinity]]); - const nBuffer = new TextBuffer({text: '0001\n0002\n'}); const nLayers = buildLayers(nBuffer); const nHunks = [ From b32066b90e8b0968d14b021a04dec3c251eba8ce Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 24 Oct 2018 11:09:07 -0400 Subject: [PATCH 0596/4053] Simplify unstage patch generation --- lib/models/patch/file-patch.js | 39 ++++++++++++---------------- test/integration/file-patch.test.js | 2 +- test/models/patch/file-patch.test.js | 2 +- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index f0001ade59..e43719b0f0 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -192,30 +192,23 @@ export default class FilePatch { } getUnstagePatchForLines(selectedLineSet) { - if (this.patch.getChangedLineCount() === selectedLineSet.size) { - const nonNullFile = this.getNewFile().isPresent() ? this.getNewFile() : this.getOldFile(); - let oldFile = this.getNewFile(); - let newFile = this.getNewFile(); - - if (this.getStatus() === 'added') { - // Ensure that newFile is null if the patch is an addition because we're deleting the entire file from the - // index. If a symlink was deleted and replaced by a non-symlink file, we don't want the symlink entry to muck - // up the patch. - oldFile = nonNullFile; - newFile = nullFile; - } else if (this.getStatus() === 'deleted') { - oldFile = nullFile; - newFile = nonNullFile; - } - - return this.clone({oldFile, newFile, patch: this.patch.getUnstagePatchForLines(selectedLineSet)}); - } else { - return this.clone({ - oldFile: this.getNewFile(), - newFile: this.getNewFile().isPresent() ? this.getNewFile() : this.getOldFile(), - patch: this.patch.getUnstagePatchForLines(selectedLineSet), - }); + const wholeFile = this.patch.getChangedLineCount() === selectedLineSet.size; + const nonNullFile = this.getNewFile().isPresent() ? this.getNewFile() : this.getOldFile(); + let oldFile = this.getNewFile(); + let newFile = nonNullFile; + + if (wholeFile && this.getStatus() === 'added') { + // Ensure that newFile is null if the patch is an addition because we're deleting the entire file from the + // index. If a symlink was deleted and replaced by a non-symlink file, we don't want the symlink entry to muck + // up the patch. + oldFile = nonNullFile; + newFile = nullFile; + } else if (wholeFile && this.getStatus() === 'deleted') { + oldFile = nullFile; + newFile = nonNullFile; } + + return this.clone({oldFile, newFile, patch: this.patch.getUnstagePatchForLines(selectedLineSet)}); } getUnstagePatchForHunk(hunk) { diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index ef9c2285a3..14a1357d0e 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -5,7 +5,7 @@ import until from 'test-until'; import {setup, teardown} from './helpers'; import GitShellOutStrategy from '../../lib/git-shell-out-strategy'; -describe.only('integration: file patches', function() { +describe('integration: file patches', function() { let context, wrapper, atomEnv; let workspace; let commands, workspaceElement; diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 4a00080858..43b7401b7b 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -487,7 +487,7 @@ describe('FilePatch', function() { ); }); - describe.only('getUnstagePatchForLines()', function() { + describe('getUnstagePatchForLines()', function() { it('returns a new FilePatch that unstages only the specified lines', function() { const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n'}); const layers = buildLayers(buffer); From dd7fa4ff7df2835d91544605546f763a9d129549 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 24 Oct 2018 11:13:54 -0400 Subject: [PATCH 0597/4053] Patch is in line selection mode after line discard --- test/integration/file-patch.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 14a1357d0e..5b259b79c0 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -372,19 +372,19 @@ describe('integration: file patches', function() { await patchContent( 'unstaged', 'sample.js', - ['const quicksort = function() {', 'deleted', 'selected'], + ['const quicksort = function() {', 'deleted'], [' const sort = function(items) {'], [' if (items.length <= 1) { return items; }'], - [' let pivot = items.shift(), current, left = [], right = [];', 'deleted', 'selected'], - [' while (items.length > 0) {', 'deleted', 'selected'], - [' current = items.shift();', 'deleted', 'selected'], - [' current < pivot ? left.push(current) : right.push(current);', 'deleted', 'selected'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted'], + [' while (items.length > 0) {', 'deleted'], + [' current = items.shift();', 'deleted'], + [' current < pivot ? left.push(current) : right.push(current);', 'deleted'], [' }'], [' return sort(left).concat(pivot).concat(sort(right));'], [' };', 'deleted', 'selected'], - ['', 'deleted', 'selected'], - [' return sort(Array.apply(this, arguments));', 'deleted', 'selected'], - ['};', 'deleted', 'selected'], + ['', 'deleted'], + [' return sort(Array.apply(this, arguments));', 'deleted'], + ['};', 'deleted'], ); const editor = await workspace.open(repoPath('sample.js')); From 7371346160f213040c708d3c2ea6bd0cf25bc89c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 24 Oct 2018 11:29:29 -0400 Subject: [PATCH 0598/4053] Bump up timeouts for these tests --- test/integration/file-patch.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 5b259b79c0..0aab63563c 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -11,6 +11,13 @@ describe('integration: file patches', function() { let commands, workspaceElement; let repoRoot, git; + this.timeout(Math.max(this.timeout(), 10000)); + + beforeEach(function() { + // These tests take a little longer because they rely on real filesystem events and git operations. + until.setDefaultTimeout(10000); + }); + afterEach(async function() { await teardown(context); }); From b86b01b0384bb8ae48bf810aa7dbb996cf1dd2c2 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 24 Oct 2018 19:14:59 +0000 Subject: [PATCH 0599/4053] chore(package): update dugite to version 1.79.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 204de7ee06..1c1815a3fa 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "bytes": "^3.0.0", "classnames": "2.2.6", "compare-sets": "1.0.1", - "dugite": "^1.78.0", + "dugite": "^1.79.0", "event-kit": "2.5.1", "fs-extra": "4.0.3", "graphql": "0.13.2", From 59b3149aaa88ee65d36a82e59265a9f24dacebc5 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 24 Oct 2018 19:15:02 +0000 Subject: [PATCH 0600/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index be9d333a4a..bf87583146 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2523,9 +2523,9 @@ } }, "dugite": { - "version": "1.78.0", - "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.78.0.tgz", - "integrity": "sha512-t3/aKKLWS/8gmh6r7Ev2Mm+QiM/lFioQHyokz4/ORQJsJgZlKS/vxt+fzUTydzMI/1wSYRAGzVkJJMTcnh1NQQ==", + "version": "1.79.0", + "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.79.0.tgz", + "integrity": "sha512-1iohG+Yj+7wwVNUv+HCWaK5ZeAbqNyxHZf96B65KojBVcvMT29i8Tnh/Ta/KHI7LcI0dQqSqsKJdZozpWjXWKw==", "requires": { "checksum": "^0.1.1", "mkdirp": "^0.5.1", @@ -2555,16 +2555,16 @@ } }, "mime-db": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", - "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" }, "mime-types": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", - "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "~1.36.0" + "mime-db": "~1.37.0" } }, "oauth-sign": { @@ -5221,9 +5221,9 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "minipass": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz", - "integrity": "sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5237,9 +5237,9 @@ } }, "minizlib": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", - "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.1.tgz", + "integrity": "sha512-TrfjCjk4jLhcJyGMYymBH6oTXcWjYbUAXTHDbtnWHjZC25h0cdajHuPE1zxb4DVmu8crfh+HwH/WMuyLG0nHBg==", "requires": { "minipass": "^2.2.1" } From 49a1d31f9ac7f64dc4d71e41c48a9151fcd9e93f Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:12:17 -0700 Subject: [PATCH 0601/4053] Restore state from last commit only if undo is successful Repro steps: open repo with merge conflict click undo see error (cannot do a soft reset in the middle of a merge) expected: no change to commit message box actual: commit message from last commit appears in box --- lib/controllers/git-tab-controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/controllers/git-tab-controller.js b/lib/controllers/git-tab-controller.js index 8744a518ba..cf84a812b4 100644 --- a/lib/controllers/git-tab-controller.js +++ b/lib/controllers/git-tab-controller.js @@ -275,13 +275,15 @@ export default class GitTabController extends React.Component { const repo = this.props.repository; const lastCommit = await repo.getLastCommit(); if (lastCommit.isUnbornRef()) { return null; } + + await repo.undoLastCommit(); repo.setCommitMessage(lastCommit.getFullMessage()); const coAuthors = lastCommit.getCoAuthors().map(author => new Author(author.email, author.name)); this.updateSelectedCoAuthors(coAuthors); - return repo.undoLastCommit(); + return null; } async abortMerge() { From 3ebda2f2924be0752b9ce6fe02059485822dea68 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:17:41 -0700 Subject: [PATCH 0602/4053] Pass events to `observeFilesystemChange` in order to use action property --- lib/models/repository-states/present.js | 7 ++++++- lib/models/workdir-context.js | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 5c1f7a4a92..5c74e5f06c 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -91,7 +91,8 @@ export default class Present extends State { this.didUpdate(); } - observeFilesystemChange(paths) { + invalidateCacheAfterFilesystemChange(events) { + const paths = events.map(e => e.special || e.path); const keys = new Set(); for (let i = 0; i < paths.length; i++) { const fullPath = paths[i]; @@ -159,6 +160,10 @@ export default class Present extends State { } } + observeFilesystemChange(events) { + this.invalidateCacheAfterFilesystemChange(events); + } + refresh() { this.cache.clear(); this.didUpdate(); diff --git a/lib/models/workdir-context.js b/lib/models/workdir-context.js index b7a5ed749b..3c29a405ee 100644 --- a/lib/models/workdir-context.js +++ b/lib/models/workdir-context.js @@ -50,8 +50,7 @@ export default class WorkdirContext { // Wire up event forwarding among models this.subs.add(this.repository.onDidChangeState(this.repositoryChangedState)); this.subs.add(this.observer.onDidChange(events => { - const paths = events.map(e => e.special || e.path); - this.repository.observeFilesystemChange(paths); + this.repository.observeFilesystemChange(events); })); this.subs.add(this.observer.onDidChangeWorkdirOrHead(() => this.emitter.emit('did-change-workdir-or-head'))); From b6ef6d2c3314db494ee76059a6f382d8cf177837 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:21:32 -0700 Subject: [PATCH 0603/4053] Update commit message after file system change for MERGE_HEAD or config --- lib/models/repository-states/present.js | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 5c74e5f06c..03b6637637 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -160,8 +160,49 @@ export default class Present extends State { } } + isCommitMessageClean() { + if (this.commitMessage === '') { + return true; + } else if (this.commitMessageTemplate) { + return this.commitMessage === this.commitMessageTemplate; + } + } + + async updateCommitMessageAfterFileSystemChange(events) { + for (let i = 0; i < events.length; i++) { + const event = events[i]; + const endsWith = (...segments) => event.path.endsWith(path.join(...segments)); + + if (endsWith('.git', 'MERGE_HEAD')) { + if (event.action === 'created') { + if (this.isCommitMessageClean()) { // is it really necessary to check if commit message is clean? + this.setCommitMessage(await this.repository.getMergeMessage()); + // this.didUpdate(); + } + } else if (event.action === 'deleted') { + this.setCommitMessage(this.commitMessageTemplate || ''); + // this.didUpdate(); + } + } + + if (endsWith('.git', 'config')) { + // this won't catch changes made to the template file itself... + + // question -- can filewatcher watch folders that have symlinks to files that change? + // should we create a folder in .atom that symlinks to global config file and commit message template file? to watch for changes and update correctly? + const template = await this.getCommitMessageFromTemplate(); + if (this.commitMessageTemplate !== template) { + this.setCommitMessageTemplate(template); + this.setCommitMessage(template); + // this.didUpdate(); + } + } + } + } + observeFilesystemChange(events) { this.invalidateCacheAfterFilesystemChange(events); + this.updateCommitMessageAfterFileSystemChange(events); } refresh() { From d89569109e3663dc011444ceebdd9fd82cca67a8 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:21:41 -0700 Subject: [PATCH 0604/4053] :shirt: --- lib/models/repository-states/present.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 03b6637637..bf0b1b3bf0 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -166,6 +166,7 @@ export default class Present extends State { } else if (this.commitMessageTemplate) { return this.commitMessage === this.commitMessageTemplate; } + return false; } async updateCommitMessageAfterFileSystemChange(events) { From bcbbff87f2a68e68d27f22ac869476415e04a64e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:25:15 -0700 Subject: [PATCH 0605/4053] Load initial commit message based on template or merge state --- lib/models/repository-states/present.js | 35 +++++++++++++++---------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index bf0b1b3bf0..122af49707 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -41,7 +41,8 @@ export default class Present extends State { this.operationStates = new OperationStates({didUpdate: this.didUpdate.bind(this)}); this.commitMessage = ''; - this.setCommitMessageFromTemplate(); + this.commitMessageTemplate = null; + this.fetchInitialMessage(); if (history) { this.discardHistory.updateHistory(history); @@ -50,23 +51,32 @@ export default class Present extends State { setCommitMessage(message) { this.commitMessage = message; + this.didUpdate(); } - getCommitMessage() { - return this.commitMessage; + setCommitMessageTemplate(template) { + this.commitMessageTemplate = template; } - async setCommitMessageFromTemplate() { - const message = await this.getCommitMessageFromTemplate(); - if (!message) { - return; + async fetchInitialMessage() { + const mergeMessage = await this.repository.getMergeMessage(); + const template = await this.getCommitMessageFromTemplate(); + if (template) { + this.commitMessageTemplate = template; + } + if (mergeMessage) { + this.setCommitMessage(mergeMessage); + } else if (template) { + this.setCommitMessage(template); } - this.setCommitMessage(message); - this.didUpdate(); } - async getCommitMessageFromTemplate() { - return await this.git().getCommitMessageFromTemplate(); + getCommitMessage() { + return this.commitMessage; + } + + getCommitMessageFromTemplate() { + return this.git().getCommitMessageFromTemplate(); } getOperationStates() { @@ -188,9 +198,6 @@ export default class Present extends State { if (endsWith('.git', 'config')) { // this won't catch changes made to the template file itself... - - // question -- can filewatcher watch folders that have symlinks to files that change? - // should we create a folder in .atom that symlinks to global config file and commit message template file? to watch for changes and update correctly? const template = await this.getCommitMessageFromTemplate(); if (this.commitMessageTemplate !== template) { this.setCommitMessageTemplate(template); From b8e55ab6d3271e95a6a537b94d6f93ed99cfa8f6 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:25:48 -0700 Subject: [PATCH 0606/4053] :art: variable message -> template --- lib/get-repo-pipeline-manager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/get-repo-pipeline-manager.js b/lib/get-repo-pipeline-manager.js index 0e3bddc549..ee4df4375a 100644 --- a/lib/get-repo-pipeline-manager.js +++ b/lib/get-repo-pipeline-manager.js @@ -210,8 +210,8 @@ export default function({confirm, notificationManager, workspace}) { commitPipeline.addMiddleware('failed-to-commit-error', async (next, repository) => { try { const result = await next(); - const message = await repository.getCommitMessageFromTemplate(); - repository.setCommitMessage(message || ''); + const template = await repository.getCommitMessageFromTemplate(); + repository.setCommitMessage(template || ''); destroyFilePatchPaneItems({onlyStaged: true}, workspace); return result; } catch (error) { From ca10d1379b5fcf10fa728af02ab16abf4313eb66 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:27:05 -0700 Subject: [PATCH 0607/4053] Don't set merge message in controller, now handled in repository --- lib/controllers/commit-controller.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index 37837c8502..5bba94c5e3 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -27,7 +27,6 @@ export default class CommitController extends React.Component { repository: PropTypes.object.isRequired, isMerging: PropTypes.bool.isRequired, - mergeMessage: PropTypes.string, mergeConflictsExist: PropTypes.bool.isRequired, stagedChangesExist: PropTypes.bool.isRequired, lastCommit: PropTypes.object.isRequired, @@ -73,10 +72,6 @@ export default class CommitController extends React.Component { } }), ); - - if (this.props.isMerging && !this.getCommitMessage()) { - this.setCommitMessage(this.props.mergeMessage || ''); - } } render() { @@ -110,13 +105,6 @@ export default class CommitController extends React.Component { ); } - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps) { - if (!this.props.isMerging && nextProps.isMerging && !this.getCommitMessage()) { - this.setCommitMessage(nextProps.mergeMessage || ''); - } - } - componentWillUnmount() { this.subscriptions.dispose(); } @@ -137,9 +125,7 @@ export default class CommitController extends React.Component { setCommitMessage(message) { if (!this.props.repository.isPresent()) { return; } - const changed = this.props.repository.getCommitMessage() !== message; this.props.repository.setCommitMessage(message); - if (changed) { this.forceUpdate(); } } getCommitMessage() { From 99fe5420dd4e0982a7d8deb62cf6a330097c163c Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:27:48 -0700 Subject: [PATCH 0608/4053] Fix repo test now that we are passing events and not paths --- test/models/repository.test.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/models/repository.test.js b/test/models/repository.test.js index f72833b17e..65c61642a2 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -1850,10 +1850,9 @@ describe('Repository', function() { return new Promise((resolve, reject) => { eventCallback = () => { const matchingPaths = observedEvents - .map(event => event.path) - .filter(eventPath => { + .filter(event => { for (const suffix of pending) { - if (eventPath.endsWith(suffix)) { + if (event.path.endsWith(suffix)) { pending.delete(suffix); return true; } From 40b015e27067dfaef449d9c0c0992bff3ad3df1b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:28:23 -0700 Subject: [PATCH 0609/4053] WIP tests for CommitController --- test/controllers/commit-controller.test.js | 74 ++++++++++++---------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 0bcc6679f7..0c42487b53 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -55,14 +55,47 @@ describe('CommitController', function() { atomEnvironment.destroy(); }); + describe('when commit.template config is set', function() { + it('populates the commit message with the template', async function() { + const workdirPath = await cloneRepository('commit-template'); + const repository = await buildRepository(workdirPath); + const templateCommitMessage = await repository.git.getCommitMessageFromTemplate(); + app = React.cloneElement(app, {repository}); + const wrapper = shallow(app, {disableLifecycleMethods: true}); + await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); + }); + + it('restores template after committig', async function() { + const templateText = 'some commit message'; + const commitMsgTemplatePath = path.join(workdirPath, '.gitmessage'); + await fs.writeFile(commitMsgTemplatePath, templateText, {encoding: 'utf8'}); + await repository.git.setConfig('commit.template', commitMsgTemplatePath); + + await fs.writeFile(path.join(workdirPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); + await repository.git.exec(['add', '.']); + + const wrapper = shallow(app, {disableLifecycleMethods: true}); + await wrapper.instance().commit('some message'); + assert.strictEqual(repository.getCommitMessage(), templateText); + }); + }); + it('correctly updates state when switching repos', async function() { const workdirPath1 = await cloneRepository('three-files'); const repository1 = await buildRepository(workdirPath1); const workdirPath2 = await cloneRepository('three-files'); const repository2 = await buildRepository(workdirPath2); - const workdirPath3 = await cloneRepository('commit-template'); - const repository3 = await buildRepository(workdirPath3); - const templateCommitMessage = await repository3.git.getCommitMessageFromTemplate(); + + // set commit template for repository2 + const templateText = 'some commit message'; + const commitMsgTemplatePath = path.join(workdirPath2, '.gitmessage'); + await fs.writeFile(commitMsgTemplatePath, templateText, {encoding: 'utf8'}); + await repository2.git.setConfig('commit.template', commitMsgTemplatePath); + // assert.strictEqual(await repository2.getCommitMessageFromTemplate(), templateText); + + // const workdirPath3 = await cloneRepository('commit-template'); + // const repository3 = await buildRepository(workdirPath3); + // const templateCommitMessage = await repository3.git.getCommitMessageFromTemplate(); app = React.cloneElement(app, {repository: repository1}); const wrapper = shallow(app, {disableLifecycleMethods: true}); @@ -70,30 +103,18 @@ describe('CommitController', function() { assert.strictEqual(wrapper.instance().getCommitMessage(), ''); wrapper.instance().setCommitMessage('message 1'); + assert.equal(wrapper.instance().getCommitMessage(), 'message 1'); wrapper.setProps({repository: repository2}); - - assert.strictEqual(wrapper.instance().getCommitMessage(), ''); + await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateText); wrapper.setProps({repository: repository1}); assert.equal(wrapper.instance().getCommitMessage(), 'message 1'); - wrapper.setProps({repository: repository3}); - await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); - }); - - describe('when commit.template config is set', function() { - it('populates the commit message with the template', async function() { - const workdirPath = await cloneRepository('commit-template'); - const repository = await buildRepository(workdirPath); - const templateCommitMessage = await repository.git.getCommitMessageFromTemplate(); - app = React.cloneElement(app, {repository}); - const wrapper = shallow(app, {disableLifecycleMethods: true}); - await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); - }); + // wrapper.setProps({repository: repository3}); + // await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); }); - describe('the passed commit message', function() { let repository; @@ -158,21 +179,6 @@ describe('CommitController', function() { assert.strictEqual(repository.getCommitMessage(), ''); }); - it('reload the commit messages from commit template', async function() { - const repoPath = await cloneRepository('commit-template'); - const repo = await buildRepositoryWithPipeline(repoPath, {confirm, notificationManager, workspace}); - const templateCommitMessage = await repo.git.getCommitMessageFromTemplate(); - const commitStub = sinon.stub().callsFake((...args) => repo.commit(...args)); - const app2 = React.cloneElement(app, {repository: repo, commit: commitStub}); - - await fs.writeFile(path.join(repoPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); - await repo.git.exec(['add', '.']); - - const wrapper = shallow(app2, {disableLifecycleMethods: true}); - await wrapper.instance().commit('some message'); - assert.strictEqual(repo.getCommitMessage(), templateCommitMessage); - }); - it('sets the verbatim flag when committing from the mini editor', async function() { await fs.writeFile(path.join(workdirPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); await repository.git.exec(['add', '.']); From ade7de86d60bc5199988fc986ce30c6995c788da Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 12:13:22 -0700 Subject: [PATCH 0610/4053] Suppress repo update when typing commit message See https://github.com/atom/github/pull/1464 (for performance) --- lib/controllers/commit-controller.js | 6 +++--- lib/models/repository-states/present.js | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index 5bba94c5e3..7c44257da7 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -123,9 +123,9 @@ export default class CommitController extends React.Component { return this.props.commit(msg.trim(), {amend, coAuthors, verbatim}); } - setCommitMessage(message) { + setCommitMessage(message, options) { if (!this.props.repository.isPresent()) { return; } - this.props.repository.setCommitMessage(message); + this.props.repository.setCommitMessage(message, options); } getCommitMessage() { @@ -140,7 +140,7 @@ export default class CommitController extends React.Component { if (!this.props.repository.isPresent()) { return; } - this.setCommitMessage(newMessage); + this.setCommitMessage(newMessage, {suppressUpdate: true}); } getCommitMessageEditors() { diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 122af49707..168584f980 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -49,9 +49,11 @@ export default class Present extends State { } } - setCommitMessage(message) { + setCommitMessage(message, {suppressUpdate} = {suppressUpdate: false}) { this.commitMessage = message; - this.didUpdate(); + if (!suppressUpdate) { + this.didUpdate(); + } } setCommitMessageTemplate(template) { From 7c18ddb0a91729b3069416014d6df6844d8f699e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:35:34 -0700 Subject: [PATCH 0611/4053] Don't trim commit message template It's best to assume that template users craft their template exactly as they want it. For example, I have two empty lines at the beginning of mine so that I can quickly type my message. --- lib/git-shell-out-strategy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index aaa283a1ad..da0a6b554a 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -440,7 +440,7 @@ export default class GitShellOutStrategy { throw new Error(`Invalid commit template path set in Git config: ${templatePath}`); } const message = await fs.readFile(templatePath, {encoding: 'utf8'}); - return message.trim(); + return message; } unstageFiles(paths, commit = 'HEAD') { From 7a731381874003c3511d095aa0b6718be0b950b7 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:39:05 -0700 Subject: [PATCH 0612/4053] :art: rename getCommitMessageFromTemplate to getCommitMessageTemplate --- lib/get-repo-pipeline-manager.js | 2 +- lib/git-shell-out-strategy.js | 6 +++--- lib/models/repository-states/present.js | 8 ++++---- lib/models/repository-states/state.js | 4 ++-- lib/models/repository.js | 2 +- test/controllers/commit-controller.test.js | 6 +++--- test/git-strategies.test.js | 8 ++++---- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/get-repo-pipeline-manager.js b/lib/get-repo-pipeline-manager.js index ee4df4375a..b153920ddc 100644 --- a/lib/get-repo-pipeline-manager.js +++ b/lib/get-repo-pipeline-manager.js @@ -210,7 +210,7 @@ export default function({confirm, notificationManager, workspace}) { commitPipeline.addMiddleware('failed-to-commit-error', async (next, repository) => { try { const result = await next(); - const template = await repository.getCommitMessageFromTemplate(); + const template = await repository.getCommitMessageTemplate(); repository.setCommitMessage(template || ''); destroyFilePatchPaneItems({onlyStaged: true}, workspace); return result; diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index da0a6b554a..d1399a16fa 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -415,7 +415,7 @@ export default class GitShellOutStrategy { return this.exec(args, {writeOperation: true}); } - async getCommitMessageFromTemplate() { + async getCommitMessageTemplate() { let templatePath = await this.getConfig('commit.template'); if (!templatePath) { return null; @@ -439,8 +439,8 @@ export default class GitShellOutStrategy { if (!await fileExists(templatePath)) { throw new Error(`Invalid commit template path set in Git config: ${templatePath}`); } - const message = await fs.readFile(templatePath, {encoding: 'utf8'}); - return message; + const template = await fs.readFile(templatePath, {encoding: 'utf8'}); + return template; } unstageFiles(paths, commit = 'HEAD') { diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 168584f980..a19b822792 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -62,7 +62,7 @@ export default class Present extends State { async fetchInitialMessage() { const mergeMessage = await this.repository.getMergeMessage(); - const template = await this.getCommitMessageFromTemplate(); + const template = await this.getCommitMessageTemplate(); if (template) { this.commitMessageTemplate = template; } @@ -77,8 +77,8 @@ export default class Present extends State { return this.commitMessage; } - getCommitMessageFromTemplate() { - return this.git().getCommitMessageFromTemplate(); + getCommitMessageTemplate() { + return this.git().getCommitMessageTemplate(); } getOperationStates() { @@ -200,7 +200,7 @@ export default class Present extends State { if (endsWith('.git', 'config')) { // this won't catch changes made to the template file itself... - const template = await this.getCommitMessageFromTemplate(); + const template = await this.getCommitMessageTemplate(); if (this.commitMessageTemplate !== template) { this.setCommitMessageTemplate(template); this.setCommitMessage(template); diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index afdd3c2c59..5e6b7373f1 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -373,8 +373,8 @@ export default class State { return ''; } - getCommitMessageFromTemplate() { - return unsupportedOperationPromise(this, 'getCommitMessageFromTemplate'); + getCommitMessageTemplate() { + return unsupportedOperationPromise(this, 'getCommitMessageTemplate'); } // Cache diff --git a/lib/models/repository.js b/lib/models/repository.js index 0d80e05950..ef54885474 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -358,7 +358,7 @@ const delegates = [ 'setCommitMessage', 'getCommitMessage', - 'getCommitMessageFromTemplate', + 'getCommitMessageTemplate', 'getCache', ]; diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 0c42487b53..225d6d69f2 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -59,7 +59,7 @@ describe('CommitController', function() { it('populates the commit message with the template', async function() { const workdirPath = await cloneRepository('commit-template'); const repository = await buildRepository(workdirPath); - const templateCommitMessage = await repository.git.getCommitMessageFromTemplate(); + const templateCommitMessage = await repository.git.getCommitMessageTemplate(); app = React.cloneElement(app, {repository}); const wrapper = shallow(app, {disableLifecycleMethods: true}); await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); @@ -91,11 +91,11 @@ describe('CommitController', function() { const commitMsgTemplatePath = path.join(workdirPath2, '.gitmessage'); await fs.writeFile(commitMsgTemplatePath, templateText, {encoding: 'utf8'}); await repository2.git.setConfig('commit.template', commitMsgTemplatePath); - // assert.strictEqual(await repository2.getCommitMessageFromTemplate(), templateText); + // assert.strictEqual(await repository2.getCommitMessageTemplate(), templateText); // const workdirPath3 = await cloneRepository('commit-template'); // const repository3 = await buildRepository(workdirPath3); - // const templateCommitMessage = await repository3.git.getCommitMessageFromTemplate(); + // const templateCommitMessage = await repository3.git.getCommitMessageTemplate(); app = React.cloneElement(app, {repository: repository1}); const wrapper = shallow(app, {disableLifecycleMethods: true}); diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index c1f310fc95..4d61ba4b94 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -86,7 +86,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); }); - describe('getCommitMessageFromTemplate', function() { + describe('getCommitMessageTemplate', function() { it('gets commit message from template', async function() { const workingDirPath = await cloneRepository('three-files'); const git = createTestStrategy(workingDirPath); @@ -96,7 +96,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; await fs.writeFile(commitMsgTemplatePath, templateText, {encoding: 'utf8'}); await git.setConfig('commit.template', commitMsgTemplatePath); - assert.equal(await git.getCommitMessageFromTemplate(), templateText); + assert.equal(await git.getCommitMessageTemplate(), templateText); }); it('if config is not set return null', async function() { @@ -104,7 +104,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; const git = createTestStrategy(workingDirPath); assert.strictEqual(await git.getConfig('commit.template'), ''); - assert.isNull(await git.getCommitMessageFromTemplate()); + assert.isNull(await git.getCommitMessageTemplate()); }); it('if config is set but file does not exist throw an error', async function() { @@ -114,7 +114,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; const nonExistentCommitTemplatePath = path.join(workingDirPath, 'file-that-doesnt-exist'); await git.setConfig('commit.template', nonExistentCommitTemplatePath); await assert.isRejected( - git.getCommitMessageFromTemplate(), + git.getCommitMessageTemplate(), `Invalid commit template path set in Git config: ${nonExistentCommitTemplatePath}`, ); }); From b16525c9a21529dcbcdd4eaa069de609cd00427d Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:40:34 -0700 Subject: [PATCH 0613/4053] Enable commit button only if message contains non-comment lines --- lib/views/commit-view.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index f472b60cee..30aa57db6d 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -468,13 +468,16 @@ export default class CommitView extends React.Component { } } + isValidMessage() { + return this.editor && this.editor.getText().replace(/^#.*$/gm, '').trim().length !== 0; + } + commitIsEnabled(amend) { - const messageExists = this.editor && this.editor.getText().length !== 0; return !this.props.isCommitting && (amend || this.props.stagedChangesExist) && !this.props.mergeConflictsExist && this.props.lastCommit.isPresent() && - (this.props.deactivateCommitBox || (amend || messageExists)); + (this.props.deactivateCommitBox || (amend || this.isValidMessage())); } commitButtonText() { From b3cc2c318a67ec4ceab0eb0ac82a4b7197e9b25d Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 13:41:26 -0700 Subject: [PATCH 0614/4053] Set cursor to beginning of commit message template We don't want it to be at the end --- lib/views/commit-view.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index 30aa57db6d..889da4cddc 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -591,6 +591,11 @@ export default class CommitView extends React.Component { if (focus === CommitView.focus.EDITOR) { if (this.refEditor.map(focusElement).getOr(false)) { + if (this.editor && this.editor.getText().length > 0 && !this.isValidMessage()) { + // there is likely a commit message template present + // we want the cursor to be at the beginning, not at the and of the template + this.editor.setCursorBufferPosition([0, 0]); + } return true; } } From 5a7b2d4789da3a25f5ff967e933115ef90db1b6d Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 14:19:38 -0700 Subject: [PATCH 0615/4053] Reset commit message after a successful abort triggered through the UI According to @smashwilson > it may be worth triggering some of that behavior manually if a user aborts (or, eventually, initiates) a merge through our UI. in terms of reliability - on all three platforms there is some risk of dropped events that increases with the number of events that arrive in quick succession (like if a ton of files are created at once). merges in huge repositories _might_ trigger this but it shouldn't be common. Co-Authored-By: Ash Wilson --- lib/models/repository-states/present.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index a19b822792..156cc112b7 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -351,7 +351,10 @@ export default class Present extends State { Keys.filePatch.all, Keys.index.all, ], - () => this.git().abortMerge(), + async () => { + await this.git().abortMerge(); + this.setCommitMessage(this.commitMessageTemplate || ''); + }, ); } From b3588ef93f7116294032269d5a81a98075882d0b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 16:17:32 -0700 Subject: [PATCH 0616/4053] Add repository tests for commit message updating due to file system events --- test/models/repository.test.js | 189 +++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 65c61642a2..329a750485 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -2108,4 +2108,193 @@ describe('Repository', function() { }); }); }); + + describe('updating commit message', function() { + let workdir, sub; + let observedEvents, eventCallback; + + async function wireUpObserver(fixtureName = 'multi-commits-files', existingWorkdir = null) { + observedEvents = []; + eventCallback = () => {}; + + workdir = existingWorkdir || await cloneRepository(fixtureName); + const repository = new Repository(workdir); + await repository.getLoadPromise(); + + const observer = new FileSystemChangeObserver(repository); + + sub = new CompositeDisposable( + new Disposable(async () => { + await observer.destroy(); + repository.destroy(); + }), + ); + + sub.add(observer.onDidChange(events => { + observedEvents.push(...events); + eventCallback(); + })); + + return {repository, observer}; + } + + function expectEvents(repository, ...suffixes) { + const pending = new Set(suffixes); + return new Promise((resolve, reject) => { + eventCallback = () => { + const matchingPaths = observedEvents + .filter(event => { + for (const suffix of pending) { + if (event.path.endsWith(suffix)) { + pending.delete(suffix); + return true; + } + } + return false; + }); + + if (matchingPaths.length > 0) { + repository.observeFilesystemChange(matchingPaths); + } + + if (pending.size === 0) { + resolve(); + } + }; + + if (observedEvents.length > 0) { + eventCallback(); + } + }); + } + + afterEach(function() { + sub && sub.dispose(); + }); + + describe('config commit.template change', function() { + it('updates commit messages to new template', async function() { + const {repository, observer} = await wireUpObserver(); + await observer.start(); + + assert.strictEqual(repository.getCommitMessage(), ''); + + const templatePath = path.join(workdir, 'a.txt'); + await repository.git.setConfig('commit.template', templatePath); + await expectEvents( + repository, + path.join('.git', 'config'), + ); + await assert.async.strictEqual(repository.getCommitMessage(), fs.readFileSync(templatePath, 'utf8')); + }); + }); + + describe('merge events', function() { + describe('when commit message is empty', function() { + it('merge message is set as new commit message', async function() { + const {repository, observer} = await wireUpObserver('merge-conflict'); + await observer.start(); + + assert.strictEqual(repository.getCommitMessage(), ''); + await assert.isRejected(repository.git.merge('origin/branch')); + await expectEvents( + repository, + path.join('.git', 'MERGE_HEAD'), + ); + await assert.async.strictEqual(repository.getCommitMessage(), await repository.getMergeMessage()); + }); + }); + + describe('when commit message contains unmodified template', function() { + it('merge message is set as new commit message', async function() { + const {repository, observer} = await wireUpObserver('merge-conflict'); + await observer.start(); + + const templatePath = path.join(workdir, 'added-to-both.txt'); + const templateText = fs.readFileSync(templatePath, 'utf8'); + await repository.git.setConfig('commit.template', templatePath); + await expectEvents( + repository, + path.join('.git', 'config'), + ); + + await assert.async.strictEqual(repository.getCommitMessage(), templateText); + + await assert.isRejected(repository.git.merge('origin/branch')); + await expectEvents( + repository, + path.join('.git', 'MERGE_HEAD'), + ); + await assert.async.strictEqual(repository.getCommitMessage(), await repository.getMergeMessage()); + }); + }); + + describe('when commit message is "dirty"', function() { + it('leaves commit message as is', async function() { + const {repository, observer} = await wireUpObserver('merge-conflict'); + await observer.start(); + + const dirtyMessage = 'foo bar baz'; + repository.setCommitMessage(dirtyMessage); + await assert.isRejected(repository.git.merge('origin/branch')); + await expectEvents( + repository, + path.join('.git', 'MERGE_HEAD'), + ); + assert.strictEqual(repository.getCommitMessage(), dirtyMessage); + }); + }); + + describe('when merge is aborted', function() { + it('merge message gets cleared', async function() { + const {repository, observer} = await wireUpObserver('merge-conflict'); + await observer.start(); + await assert.isRejected(repository.git.merge('origin/branch')); + await expectEvents( + repository, + path.join('.git', 'MERGE_HEAD'), + ); + await assert.async.strictEqual(repository.getCommitMessage(), await repository.getMergeMessage()); + + await repository.abortMerge(); + await expectEvents( + repository, + path.join('.git', 'MERGE_HEAD'), + ); + assert.strictEqual(repository.getCommitMessage(), ''); + + }); + + describe('when commit message template is present', function() { + it('sets template as commit message', async function() { + const {repository, observer} = await wireUpObserver('merge-conflict'); + await observer.start(); + + const templatePath = path.join(workdir, 'added-to-both.txt'); + const templateText = fs.readFileSync(templatePath, 'utf8'); + await repository.git.setConfig('commit.template', templatePath); + await expectEvents( + repository, + path.join('.git', 'config'), + ); + + await assert.isRejected(repository.git.merge('origin/branch')); + await expectEvents( + repository, + path.join('.git', 'MERGE_HEAD'), + ); + await assert.async.strictEqual(repository.getCommitMessage(), await repository.getMergeMessage()); + + await repository.abortMerge(); + await expectEvents( + repository, + path.join('.git', 'MERGE_HEAD'), + ); + + await assert.async.strictEqual(repository.getCommitMessage(), templateText); + }); + }); + }); + }); + }); }); From 4a1525b7e11c591a8e32f4fb61f8110430f9e0fe Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 16:17:56 -0700 Subject: [PATCH 0617/4053] Trim message before checking if clean --- lib/models/repository-states/present.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 156cc112b7..a574e83138 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -173,7 +173,7 @@ export default class Present extends State { } isCommitMessageClean() { - if (this.commitMessage === '') { + if (this.commitMessage.trim() === '') { return true; } else if (this.commitMessageTemplate) { return this.commitMessage === this.commitMessageTemplate; From 2c2857e916dde4f2639394fa1412ab2afab6a203 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 16:19:38 -0700 Subject: [PATCH 0618/4053] :fire: merge message tests in CommitController (now handled in repo) --- test/controllers/commit-controller.test.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 225d6d69f2..10718f416e 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -40,7 +40,6 @@ describe('CommitController', function() { isMerging={false} mergeConflictsExist={false} stagedChangesExist={false} - mergeMessage={''} lastCommit={lastCommit} currentBranch={nullBranch} userStore={store} @@ -139,21 +138,6 @@ describe('CommitController', function() { assert.strictEqual(wrapper.getCommitMessage(), 'new message'); assert.isFalse(wrapper.props.repository.state.didUpdate.called); }); - - describe('when a merge message is defined', function() { - it('is set to the merge message when merging', function() { - app = React.cloneElement(app, {isMerging: true, mergeMessage: 'merge conflict!'}); - const wrapper = shallow(app, {disableLifecycleMethods: true}); - assert.strictEqual(wrapper.find('CommitView').prop('message'), 'merge conflict!'); - }); - - it('is set to getCommitMessage() if it is set', function() { - repository.setCommitMessage('some commit message'); - app = React.cloneElement(app, {isMerging: true, mergeMessage: 'merge conflict!'}); - const wrapper = shallow(app, {disableLifecycleMethods: true}); - assert.strictEqual(wrapper.find('CommitView').prop('message'), 'some commit message'); - }); - }); }); describe('committing', function() { From f780ad100545389674c4ce0e9f4c7f161e68a346 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 16:22:18 -0700 Subject: [PATCH 0619/4053] Fix WorkdirContext test --- test/models/workdir-context.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/models/workdir-context.test.js b/test/models/workdir-context.test.js index c4d8df2371..a7732e9d09 100644 --- a/test/models/workdir-context.test.js +++ b/test/models/workdir-context.test.js @@ -64,9 +64,10 @@ describe('WorkdirContext', function() { sinon.spy(repo, 'observeFilesystemChange'); - context.getChangeObserver().didChange([{path: path.join('a', 'b', 'c')}]); + const events = [{path: path.join('a', 'b', 'c')}]; + context.getChangeObserver().didChange(events); - assert.isTrue(repo.observeFilesystemChange.calledWith([path.join('a', 'b', 'c')])); + assert.isTrue(repo.observeFilesystemChange.calledWith(events)); }); it('re-emits an event on workdir or head change', async function() { From 5ee83db1dd30b580582ad89c70c4b3988d8f91c8 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 19:06:21 -0700 Subject: [PATCH 0620/4053] Fix CommitController tests Co-Authored-By: Matt --- lib/controllers/commit-controller.js | 3 ++ test/controllers/commit-controller.test.js | 63 ++++++++++------------ 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index 7c44257da7..9239fb7575 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -125,7 +125,10 @@ export default class CommitController extends React.Component { setCommitMessage(message, options) { if (!this.props.repository.isPresent()) { return; } + const changed = this.props.repository.getCommitMessage() !== message; this.props.repository.setCommitMessage(message, options); + // ask @smashwilson -- why do we need to do this?? + if (changed) { this.forceUpdate(); } } getCommitMessage() { diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 10718f416e..c21f3ca67f 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -54,31 +54,6 @@ describe('CommitController', function() { atomEnvironment.destroy(); }); - describe('when commit.template config is set', function() { - it('populates the commit message with the template', async function() { - const workdirPath = await cloneRepository('commit-template'); - const repository = await buildRepository(workdirPath); - const templateCommitMessage = await repository.git.getCommitMessageTemplate(); - app = React.cloneElement(app, {repository}); - const wrapper = shallow(app, {disableLifecycleMethods: true}); - await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); - }); - - it('restores template after committig', async function() { - const templateText = 'some commit message'; - const commitMsgTemplatePath = path.join(workdirPath, '.gitmessage'); - await fs.writeFile(commitMsgTemplatePath, templateText, {encoding: 'utf8'}); - await repository.git.setConfig('commit.template', commitMsgTemplatePath); - - await fs.writeFile(path.join(workdirPath, 'a.txt'), 'some changes', {encoding: 'utf8'}); - await repository.git.exec(['add', '.']); - - const wrapper = shallow(app, {disableLifecycleMethods: true}); - await wrapper.instance().commit('some message'); - assert.strictEqual(repository.getCommitMessage(), templateText); - }); - }); - it('correctly updates state when switching repos', async function() { const workdirPath1 = await cloneRepository('three-files'); const repository1 = await buildRepository(workdirPath1); @@ -86,15 +61,10 @@ describe('CommitController', function() { const repository2 = await buildRepository(workdirPath2); // set commit template for repository2 - const templateText = 'some commit message'; - const commitMsgTemplatePath = path.join(workdirPath2, '.gitmessage'); - await fs.writeFile(commitMsgTemplatePath, templateText, {encoding: 'utf8'}); - await repository2.git.setConfig('commit.template', commitMsgTemplatePath); - // assert.strictEqual(await repository2.getCommitMessageTemplate(), templateText); - - // const workdirPath3 = await cloneRepository('commit-template'); - // const repository3 = await buildRepository(workdirPath3); - // const templateCommitMessage = await repository3.git.getCommitMessageTemplate(); + const templatePath = path.join(workdirPath2, 'a.txt'); + const templateText = fs.readFileSync(templatePath, 'utf8'); + await repository2.git.setConfig('commit.template', templatePath); + await assert.async.strictEqual(repository2.getCommitMessage(), templateText); app = React.cloneElement(app, {repository: repository1}); const wrapper = shallow(app, {disableLifecycleMethods: true}); @@ -106,12 +76,13 @@ describe('CommitController', function() { wrapper.setProps({repository: repository2}); await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateText); + wrapper.instance().setCommitMessage('message 2'); wrapper.setProps({repository: repository1}); assert.equal(wrapper.instance().getCommitMessage(), 'message 1'); - // wrapper.setProps({repository: repository3}); - // await assert.async.strictEqual(wrapper.instance().getCommitMessage(), templateCommitMessage); + wrapper.setProps({repository: repository2}); + assert.equal(wrapper.instance().getCommitMessage(), 'message 2'); }); describe('the passed commit message', function() { @@ -196,6 +167,26 @@ describe('CommitController', function() { assert.strictEqual(repository.getCommitMessage(), 'some message'); }); + it('restores template after committing', async function() { + const templatePath = path.join(workdirPath, 'a.txt'); + const templateText = fs.readFileSync(templatePath, 'utf8'); + await repository.git.setConfig('commit.template', templatePath); + await assert.async.strictEqual(repository.getCommitMessage(), templateText); + + const nonTemplateText = 'some new text...'; + repository.setCommitMessage(nonTemplateText); + + assert.strictEqual(repository.getCommitMessage(), nonTemplateText); + + app = React.cloneElement(app, {repository}); + const wrapper = shallow(app, {disableLifecycleMethods: true}); + await fs.writeFile(path.join(workdirPath, 'new-file.txt'), 'some changes', {encoding: 'utf8'}); + await repository.stageFiles(['new-file.txt']); + await wrapper.instance().commit(nonTemplateText); + + await assert.async.strictEqual(repository.getCommitMessage(), templateText); + }); + describe('message formatting', function() { let commitSpy, wrapper; From 6e45f0cfc7d9c22a21fec5ef849dc99574a54ca9 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 19:08:19 -0700 Subject: [PATCH 0621/4053] :fire: commit-template test fixture --- test/fixtures/repo-commit-template/a.txt | 1 - test/fixtures/repo-commit-template/b.txt | 1 - test/fixtures/repo-commit-template/c.txt | 1 - test/fixtures/repo-commit-template/dot-git/HEAD | 1 - test/fixtures/repo-commit-template/dot-git/config | 9 --------- .../repo-commit-template/dot-git/description | 1 - test/fixtures/repo-commit-template/dot-git/index | Bin 634 -> 0 bytes .../repo-commit-template/dot-git/info/exclude | 6 ------ .../repo-commit-template/dot-git/logs/HEAD | 2 -- .../dot-git/logs/refs/heads/master | 2 -- .../25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 | Bin 19 -> 0 bytes .../3f/af8f2e3b6247c9d7e86d3849612cba95e94af4 | Bin 179 -> 0 bytes .../4b/825dc642cb6eb9a060e54bf8d69288fbee4904 | Bin 15 -> 0 bytes .../57/16ca5987cbf97d6bb54920bea6adde242d87e6 | Bin 19 -> 0 bytes .../76/018072e09c5d31c8c6e3113b8aa0fe625195ca | Bin 19 -> 0 bytes .../d4/d73b42443436ee23eebd43174645bb4b9eaf31 | 2 -- .../ea/e629e0574add4da0f67056fd3ec128c18b2c47 | Bin 102 -> 0 bytes .../ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 | Bin 151 -> 0 bytes .../f9/14a98b83588057c160f6a29b8c0a0f54e44548 | Bin 50 -> 0 bytes .../dot-git/refs/heads/master | 1 - test/fixtures/repo-commit-template/gitmessage.txt | 1 - test/fixtures/repo-commit-template/subdir-1/a.txt | 1 - test/fixtures/repo-commit-template/subdir-1/b.txt | 1 - test/fixtures/repo-commit-template/subdir-1/c.txt | 1 - 24 files changed, 31 deletions(-) delete mode 100644 test/fixtures/repo-commit-template/a.txt delete mode 100644 test/fixtures/repo-commit-template/b.txt delete mode 100644 test/fixtures/repo-commit-template/c.txt delete mode 100644 test/fixtures/repo-commit-template/dot-git/HEAD delete mode 100644 test/fixtures/repo-commit-template/dot-git/config delete mode 100644 test/fixtures/repo-commit-template/dot-git/description delete mode 100644 test/fixtures/repo-commit-template/dot-git/index delete mode 100644 test/fixtures/repo-commit-template/dot-git/info/exclude delete mode 100644 test/fixtures/repo-commit-template/dot-git/logs/HEAD delete mode 100644 test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/3f/af8f2e3b6247c9d7e86d3849612cba95e94af4 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/76/018072e09c5d31c8c6e3113b8aa0fe625195ca delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/d4/d73b42443436ee23eebd43174645bb4b9eaf31 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/ea/e629e0574add4da0f67056fd3ec128c18b2c47 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 delete mode 100644 test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 delete mode 100644 test/fixtures/repo-commit-template/dot-git/refs/heads/master delete mode 100644 test/fixtures/repo-commit-template/gitmessage.txt delete mode 100644 test/fixtures/repo-commit-template/subdir-1/a.txt delete mode 100644 test/fixtures/repo-commit-template/subdir-1/b.txt delete mode 100644 test/fixtures/repo-commit-template/subdir-1/c.txt diff --git a/test/fixtures/repo-commit-template/a.txt b/test/fixtures/repo-commit-template/a.txt deleted file mode 100644 index 257cc5642c..0000000000 --- a/test/fixtures/repo-commit-template/a.txt +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/test/fixtures/repo-commit-template/b.txt b/test/fixtures/repo-commit-template/b.txt deleted file mode 100644 index 5716ca5987..0000000000 --- a/test/fixtures/repo-commit-template/b.txt +++ /dev/null @@ -1 +0,0 @@ -bar diff --git a/test/fixtures/repo-commit-template/c.txt b/test/fixtures/repo-commit-template/c.txt deleted file mode 100644 index 76018072e0..0000000000 --- a/test/fixtures/repo-commit-template/c.txt +++ /dev/null @@ -1 +0,0 @@ -baz diff --git a/test/fixtures/repo-commit-template/dot-git/HEAD b/test/fixtures/repo-commit-template/dot-git/HEAD deleted file mode 100644 index cb089cd89a..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/master diff --git a/test/fixtures/repo-commit-template/dot-git/config b/test/fixtures/repo-commit-template/dot-git/config deleted file mode 100644 index 52b216917a..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/config +++ /dev/null @@ -1,9 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = true - bare = false - logallrefupdates = true - ignorecase = true - precomposeunicode = true -[commit] - template = gitmessage.txt diff --git a/test/fixtures/repo-commit-template/dot-git/description b/test/fixtures/repo-commit-template/dot-git/description deleted file mode 100644 index 498b267a8c..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/fixtures/repo-commit-template/dot-git/index b/test/fixtures/repo-commit-template/dot-git/index deleted file mode 100644 index 064b3f3adc4f9d0fc3c482a70cba225911936212..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 634 zcmZ?q402{*U|<4b_UO&Q%Y@^<9)i(~3=Av`wqHva7#f!_Ffe`vsu2NV7S)=gDLNY$ zgnZ~ZVXr&IF6^(}zL^ZHiFzd!K&3zc)}O7U2&19ql%ksxE_N!i{q)b;?5&;(`|E#?bbH1q(We#6@W=U>padBdLD$GETdqm}?yoJ$FcdbP?4{JE_6_+NZ zWESZf>cayJY>t-jH5d&wZymaMSi=ip9z5W{=9vBa45Ojutw%QxYq%lIg9l(pkgF>& zTCEt&6%4uFC-e%>Jbb|T{O6xvmd?Lk_3(4+6quJ7j1>&HUOm%%5bkx?cfq#;V9GeC eaj;v*ecSuJ?D7jr_FNVZiJvO}^4Q|L0U7{%+uH#E diff --git a/test/fixtures/repo-commit-template/dot-git/info/exclude b/test/fixtures/repo-commit-template/dot-git/info/exclude deleted file mode 100644 index a5196d1be8..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/test/fixtures/repo-commit-template/dot-git/logs/HEAD b/test/fixtures/repo-commit-template/dot-git/logs/HEAD deleted file mode 100644 index 678e774168..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/logs/HEAD +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 d4d73b42443436ee23eebd43174645bb4b9eaf31 Gaurav Chikhale 1538479109 +0530 commit (initial): Initial commit -d4d73b42443436ee23eebd43174645bb4b9eaf31 3faf8f2e3b6247c9d7e86d3849612cba95e94af4 Gaurav Chikhale 1538479155 +0530 commit: Add git message diff --git a/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master b/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master deleted file mode 100644 index 678e774168..0000000000 --- a/test/fixtures/repo-commit-template/dot-git/logs/refs/heads/master +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 d4d73b42443436ee23eebd43174645bb4b9eaf31 Gaurav Chikhale 1538479109 +0530 commit (initial): Initial commit -d4d73b42443436ee23eebd43174645bb4b9eaf31 3faf8f2e3b6247c9d7e86d3849612cba95e94af4 Gaurav Chikhale 1538479155 +0530 commit: Add git message diff --git a/test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 b/test/fixtures/repo-commit-template/dot-git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 deleted file mode 100644 index bdcf704c9e663f3a11b3146b1b455bc2581b4761..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19 acmb^)#C@5-_ugq9xq9V=;Nnfiq+2m1Fl`G#EQFf(CW zmZ?J54cP;pr6gc%1 I09Vl}{eDC-*#H0l diff --git a/test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 b/test/fixtures/repo-commit-template/dot-git/objects/ef/608d1399c3c033cff3f9f4a59fd77ae1f38594 deleted file mode 100644 index a90cc1ccb50a8c244093ae8c44b4903ad0394260..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmV;I0BHYs0V^p=O;s>7H)Aj~FfcPQQApG)sVHGktvQ;avvEPlhn^Gmx>M}J{@U%E z3005;RuC?BDzg3b&)V#*o(lVxt-YtB+x`ryAQ`NnjIp8U!JJsb6UQD4T6Zn@mlQbl z6jVWaW=U>padBdLDo&Lq20)-tT$+@US)^;o@amc7gK)3Az6-t;0G)DB<6yUrI{@-l FOLuJZM@Ikv diff --git a/test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 b/test/fixtures/repo-commit-template/dot-git/objects/f9/14a98b83588057c160f6a29b8c0a0f54e44548 deleted file mode 100644 index 6e37521f9b2536eebd27c18ce54b0f62bd26e992..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50 zcmV-20L}k+0V^p=O;s>9WiT-S0)^tzq?F7eT| Date: Wed, 24 Oct 2018 19:10:51 -0700 Subject: [PATCH 0622/4053] Clean up cloneRepository We don't want to be setting up a commit template for every single test that runs. We do it locally for the tests that need it --- test/helpers.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/test/helpers.js b/test/helpers.js index 1d4c707324..a8fd009e1a 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -42,17 +42,9 @@ export async function cloneRepository(repoName = 'three-files') { if (!cachedClonedRepos[repoName]) { const cachedPath = temp.mkdirSync('git-fixture-cache-'); const git = new GitShellOutStrategy(cachedPath); - const repoPath = path.join(__dirname, 'fixtures', `repo-${repoName}`, 'dot-git'); - - let templatePath = ''; - try { - templatePath = await git.exec(['config', '--file', repoPath + '/config', 'commit.template']); - } catch (err) {} - - await git.clone(repoPath, {noLocal: true}); + await git.clone(path.join(__dirname, 'fixtures', `repo-${repoName}`, 'dot-git'), {noLocal: true}); await git.exec(['config', '--local', 'core.autocrlf', 'false']); await git.exec(['config', '--local', 'commit.gpgsign', 'false']); - await git.exec(['config', '--local', 'commit.template', templatePath]); await git.exec(['config', '--local', 'user.email', FAKE_USER.email]); await git.exec(['config', '--local', 'user.name', FAKE_USER.name]); await git.exec(['config', '--local', 'push.default', 'simple']); From d1450845752233339df1beab1258879f6bc27e8a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 24 Oct 2018 19:30:09 -0700 Subject: [PATCH 0623/4053] :fire: comment Co-Authored-By: Matt --- lib/git-shell-out-strategy.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index d1399a16fa..a7564d354d 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -426,7 +426,6 @@ export default class GitShellOutStrategy { * https://git-scm.com/docs/git-config#git-config-pathname */ const homeDir = os.homedir(); - // TODO: figure out what this is doing: const regex = new RegExp('^~([^/]*)/'); templatePath = templatePath.trim().replace(regex, (_, user) => { return `${user ? path.join(path.dirname(homeDir), user) : homeDir}/`; @@ -439,8 +438,7 @@ export default class GitShellOutStrategy { if (!await fileExists(templatePath)) { throw new Error(`Invalid commit template path set in Git config: ${templatePath}`); } - const template = await fs.readFile(templatePath, {encoding: 'utf8'}); - return template; + return await fs.readFile(templatePath, {encoding: 'utf8'}); } unstageFiles(paths, commit = 'HEAD') { From 75ee9c7002209ab8888ed9a55997945a142cc224 Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 25 Oct 2018 20:45:36 +0900 Subject: [PATCH 0624/4053] Add some mockups --- docs/rfcs/004-multi-file-diff.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 7a963635ae..20d1418f59 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -30,8 +30,9 @@ The ability to display multiple diffs in one view will also serve as a building - Same behavior as Unstaged Changes pane. - Once there is at least one file selected, `Unstage All` button should be worded as `Unstage Selected`. +![staged changes](https://user-images.githubusercontent.com/378023/47497740-0a0b3200-d896-11e8-85af-7c644af9ca37.png) + #### Multi-file diff view -_(note: The following is a summary of what we would like the UX to achieve, but I don't have a clear visuals of what that looks like yet.)_ - Shows diffs of multiple files as a stack. - Each diff should show up as its own block, and the current functionality should remain independent of each block. @@ -40,6 +41,10 @@ _(note: The following is a summary of what we would like the UX to achieve, but Nice-to-have UX that doesn't necessarily need to be implemented - Each file diff can be collapsed (this can potentially be ) +All files collapsed | Some files collapsed +--- | --- +![all collapsed](https://user-images.githubusercontent.com/378023/47497741-0a0b3200-d896-11e8-90b5-4153009f80b4.png) | ![some collapsed](https://user-images.githubusercontent.com/378023/47497742-0aa3c880-d896-11e8-9a20-cd3a722647f1.png) + ## Drawbacks From 7b377d0c14f187dc35ef788bf83c9aeeeb83ac6a Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 25 Oct 2018 20:55:14 +0900 Subject: [PATCH 0625/4053] Update partially collapsed files mockup --- docs/rfcs/004-multi-file-diff.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 20d1418f59..62c30c1729 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -43,7 +43,7 @@ Nice-to-have UX that doesn't necessarily need to be implemented All files collapsed | Some files collapsed --- | --- -![all collapsed](https://user-images.githubusercontent.com/378023/47497741-0a0b3200-d896-11e8-90b5-4153009f80b4.png) | ![some collapsed](https://user-images.githubusercontent.com/378023/47497742-0aa3c880-d896-11e8-9a20-cd3a722647f1.png) +![all collapsed](https://user-images.githubusercontent.com/378023/47497741-0a0b3200-d896-11e8-90b5-4153009f80b4.png) | ![some collapsed](https://user-images.githubusercontent.com/378023/47498408-27410000-d898-11e8-8e4b-c02dafe7e35a.png) ## Drawbacks From 865ca04a95bd7a909e8731dade85dcd6b4586a36 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 10:23:07 -0400 Subject: [PATCH 0626/4053] Timeout tweaks --- test/integration/file-patch.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 0aab63563c..56e18f2e93 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -15,7 +15,7 @@ describe('integration: file patches', function() { beforeEach(function() { // These tests take a little longer because they rely on real filesystem events and git operations. - until.setDefaultTimeout(10000); + until.setDefaultTimeout(9000); }); afterEach(async function() { From 8c1a5b931ed4ed6d712b5414adcfc1e3ecdf5e3d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 10:23:30 -0400 Subject: [PATCH 0627/4053] Separate getPatchItem() helper --- test/integration/file-patch.test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 56e18f2e93..7b892e7043 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -67,11 +67,12 @@ describe('integration: file patches', function() { ); } + function getPatchItem(stagingStatus, relativePath) { + return wrapper.update().find(`FilePatchItem[relPath="${relativePath}"][stagingStatus="${stagingStatus}"]`); + } + function getPatchEditor(stagingStatus, relativePath) { - const component = wrapper - .find(`FilePatchItem[relPath="${relativePath}"][stagingStatus="${stagingStatus}"]`) - .find('.github-FilePatchView') - .find('AtomTextEditor'); + const component = getPatchItem(stagingStatus, relativePath).find('.github-FilePatchView').find('AtomTextEditor'); if (!component.exists()) { return null; From 3fc012cdbb102afd8111e8eaf384f17a49a9f07d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 10:23:56 -0400 Subject: [PATCH 0628/4053] Tests about symlink changes --- test/integration/file-patch.test.js | 241 ++++++++++++++++++++++++++-- 1 file changed, 231 insertions(+), 10 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 7b892e7043..81cbc778a4 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -407,37 +407,258 @@ describe('integration: file patches', function() { }); describe('staged', function() { - it('may be partially unstaged'); + beforeEach(async function() { + await git.stageFiles(['sample.js']); + await clickFileInGitTab('staged', 'sample.js'); + }); - it('may be partially staged'); + it('may be partially unstaged', async function() { + getPatchEditor('staged', 'sample.js').setSelectedBufferRange([[8, 0], [8, 5]]); + wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); + + await patchContent( + 'staged', 'sample.js', + ['const quicksort = function() {', 'deleted'], + [' const sort = function(items) {', 'deleted'], + [' if (items.length <= 1) { return items; }', 'deleted'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted'], + [' while (items.length > 0) {', 'deleted'], + [' current = items.shift();', 'deleted'], + [' current < pivot ? left.push(current) : right.push(current);', 'deleted'], + [' }', 'deleted'], + [' return sort(left).concat(pivot).concat(sort(right));'], + [' };', 'deleted', 'selected'], + ['', 'deleted'], + [' return sort(Array.apply(this, arguments));', 'deleted'], + ['};', 'deleted'], + ); + + await clickFileInGitTab('unstaged', 'sample.js'); + + await patchContent( + 'unstaged', 'sample.js', + [' return sort(left).concat(pivot).concat(sort(right));', 'deleted', 'selected'], + ); + }); + + it('may be completely unstaged', async function() { + getPatchEditor('staged', 'sample.js').selectAll(); + wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); + + await clickFileInGitTab('unstaged', 'sample.js'); + + await patchContent( + 'unstaged', 'sample.js', + ['const quicksort = function() {', 'deleted', 'selected'], + [' const sort = function(items) {', 'deleted', 'selected'], + [' if (items.length <= 1) { return items; }', 'deleted', 'selected'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted', 'selected'], + [' while (items.length > 0) {', 'deleted', 'selected'], + [' current = items.shift();', 'deleted', 'selected'], + [' current < pivot ? left.push(current) : right.push(current);', 'deleted', 'selected'], + [' }', 'deleted', 'selected'], + [' return sort(left).concat(pivot).concat(sort(right));', 'deleted', 'selected'], + [' };', 'deleted', 'selected'], + ['', 'deleted', 'selected'], + [' return sort(Array.apply(this, arguments));', 'deleted', 'selected'], + ['};', 'deleted', 'selected'], + ); + }); }); }); describe('with a symlink that used to be a file', function() { + beforeEach(async function() { + if (process.platform === 'win32') { + this.skip(); + } + + await useFixture('multi-line-file'); + await fs.remove(repoPath('sample.js')); + await fs.writeFile(repoPath('target.txt'), 'something to point the symlink to', {encoding: 'utf8'}); + await fs.symlink(repoPath('target.txt'), repoPath('sample.js')); + }); + describe('unstaged', function() { - it('may stage the content deletion without the symlink creation'); + beforeEach(async function() { + await clickFileInGitTab('unstaged', 'sample.js'); + }); + + it('may stage the content deletion without the symlink creation', async function() { + getPatchEditor('unstaged', 'sample.js').selectAll(); + getPatchItem('unstaged', 'sample.js').find('.github-HunkHeaderView-stageButton').simulate('click'); + + assert.isTrue(getPatchItem('unstaged', 'sample.js').find('.github-FilePatchView-metaTitle').exists()); - it('may stage the content deletion and the symlink creation'); + await clickFileInGitTab('staged', 'sample.js'); + + await patchContent( + 'staged', 'sample.js', + ['const quicksort = function() {', 'deleted', 'selected'], + [' const sort = function(items) {', 'deleted', 'selected'], + [' if (items.length <= 1) { return items; }', 'deleted', 'selected'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted', 'selected'], + [' while (items.length > 0) {', 'deleted', 'selected'], + [' current = items.shift();', 'deleted', 'selected'], + [' current < pivot ? left.push(current) : right.push(current);', 'deleted', 'selected'], + [' }', 'deleted', 'selected'], + [' return sort(left).concat(pivot).concat(sort(right));', 'deleted', 'selected'], + [' };', 'deleted', 'selected'], + ['', 'deleted', 'selected'], + [' return sort(Array.apply(this, arguments));', 'deleted', 'selected'], + ['};', 'deleted', 'selected'], + ); + assert.isFalse(getPatchItem('staged', 'sample.js').find('.github-FilePatchView-metaTitle').exists()); + }); + + it('may stage the content deletion and the symlink creation', async function() { + getPatchItem('unstaged', 'sample.js').find('.github-FilePatchView-metaControls button').simulate('click'); + + await clickFileInGitTab('staged', 'sample.js'); + + await patchContent( + 'staged', 'sample.js', + ['const quicksort = function() {', 'deleted', 'selected'], + [' const sort = function(items) {', 'deleted', 'selected'], + [' if (items.length <= 1) { return items; }', 'deleted', 'selected'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted', 'selected'], + [' while (items.length > 0) {', 'deleted', 'selected'], + [' current = items.shift();', 'deleted', 'selected'], + [' current < pivot ? left.push(current) : right.push(current);', 'deleted', 'selected'], + [' }', 'deleted', 'selected'], + [' return sort(left).concat(pivot).concat(sort(right));', 'deleted', 'selected'], + [' };', 'deleted', 'selected'], + ['', 'deleted', 'selected'], + [' return sort(Array.apply(this, arguments));', 'deleted', 'selected'], + ['};', 'deleted', 'selected'], + ); + assert.isTrue(getPatchItem('staged', 'sample.js').find('.github-FilePatchView-metaTitle').exists()); + }); }); describe('staged', function() { - it('may unstage the content deletion and the symlink creation'); + beforeEach(async function() { + await git.stageFiles(['sample.js']); + await clickFileInGitTab('staged', 'sample.js'); + }); + + it.skip('may unstage the content deletion and the symlink creation', async function() { + getPatchItem('staged', 'sample.js').find('.github-FilePatchView-metaControls button').simulate('click'); + + await clickFileInGitTab('unstaged', 'sample.js'); + + await patchContent( + 'unstaged', 'sample.js', + ['const quicksort = function() {', 'deleted', 'selected'], + [' const sort = function(items) {', 'deleted', 'selected'], + [' if (items.length <= 1) { return items; }', 'deleted', 'selected'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted', 'selected'], + [' while (items.length > 0) {', 'deleted', 'selected'], + [' current = items.shift();', 'deleted', 'selected'], + [' current < pivot ? left.push(current) : right.push(current);', 'deleted', 'selected'], + [' }', 'deleted', 'selected'], + [' return sort(left).concat(pivot).concat(sort(right));', 'deleted', 'selected'], + [' };', 'deleted', 'selected'], + ['', 'deleted', 'selected'], + [' return sort(Array.apply(this, arguments));', 'deleted', 'selected'], + ['};', 'deleted', 'selected'], + ); + assert.isTrue(getPatchItem('unstaged', 'sample.js').find('.github-FilePatchView-metaTitle').exists()); + }); }); }); describe('with a file that used to be a symlink', function() { + beforeEach(async function() { + await useFixture('symlinks'); + + await fs.remove(repoPath('symlink.txt')); + await fs.writeFile(repoPath('symlink.txt'), "Guess what I'm a text file now suckers", {encoding: 'utf8'}); + }); + describe('unstaged', function() { - it('may stage the symlink deletion without the content addition'); + beforeEach(async function() { + await clickFileInGitTab('unstaged', 'symlink.txt'); + }); + + it('may stage the symlink deletion without the content addition', async function() { + getPatchItem('unstaged', 'symlink.txt').find('.github-FilePatchView-metaControls button').simulate('click'); + await assert.async.isFalse( + getPatchItem('unstaged', 'symlink.txt').find('.github-FilePatchView-metaTitle').exists(), + ); + + await patchContent( + 'unstaged', 'symlink.txt', + ["Guess what I'm a text file now suckers", 'added', 'selected'], + [' No newline at end of file', 'nonewline'], + ); + + await clickFileInGitTab('staged', 'symlink.txt'); + + await patchContent( + 'staged', 'symlink.txt', + ['./regular-file.txt', 'deleted', 'selected'], + [' No newline at end of file', 'nonewline'], + ); + assert.isTrue(getPatchItem('staged', 'symlink.txt').find('.github-FilePatchView-metaTitle').exists()); + }); + + it('may stage the content addition and the symlink deletion together', async function() { + getPatchEditor('unstaged', 'symlink.txt').selectAll(); + getPatchItem('unstaged', 'symlink.txt').find('.github-HunkHeaderView-stageButton').simulate('click'); - it('may stage the content addition and the symlink deletion'); + await clickFileInGitTab('staged', 'symlink.txt'); + + await patchContent( + 'staged', 'symlink.txt', + ["Guess what I'm a text file now suckers", 'added', 'selected'], + [' No newline at end of file', 'nonewline'], + ); + assert.isTrue(getPatchItem('staged', 'symlink.txt').find('.github-FilePatchView-metaTitle').exists()); + }); }); describe('staged', function() { - it('may unstage the content addition and the symlink creation'); + beforeEach(async function() { + await git.stageFiles(['symlink.txt']); + await clickFileInGitTab('staged', 'symlink.txt'); + }); + + it('may unstage the content addition and the symlink deletion together', async function() { + getPatchItem('staged', 'symlink.txt').find('.github-FilePatchView-metaControls button').simulate('click'); + + await clickFileInGitTab('unstaged', 'symlink.txt'); + + assert.isTrue(getPatchItem('unstaged', 'symlink.txt').find('.github-FilePatchView-metaTitle').exists()); + + await patchContent( + 'unstaged', 'symlink.txt', + ["Guess what I'm a text file now suckers", 'added', 'selected'], + [' No newline at end of file', 'nonewline'], + ); + }); + + it('may unstage the content addition without the symlink deletion', async function() { + getPatchEditor('staged', 'symlink.txt').selectAll(); + getPatchItem('staged', 'symlink.txt').find('.github-HunkHeaderView-stageButton').simulate('click'); + + await clickFileInGitTab('unstaged', 'symlink.txt'); - it('may unstage the content addition without the symlink creation'); + await patchContent( + 'unstaged', 'symlink.txt', + ["Guess what I'm a text file now suckers", 'added', 'selected'], + [' No newline at end of file', 'nonewline'], + ); + assert.isFalse(getPatchItem('unstaged', 'symlink.txt').find('.github-FilePatchView-metaTitle').exists()); - it('may unstage the symlink creation without the content addition'); + await clickFileInGitTab('staged', 'symlink.txt'); + assert.isTrue(getPatchItem('staged', 'symlink.txt').find('.github-FilePatchView-metaTitle').exists()); + await patchContent( + 'staged', 'symlink.txt', + ['./regular-file.txt', 'deleted', 'selected'], + [' No newline at end of file', 'nonewline'], + ); + }); }); }); From f10c10c452593930b05b7c9f5cd15a43b31f47f0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 10:27:43 -0400 Subject: [PATCH 0629/4053] Update assertion to match changed fixture --- test/controllers/root-controller.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 8f481eafb9..4d94f594ea 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -682,17 +682,17 @@ describe('RootController', function() { const diff = await repository.git.exec(['diff', '--', 'sample.js']); assert.equal(diff, dedent` diff --cc sample.js - index 5c084c0,86e041d..0000000 + index 0443956,86e041d..0000000 --- a/sample.js +++ b/sample.js @@@ -1,6 -1,3 +1,12 @@@ ++<<<<<<< current + - +change file contentsvar quicksort = function () { - + var sort = function(items) { + +change file contentsconst quicksort = function() { + + const sort = function(items) { ++||||||| after discard - ++var quicksort = function () { - ++ var sort = function(items) { + ++const quicksort = function() { + ++ const sort = function(items) { ++======= ++>>>>>>> before discard foo From 58d6078175a32be463599c1a038ae34b0558d6ab Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 11:19:42 -0400 Subject: [PATCH 0630/4053] Modified patch integration tests, mostly for completeness --- test/integration/file-patch.test.js | 209 +++++++++++++++++++++++++++- 1 file changed, 202 insertions(+), 7 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 81cbc778a4..bb07d89b28 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -1,6 +1,7 @@ import fs from 'fs-extra'; import path from 'path'; import until from 'test-until'; +import dedent from 'dedent-js'; import {setup, teardown} from './helpers'; import GitShellOutStrategy from '../../lib/git-shell-out-strategy'; @@ -663,22 +664,216 @@ describe('integration: file patches', function() { }); describe('with a modified file', function() { + beforeEach(async function() { + await useFixture('multi-line-file'); + + await fs.writeFile( + repoPath('sample.js'), + dedent` + const quicksort = function() { + const sort = function(items) { + while (items.length > 0) { + current = items.shift(); + current < pivot ? left.push(current) : right.push(current); + } + return sort(left).concat(pivot).concat(sort(right)); + // added 0 + // added 1 + }; + + return sort(Array.apply(this, arguments)); + };\n + `, + {encoding: 'utf8'}, + ); + }); + describe('unstaged', function() { - it('may be partially staged'); + beforeEach(async function() { + await clickFileInGitTab('unstaged', 'sample.js'); + }); + + it('may be partially staged', async function() { + getPatchEditor('unstaged', 'sample.js').setSelectedBufferRanges([ + [[2, 0], [2, 0]], + [[10, 0], [10, 0]], + ]); + getPatchItem('unstaged', 'sample.js').find('.github-HunkHeaderView-stageButton').simulate('click'); + + await patchContent( + 'unstaged', 'sample.js', + ['const quicksort = function() {'], + [' const sort = function(items) {'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted', 'selected'], + [' while (items.length > 0) {'], + [' current = items.shift();'], + [' current < pivot ? left.push(current) : right.push(current);'], + [' }'], + [' return sort(left).concat(pivot).concat(sort(right));'], + [' // added 0', 'added'], + [' // added 1'], + [' };'], + [''], + ); + + await clickFileInGitTab('staged', 'sample.js'); + await patchContent( + 'staged', 'sample.js', + ['const quicksort = function() {'], + [' const sort = function(items) {'], + [' if (items.length <= 1) { return items; }', 'deleted', 'selected'], + [' let pivot = items.shift(), current, left = [], right = [];'], + [' while (items.length > 0) {'], + [' current = items.shift();'], + [' current < pivot ? left.push(current) : right.push(current);'], + [' }'], + [' return sort(left).concat(pivot).concat(sort(right));'], + [' // added 1', 'added', 'selected'], + [' };'], + [''], + [' return sort(Array.apply(this, arguments));'], + ); + }); - it('may be completely staged'); + it('may be completely staged', async function() { + getPatchEditor('unstaged', 'sample.js').selectAll(); + getPatchItem('unstaged', 'sample.js').find('.github-HunkHeaderView-stageButton').simulate('click'); + + await clickFileInGitTab('staged', 'sample.js'); + await patchContent( + 'staged', 'sample.js', + ['const quicksort = function() {'], + [' const sort = function(items) {'], + [' if (items.length <= 1) { return items; }', 'deleted', 'selected'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted', 'selected'], + [' while (items.length > 0) {'], + [' current = items.shift();'], + [' current < pivot ? left.push(current) : right.push(current);'], + [' }'], + [' return sort(left).concat(pivot).concat(sort(right));'], + [' // added 0', 'added', 'selected'], + [' // added 1', 'added', 'selected'], + [' };'], + [''], + [' return sort(Array.apply(this, arguments));'], + ); + }); - it('may discard lines'); + it('may discard lines', async function() { + getPatchEditor('unstaged', 'sample.js').setSelectedBufferRanges([ + [[3, 0], [3, 0]], + [[9, 0], [9, 0]], + ]); + getPatchItem('unstaged', 'sample.js').find('.github-HunkHeaderView-discardButton').simulate('click'); - it('may stage an executable mode change'); + await patchContent( + 'unstaged', 'sample.js', + ['const quicksort = function() {'], + [' const sort = function(items) {'], + [' if (items.length <= 1) { return items; }', 'deleted'], + [' let pivot = items.shift(), current, left = [], right = [];'], + [' while (items.length > 0) {'], + [' current = items.shift();'], + [' current < pivot ? left.push(current) : right.push(current);'], + [' }'], + [' return sort(left).concat(pivot).concat(sort(right));'], + [' // added 1', 'added', 'selected'], + [' };'], + [''], + [' return sort(Array.apply(this, arguments));'], + ); + + const editor = await workspace.open(repoPath('sample.js')); + assert.strictEqual(editor.getText(), dedent` + const quicksort = function() { + const sort = function(items) { + let pivot = items.shift(), current, left = [], right = []; + while (items.length > 0) { + current = items.shift(); + current < pivot ? left.push(current) : right.push(current); + } + return sort(left).concat(pivot).concat(sort(right)); + // added 1 + }; + + return sort(Array.apply(this, arguments)); + };\n + `); + }); }); describe('staged', function() { - it('may be partially unstaged'); + beforeEach(async function() { + await git.stageFiles(['sample.js']); + await clickFileInGitTab('staged', 'sample.js'); + }); - it('may be partially staged'); + it('may be partially unstaged', async function() { + getPatchEditor('staged', 'sample.js').setSelectedBufferRanges([ + [[3, 0], [3, 0]], + [[10, 0], [10, 0]], + ]); + getPatchItem('staged', 'sample.js').find('.github-HunkHeaderView-stageButton').simulate('click'); - it('may unstage an executable mode change'); + await patchContent( + 'staged', 'sample.js', + ['const quicksort = function() {'], + [' const sort = function(items) {'], + [' if (items.length <= 1) { return items; }', 'deleted', 'selected'], + [' let pivot = items.shift(), current, left = [], right = [];'], + [' while (items.length > 0) {'], + [' current = items.shift();'], + [' current < pivot ? left.push(current) : right.push(current);'], + [' }'], + [' return sort(left).concat(pivot).concat(sort(right));'], + [' // added 0', 'added'], + [' };'], + [''], + [' return sort(Array.apply(this, arguments));'], + ); + + await clickFileInGitTab('unstaged', 'sample.js'); + await patchContent( + 'unstaged', 'sample.js', + ['const quicksort = function() {'], + [' const sort = function(items) {'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted', 'selected'], + [' while (items.length > 0) {'], + [' current = items.shift();'], + [' current < pivot ? left.push(current) : right.push(current);'], + [' }'], + [' return sort(left).concat(pivot).concat(sort(right));'], + [' // added 0'], + [' // added 1', 'added', 'selected'], + [' };'], + [''], + [' return sort(Array.apply(this, arguments));'], + ); + }); + + it('may be fully unstaged', async function() { + getPatchEditor('staged', 'sample.js').selectAll(); + getPatchItem('staged', 'sample.js').find('.github-HunkHeaderView-stageButton').simulate('click'); + + await clickFileInGitTab('unstaged', 'sample.js'); + await patchContent( + 'unstaged', 'sample.js', + ['const quicksort = function() {'], + [' const sort = function(items) {'], + [' if (items.length <= 1) { return items; }', 'deleted', 'selected'], + [' let pivot = items.shift(), current, left = [], right = [];', 'deleted', 'selected'], + [' while (items.length > 0) {'], + [' current = items.shift();'], + [' current < pivot ? left.push(current) : right.push(current);'], + [' }'], + [' return sort(left).concat(pivot).concat(sort(right));'], + [' // added 0', 'added', 'selected'], + [' // added 1', 'added', 'selected'], + [' };'], + [''], + [' return sort(Array.apply(this, arguments));'], + ); + }); }); }); }); From 7b9d8553616f6c9a9e2f1c74fbb4c7967256b81d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 11:31:44 -0400 Subject: [PATCH 0631/4053] Ensure project watcher has a chance to get started --- test/integration/helpers.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/integration/helpers.js b/test/integration/helpers.js index 2c458ea46f..1fb687d854 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -63,6 +63,11 @@ export async function setup(options = {}) { atomEnv.project.setPaths(projectDirs, {mustExist: true, exact: true}); + // Ensure project watchers have a chance to start + await Promise.all( + atomEnv.project.getPaths().map(projectPath => atomEnv.project.watcherPromisesByPath[projectPath]), + ); + const loginModel = new GithubLoginModel(InMemoryStrategy); let configDirPath = null; From 157c22dee07e8cb0b02de19fd1236e3e760e1eaf Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 25 Oct 2018 17:51:40 +0200 Subject: [PATCH 0632/4053] update RFC & emojify --- docs/rfcs/004-multi-file-diff.md | 82 ++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 62c30c1729..2ea9ba8f21 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -1,72 +1,82 @@ -# Multi-file Diffs +# Commit Preview & Multi-file Diffs -## Status +## :tipping_hand_woman: Status Proposed -## Summary +## :memo: Summary -Users can select multiple files in unstaged changes and staged changes pane and see the diffs of selected files in one view, akin to the [`Files changed` tab in pull requests on github.com](https://github.com/atom/github/pull/1753/files). +Give users an option to, before they make a commit, see diffs of all staged changes in one view, akin to the [`Files changed` tab in pull requests on github.com](https://github.com/atom/github/pull/1753/files). -## Motivation +## :checkered_flag: Motivation -So that users can view a full set of changes with more context before staging or committing those changes. +So that users can view a full set of changes with more context before committing them. + +Note that the multi-diff view is the MVP of this RFC, and we have identified `Commit Preview` to be the least frictional way to introduce this feature without making too many UX changes. Other planned features that will also make use of multi-diff view are: -The ability to display multiple diffs in one view will also serve as a building block for the following planned features: - [commit pane item](#1655) where it shows all changes in a single commit - [new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) that shows all changed files proposed in a PR +- (TBD) multi-select files from unstaged & staged panes to view diffs -## Explanation - -#### Mock-ups coming soon - - -#### Unstaged Changes pane -- User can `cmd+click` and select multiple files from the list of unstaged changes, and the pane on the left (see multi-file diff section below) will show diffs of the selected files. That pane will continue to reflect any further selecting/unselecting on the Unstaged Changes pane. -- Once there is at least one file selected, `Stage All` button should be worded as `Stage Selected`. - -#### Staged Changes pane -- Same behavior as Unstaged Changes pane. -- Once there is at least one file selected, `Unstage All` button should be worded as `Unstage Selected`. +## 🤯 Explanation ![staged changes](https://user-images.githubusercontent.com/378023/47497740-0a0b3200-d896-11e8-85af-7c644af9ca37.png) +#### Commit preview button +A new button added above the commit message box that, when clicked, opens a multi-file diff pane item called something like "Commit Preview" and shows a summary of what will go into the user's next commit based on what is currently staged. + #### Multi-file diff view - Shows diffs of multiple files as a stack. -- Each diff should show up as its own block, and the current functionality should remain independent of each block. -- It should be easy to jump quickly to a specific file you care about, or back to the file list to get to another file. Dotcom does so by creating a `jump to` drop down. +- Each diff retains the file-specific controls it currently has in its header (e.g. the open file, stage file, undo last discard, etc). +- **[[out of scope](https://github.com/atom/github/blob/multi-diff-rfc/docs/rfcs/004-multi-file-diff.md#out-of-scope)]** It should be easy to jump quickly to a specific file you care about, or back to the file list to get to another file. Dotcom does so by creating a `jump to` drop down. - As user scrolls through a long list of diffs, there should be a sticky heading which remains visible showing the filename of the diff being viewed. -Nice-to-have UX that doesn't necessarily need to be implemented -- Each file diff can be collapsed (this can potentially be ) +- **[[out of scope](https://github.com/atom/github/blob/multi-diff-rfc/docs/rfcs/004-multi-file-diff.md#out-of-scope)]** Each file diff can be collapsed. -All files collapsed | Some files collapsed ---- | --- -![all collapsed](https://user-images.githubusercontent.com/378023/47497741-0a0b3200-d896-11e8-90b5-4153009f80b4.png) | ![some collapsed](https://user-images.githubusercontent.com/378023/47498408-27410000-d898-11e8-8e4b-c02dafe7e35a.png) +#### Workflow +This would be a nice addition to the top-to-bottom flow that currently exists in our panel: +1. View unstaged changes +2. Stage changes to be committed +3. :new: Click "Commit Preview" :new: +4. Write commit message that summarizes all changes +5. Hit commit button +6. See commit appear in recent commits list +7. Profit :tada: -## Drawbacks +## :anchor: Drawbacks -- `cmd-click` to select multiple files might not be as universally known as we assume, so that might affect discoverability of this feature. - There might be performance concerns having to render many diffs at once. -## Rationale and alternatives +## :thinking: Rationale and alternatives An alternative would be to _not_ implement multi-file diff, as other editors like VS Code also only has per-file diff at the time of writing. However, not implementing this would imply that [the proposed new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) will have to find another solution to display all changes in a PR. Additionally users would have to do a lot more clicking to view all of their changes. Imagine there was a variable rename and only 10 lines are changed, but they are each in a different file. It'd be a bit of a pain to click through to view each one. Also, if we didn't implement multi-file diffs then we couldn't show commit contents since they often include changes across multiple files. -## Unresolved questions - -- What unresolved questions do you expect to resolve through the implementation of this feature before it is released in a new version of the package? +## :question: Unresolved questions How exactly do we construct the multi-file diffs? Do we have one TextEditor component that has different sections for each file. Or do we create a new type of pane item that contains multiple TextEditor components stacked on top of one another, one for each file diff... If we do the former we could probably get something shipped sooner (we could just get the diff of the staged changes from Git, add a special decoration for file headers, and present all the changes in one editor). But to pave the way for a more complex code review UX I think taking extra time to do the latter will serve us well. For example, I can imagine reviewers wanting to collapse some files, or mark them as "Done", in which case it would be easier if we treated each diff as its own component. -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? -It would be cool if each diff was collapsable. Especially for when we start using the multi-file diff for code review and the reviewers may want to hide the contents of a file once they're done addressing the changes in it. "Collapse/Expand All" capabilities would be nice as well. I don't see this as a critical feature for this particular RFC. +## :warning: Out of Scope + +The following items are considered out of scope for this RFC, but can be addressed in the future independently of this RFC. + +#### Collapsable Diff +It would be cool if each diff was collapsable. Especially for when we start using the multi-file diff for code review and the reviewers may want to hide the contents of a file once they're done addressing the changes in it. "Collapse/Expand All" capabilities would be nice as well. + +All files collapsed | Some files collapsed +--- | --- +![all collapsed](https://user-images.githubusercontent.com/378023/47497741-0a0b3200-d896-11e8-90b5-4153009f80b4.png) | ![some collapsed](https://user-images.githubusercontent.com/378023/47498408-27410000-d898-11e8-8e4b-c02dafe7e35a.png) + +#### File filter for diff view +"Find" input field for filtering diffs based on search term (which could be a file name, an author, a variable name, etc). See [this section](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md#sort-options) in PR review RFC for more details. + +#### Other out of scope UX considerations +- whether `cmd+click` to select multiple files is discoverable -## Implementation phases +## :construction: Implementation phases TBD -## Definition of done +## :white_check_mark: Definition of done TBD From 8def2554a0a7df6721dbec4c87799a69a7f9e8f1 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 25 Oct 2018 17:54:48 +0200 Subject: [PATCH 0633/4053] fix link --- docs/rfcs/004-multi-file-diff.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 2ea9ba8f21..0dd7d7ca20 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -30,9 +30,9 @@ A new button added above the commit message box that, when clicked, opens a mult - Shows diffs of multiple files as a stack. - Each diff retains the file-specific controls it currently has in its header (e.g. the open file, stage file, undo last discard, etc). -- **[[out of scope](https://github.com/atom/github/blob/multi-diff-rfc/docs/rfcs/004-multi-file-diff.md#out-of-scope)]** It should be easy to jump quickly to a specific file you care about, or back to the file list to get to another file. Dotcom does so by creating a `jump to` drop down. +- **[[out of scope]](https://github.com/atom/github/blob/multi-diff-rfc/docs/rfcs/004-multi-file-diff.md#warning-out-of-scope)** It should be easy to jump quickly to a specific file you care about, or back to the file list to get to another file. Dotcom does so by creating a `jump to` drop down. - As user scrolls through a long list of diffs, there should be a sticky heading which remains visible showing the filename of the diff being viewed. -- **[[out of scope](https://github.com/atom/github/blob/multi-diff-rfc/docs/rfcs/004-multi-file-diff.md#out-of-scope)]** Each file diff can be collapsed. +- **[[out of scope]](https://github.com/atom/github/blob/multi-diff-rfc/docs/rfcs/004-multi-file-diff.md#warning-out-of-scope)** Each file diff can be collapsed. #### Workflow This would be a nice addition to the top-to-bottom flow that currently exists in our panel: From 32804d40b222bc48c3183a9e0edee54d1a5185ae Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 13:47:52 -0400 Subject: [PATCH 0634/4053] Don't debounce the selectedItems call in mouseup --- lib/views/staging-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index cab8e5b983..fae58d5cf0 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -873,7 +873,7 @@ export default class StagingView extends React.Component { }), resolve); }); if (hadSelectionInProgress) { - this.debouncedDidChangeSelectedItem(true); + this.didChangeSelectedItems(true); } } From 0a719cac4b551f754e820d8d1025b5be9d9f45a9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 13:48:06 -0400 Subject: [PATCH 0635/4053] Wait for the watchers in the GitHub package object to start instead --- test/integration/helpers.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/integration/helpers.js b/test/integration/helpers.js index 1fb687d854..616d66f705 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -63,11 +63,6 @@ export async function setup(options = {}) { atomEnv.project.setPaths(projectDirs, {mustExist: true, exact: true}); - // Ensure project watchers have a chance to start - await Promise.all( - atomEnv.project.getPaths().map(projectPath => atomEnv.project.watcherPromisesByPath[projectPath]), - ); - const loginModel = new GithubLoginModel(InMemoryStrategy); let configDirPath = null; @@ -122,8 +117,13 @@ export async function setup(options = {}) { }); } + githubPackage.getContextPool().set(projectDirs, opts.state); await githubPackage.activate(opts.state); + await Promise.all( + projectDirs.map(projectDir => githubPackage.getContextPool().getContext(projectDir).getObserverStartedPromise()), + ); + return { atomEnv, githubPackage, From eed04f395bd2983fdaae53526b9ef4ac49a3a5be Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 13:48:27 -0400 Subject: [PATCH 0636/4053] Slightly clearer timeout error messages --- test/integration/file-patch.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index bb07d89b28..c5224bb661 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -56,7 +56,7 @@ describe('integration: file patches', function() { .find(`.github-StagingView-${stagingStatus} .github-FilePatchListView-item`) .filterWhere(w => w.find('.github-FilePatchListView-path').text() === relativePath); return listItem.exists(); - }, `list item for path ${relativePath} (${stagingStatus}) appears`); + }, `the list item for path ${relativePath} (${stagingStatus}) appears`); listItem.simulate('mousedown', {button: 0, persist() {}}); window.dispatchEvent(new MouseEvent('mouseup')); @@ -64,7 +64,7 @@ describe('integration: file patches', function() { const itemSelector = `FilePatchItem[relPath="${relativePath}"][stagingStatus="${stagingStatus}"]`; await until( () => wrapper.update().find(itemSelector).find('.github-FilePatchView').exists(), - `File patch pane item for ${relativePath} arrives and loads`, + `the FilePatchItem for ${relativePath} arrives and loads`, ); } From 0837366c50e491f3846e43ca1300534d252e387f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 14:16:56 -0400 Subject: [PATCH 0637/4053] Clarify test name --- test/integration/file-patch.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index c5224bb661..778a978f37 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -543,7 +543,7 @@ describe('integration: file patches', function() { await clickFileInGitTab('staged', 'sample.js'); }); - it.skip('may unstage the content deletion and the symlink creation', async function() { + it.skip('may unstage the symlink creation but not the content deletion', async function() { getPatchItem('staged', 'sample.js').find('.github-FilePatchView-metaControls button').simulate('click'); await clickFileInGitTab('unstaged', 'sample.js'); From 8a40a74d9fdd7c0cc284dec8a30b8ba84208fb7e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 25 Oct 2018 11:21:31 -0700 Subject: [PATCH 0638/4053] Fix and clean up GitStrategies tests --- test/git-strategies.test.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 4d61ba4b94..f981b6ad43 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -103,7 +103,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; const workingDirPath = await cloneRepository('three-files'); const git = createTestStrategy(workingDirPath); - assert.strictEqual(await git.getConfig('commit.template'), ''); + assert.isNotOk(await git.getConfig('commit.template')); // falsy value of null or '' assert.isNull(await git.getCommitMessageTemplate()); }); @@ -118,14 +118,6 @@ import * as reporterProxy from '../lib/reporter-proxy'; `Invalid commit template path set in Git config: ${nonExistentCommitTemplatePath}`, ); }); - - it('test that ~ gets expanded correctly', async function() { - - }); - - it('test that relative path in repo is covered', async function() { - - }); }); if (process.platform === 'win32') { From 708d388375d2a0b3e9ec0245531700e2e7626347 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 14:32:34 -0400 Subject: [PATCH 0639/4053] Rename integration test suites --- test/integration/checkout-pr.test.js | 2 +- test/integration/launch.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/checkout-pr.test.js b/test/integration/checkout-pr.test.js index 158b9f1d94..ca037e9008 100644 --- a/test/integration/checkout-pr.test.js +++ b/test/integration/checkout-pr.test.js @@ -8,7 +8,7 @@ import {createRepositoryResult} from '../fixtures/factories/repository-result'; import IDGenerator from '../fixtures/factories/id-generator'; import {createPullRequestsResult, createPullRequestDetailResult} from '../fixtures/factories/pull-request-result'; -describe('check out a pull request', function() { +describe('integration: check out a pull request', function() { let context, wrapper, atomEnv, workspaceElement, git, idGen, repositoryID; beforeEach(async function() { diff --git a/test/integration/launch.test.js b/test/integration/launch.test.js index 0425fc2aab..47f22a2e76 100644 --- a/test/integration/launch.test.js +++ b/test/integration/launch.test.js @@ -5,7 +5,7 @@ import {setup, teardown} from './helpers'; import GitTabItem from '../../lib/items/git-tab-item'; import GitHubTabItem from '../../lib/items/github-tab-item'; -describe('Package initialization', function() { +describe('integration: package initialization', function() { let context; afterEach(async function() { From 8b1ad3e54987720009bb9357fb6b0d386e4b419a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 14:32:51 -0400 Subject: [PATCH 0640/4053] Mark that checkout PR test as flaky --- test/integration/checkout-pr.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/integration/checkout-pr.test.js b/test/integration/checkout-pr.test.js index ca037e9008..f059df6685 100644 --- a/test/integration/checkout-pr.test.js +++ b/test/integration/checkout-pr.test.js @@ -165,6 +165,8 @@ describe('integration: check out a pull request', function() { // achtung! this test is flaky it('opens a pane item for a pull request by clicking on an entry in the GitHub tab', async function() { + this.retries(5); // FLAKE + const {resolve: resolve0, promise: promise0} = expectRepositoryQuery(); resolve0(); await promise0; From 131fafb218813902434e374a720cb611af72e9aa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 14:50:38 -0400 Subject: [PATCH 0641/4053] Invalidate remotes when the config changes --- lib/models/repository-states/present.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 7e385a4db3..ed25c56e49 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -126,6 +126,7 @@ export default class Present extends State { } if (endsWith('.git', 'config')) { + keys.add(Keys.remotes); keys.add(Keys.config.all); keys.add(Keys.statusBundle); continue; From f9e3e773dad6772e8de92ee6d6ae6500540a0ed8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 15:02:29 -0400 Subject: [PATCH 0642/4053] Skip symlink tests on Windows --- test/integration/file-patch.test.js | 32 ++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 778a978f37..ac99b9277f 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -20,7 +20,9 @@ describe('integration: file patches', function() { }); afterEach(async function() { - await teardown(context); + if (context) { + await teardown(context); + } }); async function useFixture(fixtureName) { @@ -470,10 +472,6 @@ describe('integration: file patches', function() { describe('with a symlink that used to be a file', function() { beforeEach(async function() { - if (process.platform === 'win32') { - this.skip(); - } - await useFixture('multi-line-file'); await fs.remove(repoPath('sample.js')); await fs.writeFile(repoPath('target.txt'), 'something to point the symlink to', {encoding: 'utf8'}); @@ -481,6 +479,12 @@ describe('integration: file patches', function() { }); describe('unstaged', function() { + before(function() { + if (process.platform === 'win32') { + this.skip(); + } + }); + beforeEach(async function() { await clickFileInGitTab('unstaged', 'sample.js'); }); @@ -538,6 +542,12 @@ describe('integration: file patches', function() { }); describe('staged', function() { + before(function() { + if (process.platform === 'win32') { + this.skip(); + } + }); + beforeEach(async function() { await git.stageFiles(['sample.js']); await clickFileInGitTab('staged', 'sample.js'); @@ -578,6 +588,12 @@ describe('integration: file patches', function() { }); describe('unstaged', function() { + before(function() { + if (process.platform === 'win32') { + this.skip(); + } + }); + beforeEach(async function() { await clickFileInGitTab('unstaged', 'symlink.txt'); }); @@ -620,6 +636,12 @@ describe('integration: file patches', function() { }); describe('staged', function() { + before(function() { + if (process.platform === 'win32') { + this.skip(); + } + }); + beforeEach(async function() { await git.stageFiles(['symlink.txt']); await clickFileInGitTab('staged', 'symlink.txt'); From b345f8787855ef35c93d46fcb8d848053adb301e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 15:34:42 -0400 Subject: [PATCH 0643/4053] Manually trigger filesystem changes for the WorkspaceChangeObserver --- test/integration/file-patch.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index ac99b9277f..5bd7c1df9f 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -11,6 +11,7 @@ describe('integration: file patches', function() { let workspace; let commands, workspaceElement; let repoRoot, git; + let usesWorkspaceObserver; this.timeout(Math.max(this.timeout(), 10000)); @@ -38,6 +39,8 @@ describe('integration: file patches', function() { repoRoot = atomEnv.project.getPaths()[0]; git = new GitShellOutStrategy(repoRoot); + usesWorkspaceObserver = context.githubPackage.getContextPool().getContext(repoRoot).useWorkspaceChangeObserver(); + workspaceElement = atomEnv.views.getView(workspace); // Open the git tab @@ -49,6 +52,13 @@ describe('integration: file patches', function() { return path.join(repoRoot, ...parts); } + // Manually trigger a Repository update on Linux, which uses a WorkspaceChangeObserver. + function triggerChange() { + if (usesWorkspaceObserver) { + context.githubPackage.getActiveRepository().refresh(); + } + } + async function clickFileInGitTab(stagingStatus, relativePath) { let listItem = null; @@ -197,6 +207,7 @@ describe('integration: file patches', function() { beforeEach(async function() { await useFixture('three-files'); await fs.writeFile(repoPath('added-file.txt'), '0000\n0001\n0002\n0003\n0004\n0005\n', {encoding: 'utf8'}); + triggerChange(); await clickFileInGitTab('unstaged', 'added-file.txt'); }); @@ -309,6 +320,7 @@ describe('integration: file patches', function() { await useFixture('multi-line-file'); await fs.remove(repoPath('sample.js')); + triggerChange(); }); describe('unstaged', function() { @@ -476,6 +488,7 @@ describe('integration: file patches', function() { await fs.remove(repoPath('sample.js')); await fs.writeFile(repoPath('target.txt'), 'something to point the symlink to', {encoding: 'utf8'}); await fs.symlink(repoPath('target.txt'), repoPath('sample.js')); + triggerChange(); }); describe('unstaged', function() { @@ -585,6 +598,7 @@ describe('integration: file patches', function() { await fs.remove(repoPath('symlink.txt')); await fs.writeFile(repoPath('symlink.txt'), "Guess what I'm a text file now suckers", {encoding: 'utf8'}); + triggerChange(); }); describe('unstaged', function() { @@ -708,6 +722,7 @@ describe('integration: file patches', function() { `, {encoding: 'utf8'}, ); + triggerChange(); }); describe('unstaged', function() { From a2f01c8b828a18941df72eba28419068a3ff7238 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 15:47:32 -0400 Subject: [PATCH 0644/4053] Ensure the patch editor is not unexpectedly soft-wrapped --- test/integration/file-patch.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 5bd7c1df9f..47433b6adf 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -118,6 +118,7 @@ describe('integration: file patches', function() { actualRowText = ['Unable to find patch item']; return false; } + editor.setSoftWrapped(false); const decorationsByMarker = editor.decorationManager.decorationPropertiesByMarkerForScreenRowRange(0, Infinity); actualClassesByRow.clear(); From 8f5842d4ab0d974e8cd45497f80c16b0838f38a7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 15:47:51 -0400 Subject: [PATCH 0645/4053] Clarify patchContent failure message --- test/integration/file-patch.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 47433b6adf..cea29098f5 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -175,7 +175,7 @@ describe('integration: file patches', function() { } return match; - }, 'waiting for the updated file patch to arrive').catch(e => { + }, 'a matching updated file patch arrives').catch(e => { let diagnosticOutput = ''; for (let i = 0; i < actualRowText.length; i++) { diagnosticOutput += differentRows.has(i) ? '! ' : ' '; From b2ef7ba2e0daa286caedbe90188ad97286accf27 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 25 Oct 2018 15:48:04 -0400 Subject: [PATCH 0646/4053] console.error doesn't show up in headless tests, console.log does --- test/integration/file-patch.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index cea29098f5..bbc627e85c 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -198,7 +198,7 @@ describe('integration: file patches', function() { } // eslint-disable-next-line no-console - console.error('Unexpected patch contents:\n', diagnosticOutput); + console.log('Unexpected patch contents:\n', diagnosticOutput); throw e; }); From 63ffa6960305008ff121f02900e4a175adc7e89b Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 25 Oct 2018 15:07:30 -0700 Subject: [PATCH 0647/4053] add comment explaining regex Co-Authored-By: Katrina Uychaco --- lib/git-shell-out-strategy.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index a7564d354d..49e134972d 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -426,8 +426,15 @@ export default class GitShellOutStrategy { * https://git-scm.com/docs/git-config#git-config-pathname */ const homeDir = os.homedir(); + // this regex attempts to get the specified user's home directory + // Ex: on Mac ~kuychaco/ is expanded to the specified user’s home directory (/Users/kuychaco) + + // Regex translation: + // ^~ line starts with tilde + // ([^/]*)/ captures non-forwardslash characters before first slash const regex = new RegExp('^~([^/]*)/'); templatePath = templatePath.trim().replace(regex, (_, user) => { + // if no user is specified, fall back to using the home directory. return `${user ? path.join(path.dirname(homeDir), user) : homeDir}/`; }); From c15f1d025b541f33a5ede74cc254b211a08f7570 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 25 Oct 2018 15:21:17 -0700 Subject: [PATCH 0648/4053] cleanup some dead code Co-Authored-By: Katrina Uychaco --- lib/models/repository-states/present.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index a574e83138..477575f22e 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -188,13 +188,11 @@ export default class Present extends State { if (endsWith('.git', 'MERGE_HEAD')) { if (event.action === 'created') { - if (this.isCommitMessageClean()) { // is it really necessary to check if commit message is clean? + if (this.isCommitMessageClean()) { this.setCommitMessage(await this.repository.getMergeMessage()); - // this.didUpdate(); } } else if (event.action === 'deleted') { this.setCommitMessage(this.commitMessageTemplate || ''); - // this.didUpdate(); } } @@ -204,7 +202,6 @@ export default class Present extends State { if (this.commitMessageTemplate !== template) { this.setCommitMessageTemplate(template); this.setCommitMessage(template); - // this.didUpdate(); } } } From fd1d1cfcbbe328ab103e4828a9c85d15616dfa3b Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 25 Oct 2018 15:38:27 -0700 Subject: [PATCH 0649/4053] add comment about isValidMessage regex Co-Authored-By: Katrina Uychaco --- lib/views/commit-view.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index 889da4cddc..63e4a8a479 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -469,6 +469,8 @@ export default class CommitView extends React.Component { } isValidMessage() { + // ensure that there are at least some non-comment lines in the commit message. + // Commented lines are stripped out of commit messages by git, by default configuration. return this.editor && this.editor.getText().replace(/^#.*$/gm, '').trim().length !== 0; } From 3d9440549abdd45345caf1e3cf48a495b105c6b8 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 25 Oct 2018 15:47:39 -0700 Subject: [PATCH 0650/4053] :fire: unused dependencies. Co-Authored-By: Katrina Uychaco --- test/git-strategies.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index f981b6ad43..9d0ccce2f4 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1,7 +1,6 @@ import fs from 'fs-extra'; import path from 'path'; import http from 'http'; -import os from 'os'; import mkdirp from 'mkdirp'; import dedent from 'dedent-js'; From 9b606ba232c7f3f0427c4e423148cb940db60dfa Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 25 Oct 2018 17:24:55 -0700 Subject: [PATCH 0651/4053] test merged changes + cleanup Co-Authored-By: Katrina Uychaco --- lib/controllers/commit-controller.js | 1 - lib/views/commit-view.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index 4220d97de5..db88bc8fe8 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -110,7 +110,6 @@ export default class CommitController extends React.Component { } componentDidUpdate(prevProps) { - // todo: test that this still works as expected this.commitMessageBuffer.setTextViaDiff(this.getCommitMessage()); } diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index ddfc7f3e62..e7835fa98c 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -463,8 +463,7 @@ export default class CommitView extends React.Component { isValidMessage() { // ensure that there are at least some non-comment lines in the commit message. // Commented lines are stripped out of commit messages by git, by default configuration. - // todo: test this! - return this.props.messageBuffer && this.props.messageBuffer.getText().replace(/^#.*$/gm, '').trim().length !== 0; + return this.props.messageBuffer.getText().replace(/^#.*$/gm, '').trim().length !== 0; } commitIsEnabled(amend) { From 6379f30b78a221c2650801a1b83f713e307f491e Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 25 Oct 2018 17:52:34 -0700 Subject: [PATCH 0652/4053] fix issue with setting cursor buffer position This only works for commit message templates where every line starts with a #. We explored using isCommitMessageClean to try and handle commit message templates that have uncommented lines. We're not calling didUpdate every time the commit message is updated, so it's out of sync, and we decided it was getting too edge casey out there. Co-Authored-By: Katrina Uychaco --- lib/views/commit-view.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index e7835fa98c..5b0560c589 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -588,8 +588,7 @@ export default class CommitView extends React.Component { if (this.props.messageBuffer.getText().length > 0 && !this.isValidMessage()) { // there is likely a commit message template present // we want the cursor to be at the beginning, not at the and of the template - // todo: make sure this actually works - this.refEditorComponent.get().setCursorBufferPosition([0, 0]); + this.refEditorComponent.get().getModel().setCursorBufferPosition([0, 0]); } return true; } From 4fd8f1774a988f122284d36eef5dca13d44b08a6 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 26 Oct 2018 11:10:10 +0900 Subject: [PATCH 0653/4053] Update filter section --- docs/rfcs/004-multi-file-diff.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 0dd7d7ca20..da62d4e34a 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -70,7 +70,13 @@ All files collapsed | Some files collapsed ![all collapsed](https://user-images.githubusercontent.com/378023/47497741-0a0b3200-d896-11e8-90b5-4153009f80b4.png) | ![some collapsed](https://user-images.githubusercontent.com/378023/47498408-27410000-d898-11e8-8e4b-c02dafe7e35a.png) #### File filter for diff view -"Find" input field for filtering diffs based on search term (which could be a file name, an author, a variable name, etc). See [this section](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md#sort-options) in PR review RFC for more details. +"Find" input field for filtering diffs based on search term (which could be a file name, an author, a variable name, etc). When filtering, files that have no match get collapsed. This allows you to uncollapse files (and seeing their diff) without having to clear the filter. Matches get highlighted with a yellow overlay as well as a stripe on the side, similar to git-diff in the editor. + +Unfiltered | Filtered +--- | --- +![without filter](https://user-images.githubusercontent.com/378023/47497740-0a0b3200-d896-11e8-85af-7c644af9ca37.png) | ![with filter](https://user-images.githubusercontent.com/378023/47540019-116e2200-d90e-11e8-8d22-d305328d55c4.png) + +**Alternative**: It might be possible to re-use the find+replace UI to filter the multi-file diff. And maybe even have "replace" working. #### Other out of scope UX considerations - whether `cmd+click` to select multiple files is discoverable From 3b1759dd044b729229f3bae2b5c8b496cdcfb864 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 26 Oct 2018 17:13:03 +0900 Subject: [PATCH 0654/4053] Add mockups for staged & unstaged panes --- docs/rfcs/004-multi-file-diff.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index da62d4e34a..1b7cac0052 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -16,8 +16,7 @@ Note that the multi-diff view is the MVP of this RFC, and we have identified `Co - [commit pane item](#1655) where it shows all changes in a single commit - [new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) that shows all changed files proposed in a PR -- (TBD) multi-select files from unstaged & staged panes to view diffs - +- (TBD) multi-select files from [unstaged](https://user-images.githubusercontent.com/378023/47553451-1e0c6d80-d942-11e8-981b-3c51d7df98d6.png) & [staged](https://user-images.githubusercontent.com/378023/47553450-1e0c6d80-d942-11e8-8b0c-d6c1d6a09c83.png) panes to view diffs ## 🤯 Explanation From 38c602f4e609085177dc54fef4e0c327861002af Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 26 Oct 2018 17:16:21 +0900 Subject: [PATCH 0655/4053] Update unstaged changes mockup --- docs/rfcs/004-multi-file-diff.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 1b7cac0052..17ce75a996 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -16,7 +16,7 @@ Note that the multi-diff view is the MVP of this RFC, and we have identified `Co - [commit pane item](#1655) where it shows all changes in a single commit - [new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) that shows all changed files proposed in a PR -- (TBD) multi-select files from [unstaged](https://user-images.githubusercontent.com/378023/47553451-1e0c6d80-d942-11e8-981b-3c51d7df98d6.png) & [staged](https://user-images.githubusercontent.com/378023/47553450-1e0c6d80-d942-11e8-8b0c-d6c1d6a09c83.png) panes to view diffs +- (TBD) multi-select files from [unstaged](https://user-images.githubusercontent.com/378023/47553710-b60a5700-d942-11e8-8663-731b26d513c4.png) & [staged](https://user-images.githubusercontent.com/378023/47553450-1e0c6d80-d942-11e8-8b0c-d6c1d6a09c83.png) panes to view diffs ## 🤯 Explanation From c34242f11cfa5c74c1b71e6df7a9da3ae82670d4 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 26 Oct 2018 17:39:44 +0900 Subject: [PATCH 0656/4053] Update more mockups --- docs/rfcs/004-multi-file-diff.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 17ce75a996..7976bf59e7 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -16,15 +16,17 @@ Note that the multi-diff view is the MVP of this RFC, and we have identified `Co - [commit pane item](#1655) where it shows all changes in a single commit - [new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) that shows all changed files proposed in a PR -- (TBD) multi-select files from [unstaged](https://user-images.githubusercontent.com/378023/47553710-b60a5700-d942-11e8-8663-731b26d513c4.png) & [staged](https://user-images.githubusercontent.com/378023/47553450-1e0c6d80-d942-11e8-8b0c-d6c1d6a09c83.png) panes to view diffs +- (TBD) multi-select files from [unstaged](https://user-images.githubusercontent.com/378023/47553710-b60a5700-d942-11e8-8663-731b26d513c4.png) & [staged](https://user-images.githubusercontent.com/378023/47555145-0636e880-d946-11e8-85a7-f825278cc168.png) panes to view diffs ## 🤯 Explanation -![staged changes](https://user-images.githubusercontent.com/378023/47497740-0a0b3200-d896-11e8-85af-7c644af9ca37.png) +![commit preview](https://user-images.githubusercontent.com/378023/47555097-e6072980-d945-11e8-9c29-05624825d9f8.png) #### Commit preview button A new button added above the commit message box that, when clicked, opens a multi-file diff pane item called something like "Commit Preview" and shows a summary of what will go into the user's next commit based on what is currently staged. +![commit preview button](https://user-images.githubusercontent.com/378023/47554979-afc9aa00-d945-11e8-9953-45925e3278b9.png) + #### Multi-file diff view - Shows diffs of multiple files as a stack. From ac3dc04eb4159b79610a597cb84f9072c47d3142 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 26 Oct 2018 17:42:07 +0900 Subject: [PATCH 0657/4053] Move "Commit preview" mockup --- docs/rfcs/004-multi-file-diff.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 7976bf59e7..11163bd750 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -20,8 +20,6 @@ Note that the multi-diff view is the MVP of this RFC, and we have identified `Co ## 🤯 Explanation -![commit preview](https://user-images.githubusercontent.com/378023/47555097-e6072980-d945-11e8-9c29-05624825d9f8.png) - #### Commit preview button A new button added above the commit message box that, when clicked, opens a multi-file diff pane item called something like "Commit Preview" and shows a summary of what will go into the user's next commit based on what is currently staged. @@ -29,6 +27,8 @@ A new button added above the commit message box that, when clicked, opens a mult #### Multi-file diff view +![commit preview](https://user-images.githubusercontent.com/378023/47555097-e6072980-d945-11e8-9c29-05624825d9f8.png) + - Shows diffs of multiple files as a stack. - Each diff retains the file-specific controls it currently has in its header (e.g. the open file, stage file, undo last discard, etc). - **[[out of scope]](https://github.com/atom/github/blob/multi-diff-rfc/docs/rfcs/004-multi-file-diff.md#warning-out-of-scope)** It should be easy to jump quickly to a specific file you care about, or back to the file list to get to another file. Dotcom does so by creating a `jump to` drop down. From fa541a43a6c8626127e34d593c56c925df04e303 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 26 Oct 2018 18:57:14 +0900 Subject: [PATCH 0658/4053] Fix link --- docs/rfcs/004-multi-file-diff.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index 11163bd750..b601fb8013 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -14,7 +14,7 @@ So that users can view a full set of changes with more context before committing Note that the multi-diff view is the MVP of this RFC, and we have identified `Commit Preview` to be the least frictional way to introduce this feature without making too many UX changes. Other planned features that will also make use of multi-diff view are: -- [commit pane item](#1655) where it shows all changes in a single commit +- [commit pane item](https://github.com/atom/github/issues/1655) where it shows all changes in a single commit - [new PR review flow](https://github.com/atom/github/blob/master/docs/rfcs/003-pull-request-review.md) that shows all changed files proposed in a PR - (TBD) multi-select files from [unstaged](https://user-images.githubusercontent.com/378023/47553710-b60a5700-d942-11e8-8663-731b26d513c4.png) & [staged](https://user-images.githubusercontent.com/378023/47555145-0636e880-d946-11e8-85a7-f825278cc168.png) panes to view diffs From d33137223362f3c7b51c36baaa25e57ebb364602 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 26 Oct 2018 11:08:32 -0700 Subject: [PATCH 0659/4053] Retry flakey file patch test --- test/integration/file-patch.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index bbc627e85c..7341d3fcfb 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -732,6 +732,8 @@ describe('integration: file patches', function() { }); it('may be partially staged', async function() { + this.retries(5); // FLAKE + getPatchEditor('unstaged', 'sample.js').setSelectedBufferRanges([ [[2, 0], [2, 0]], [[10, 0], [10, 0]], From 255eeebe9d650e70a7668ab1c062c9760df1b5aa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 29 Oct 2018 13:09:04 -0400 Subject: [PATCH 0660/4053] Write up "dark shipping" and "feature flags" --- docs/core-team-process.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/core-team-process.md b/docs/core-team-process.md index 5a09e207fd..3ad86b96b7 100644 --- a/docs/core-team-process.md +++ b/docs/core-team-process.md @@ -69,6 +69,35 @@ In this method, each developer (or pair) tackles a single problem in serial duri * Overlap times need to be negotiated, either by pair programming or using another method to divvy up work. If we all overlap significantly it functionally decays to one of the other solutions. * Hand-offs are high communication touchpoints, but the rest of the time is more isolated. +### 4. Dark shipping + +Incrementally create and test new hierarchies of React components and model classes in pull requests that are merged _before_ they are referenced from the "live" package root. + +:+1: _Advantages:_ + +* Enables us to merge pull requests into master more frequently +* Keeps code reviews focused and tractable +* Prevents pull requests from drifting too far from master and being a pain to merge + +:-1: _Disadvantages:_ + +* May cause an accumulation of dead code +* The merge points may not be obvious in some efforts + +### 5. Feature flags + +Use a package configuration setting to control when features under development are loaded. + +:+1: _Advantages:_ + +* Enables us to merge pull requests into master more frequently +* Makes it easier for developers outside of the core team to try out new features and provide feedback + +:-1: _Disadvantages:_ + +* Requires some up-front infrastructure work to put the mechanisms in place +* Needs some discipline in removing old code, so we don't accumulate flags without bound + ## Ambient socialization In addition to these strategies, we can take advantage of other technologies to help us feel connected in an ambient way. From ebce578ff26213e5aee47fec7614383e576846dc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 29 Oct 2018 13:11:04 -0400 Subject: [PATCH 0661/4053] Plus one downside to pull request hierarchies --- docs/core-team-process.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/core-team-process.md b/docs/core-team-process.md index 3ad86b96b7..4ba6452da6 100644 --- a/docs/core-team-process.md +++ b/docs/core-team-process.md @@ -51,6 +51,7 @@ The problem at hand is decomposed into a queue of relatively independent tasks t * Decomposing tasks well is challenging. * Less communication-friendly; we risk a developer on a long-running task feeling isolated. +* Merging closely related pull requests requires careful coordination. Merge conflicts will be frequent. ### 3. Hand-offs From c436f8740be5245616c324c42f308da3c6f98a24 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 29 Oct 2018 13:24:34 -0400 Subject: [PATCH 0662/4053] All together now --- docs/core-team-process.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/core-team-process.md b/docs/core-team-process.md index 4ba6452da6..5559ab93a7 100644 --- a/docs/core-team-process.md +++ b/docs/core-team-process.md @@ -16,6 +16,8 @@ When we plan, we choose to pursue _a single task_ as a single team, rather than This does not mean that we all pair program all the time. We do get value from pair programming but this is not always practical or desirable. Pair programming may be chosen independently from the methods below -- functionally, the pair becomes one "developer" in any of the descriptions. +## Concepts + ### 1. Seams Divide the issue at hand among the team along the abstraction layers in our codebase. Each developer continuously negotiates the interface with neighboring layers by an active Slack conversation, correcting their direction based on feedback. Developers push their work as commits to a single shared branch, documenting and coordinating overall progress in a shared pull request. @@ -99,6 +101,22 @@ Use a package configuration setting to control when features under development a * Requires some up-front infrastructure work to put the mechanisms in place * Needs some discipline in removing old code, so we don't accumulate flags without bound +## All together + +Each set of developers who are online synchronously can divide work into Seams. As that set changes when people come online and drop offline, we use Handoffs to pass context along. + +As we work, we push commits to a common branch, against a common pull request. Depending on the feature under construction, we either Dark Ship code in an early state or hide its entry points behind a Feature Flag. + +For a concrete example: + +1. Developer A comes online first and works solo for a few hours, shifting up and down the abstraction stack. +2. When developer B comes online, they get caught up on the work developer A has pushed so far and chats to sync up on progress. Developers A and B divvy up areas of work to focus on for the next few hours, chatting in Slack as they go. +3. Developers C and D come online next. Developers A and B bring them up to speed and subdivide the work underway further. Maybe C and D pair on the view work while A and B work on the model and controller. +4. When D is done for the day, they summarize how far they got on their bit. One of the other three catches up, picks up where D left off, and keeps it going. C does the same when they log off. +5. When A and B are finishing up they leave a quick writeup of their collective progress. +7. The next morning, developer A reads the diff and the writeup and gets traction on continuing through their day. +8. ...and repeat. ♻️ + ## Ambient socialization In addition to these strategies, we can take advantage of other technologies to help us feel connected in an ambient way. From 04cb55aee22b13e2e78a1e220066c89eace386f1 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 29 Oct 2018 11:11:49 -0700 Subject: [PATCH 0663/4053] Add note for community members --- docs/rfcs/000-template.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/rfcs/000-template.md b/docs/rfcs/000-template.md index 576ef91628..36a5f157f2 100644 --- a/docs/rfcs/000-template.md +++ b/docs/rfcs/000-template.md @@ -1,3 +1,10 @@ + + +Part 1 +__________________ + # Feature title ## Status @@ -21,6 +28,9 @@ Explain the proposal as if it was already implemented in the GitHub package and - Explaining any changes to existing workflows. - Design mock-ups or diagrams depicting any new UI that will be introduced. +Part 2 +__________________ + ## Drawbacks Why should we *not* do this? From 0db18af8adbe8064a73e303bdc05d65e52c55bf6 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 29 Oct 2018 11:14:09 -0700 Subject: [PATCH 0664/4053] :art: Part 1 and Part 2 headers --- docs/rfcs/000-template.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/rfcs/000-template.md b/docs/rfcs/000-template.md index 36a5f157f2..9dc134a684 100644 --- a/docs/rfcs/000-template.md +++ b/docs/rfcs/000-template.md @@ -2,8 +2,7 @@ For community contributors -- Please fill out Part 1 of the following template. This will help our team collaborate with you and give us an opportunity to provide valuable feedback that could inform your development process. Sections in Part 2 are not mandatory to get the conversation started, but will help our team understand your vision better and allow us to give better feedback. ---> -Part 1 -__________________ +**_Part 1 - Required information_** # Feature title @@ -28,8 +27,8 @@ Explain the proposal as if it was already implemented in the GitHub package and - Explaining any changes to existing workflows. - Design mock-ups or diagrams depicting any new UI that will be introduced. -Part 2 -__________________ + +**_Part 2 - Additional information_** ## Drawbacks From 32e6984bf925e209746edbc9f7cf1c58b9aa98d8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 29 Oct 2018 15:35:49 -0400 Subject: [PATCH 0665/4053] Flakes reported in #761 --- test/github-package.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/github-package.test.js b/test/github-package.test.js index 8cc03f8fe1..5d1c0fbf87 100644 --- a/test/github-package.test.js +++ b/test/github-package.test.js @@ -608,6 +608,8 @@ describe('GithubPackage', function() { let workdirPath2, atomGitRepository2, repository2; beforeEach(async function() { + this.retries(5); // FLAKE + [workdirPath1, workdirPath2] = await Promise.all([ cloneRepository('three-files'), cloneRepository('three-files'), @@ -645,6 +647,7 @@ describe('GithubPackage', function() { if (process.platform === 'linux') { this.skip(); } + this.retries(5); // FLAKE fs.writeFileSync(path.join(workdirPath1, 'a.txt'), 'some changes', 'utf8'); @@ -656,6 +659,7 @@ describe('GithubPackage', function() { if (process.platform === 'linux') { this.skip(); } + this.retries(5); // FLAKE fs.writeFileSync(path.join(workdirPath2, 'b.txt'), 'other changes', 'utf8'); From 8d8fe0ba6a19224336db9b5aedf060b4bc615051 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 29 Oct 2018 15:36:42 -0400 Subject: [PATCH 0666/4053] Reported as #1743 --- test/models/file-system-change-observer.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/models/file-system-change-observer.test.js b/test/models/file-system-change-observer.test.js index eb07bcf1c1..59c7d45d32 100644 --- a/test/models/file-system-change-observer.test.js +++ b/test/models/file-system-change-observer.test.js @@ -25,6 +25,8 @@ describe('FileSystemChangeObserver', function() { }); it('emits an event when a project file is modified, created, or deleted', async function() { + this.retries(5); // FLAKE + const workdirPath = await cloneRepository('three-files'); const repository = await buildRepository(workdirPath); observer = createObserver(repository); From f3a2c7dbac1f76c655b4682eb5257bf90001e59c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 29 Oct 2018 15:38:08 -0400 Subject: [PATCH 0667/4053] Reported as #1698 --- test/git-strategies.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 686138469d..0d3f075a07 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1457,6 +1457,8 @@ import * as reporterProxy from '../lib/reporter-proxy'; } it('prompts for authentication data through Atom', async function() { + this.retries(5); // FLAKE + let query = null; const git = await withHttpRemote({ prompt: q => { From a46d41f84d757e3717581d89a6e7b2c226b7d700 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 29 Oct 2018 15:38:42 -0400 Subject: [PATCH 0668/4053] Reported as #1688 --- test/worker-manager.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/worker-manager.test.js b/test/worker-manager.test.js index 82f5d9d251..3c96eba874 100644 --- a/test/worker-manager.test.js +++ b/test/worker-manager.test.js @@ -149,6 +149,8 @@ describe('WorkerManager', function() { describe('when the manager process is destroyed', function() { it('destroys all the renderer processes that were created', async function() { + this.retries(5); // FLAKE + const browserWindow = new BrowserWindow({show: !!process.env.ATOM_GITHUB_SHOW_RENDERER_WINDOW}); browserWindow.loadURL('about:blank'); sinon.stub(Worker.prototype, 'getWebContentsId').returns(browserWindow.webContents.id); From f50a3d527b6eadaf8d4df9f0238a49a4826d1f44 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 29 Oct 2018 15:41:26 -0400 Subject: [PATCH 0669/4053] Reported as #731 --- test/git-strategies.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 0d3f075a07..635b09dafd 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1518,6 +1518,8 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); it('prefers user-configured credential helpers if present', async function() { + this.retries(5); // FLAKE + let query = null; const git = await withHttpRemote({ prompt: q => { From 4826e20a8803b189f830b85fe7efcd9cbab0802a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 26 Oct 2018 11:08:32 -0700 Subject: [PATCH 0670/4053] Retry flakey file patch test --- test/integration/file-patch.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index bbc627e85c..7341d3fcfb 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -732,6 +732,8 @@ describe('integration: file patches', function() { }); it('may be partially staged', async function() { + this.retries(5); // FLAKE + getPatchEditor('unstaged', 'sample.js').setSelectedBufferRanges([ [[2, 0], [2, 0]], [[10, 0], [10, 0]], From 13e7342a93a446443917c86fe6fcc9b5361663d7 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 29 Oct 2018 15:34:26 -0700 Subject: [PATCH 0671/4053] extract regex and give it a better name Co-Authored-By: Katrina Uychaco --- lib/git-shell-out-strategy.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index a2ae737a09..ffd642341c 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -53,6 +53,17 @@ const DISABLE_COLOR_FLAGS = [ return acc; }, []); +/** + * Expand config path name per + * https://git-scm.com/docs/git-config#git-config-pathname + * this regex attempts to get the specified user's home directory + * Ex: on Mac ~kuychaco/ is expanded to the specified user’s home directory (/Users/kuychaco) + * Regex translation: + * ^~ line starts with tilde + * ([^/]*)/ captures non-forwardslash characters before first slash + */ +const EXPAND_TILDE_REGEX = new RegExp('^~([^/]*)/'); + export default class GitShellOutStrategy { static defaultExecArgs = { stdin: null, @@ -428,19 +439,9 @@ export default class GitShellOutStrategy { return null; } - /** - * Get absolute path from git path - * https://git-scm.com/docs/git-config#git-config-pathname - */ const homeDir = os.homedir(); - // this regex attempts to get the specified user's home directory - // Ex: on Mac ~kuychaco/ is expanded to the specified user’s home directory (/Users/kuychaco) - - // Regex translation: - // ^~ line starts with tilde - // ([^/]*)/ captures non-forwardslash characters before first slash - const regex = new RegExp('^~([^/]*)/'); - templatePath = templatePath.trim().replace(regex, (_, user) => { + + templatePath = templatePath.trim().replace(EXPAND_TILDE_REGEX, (_, user) => { // if no user is specified, fall back to using the home directory. return `${user ? path.join(path.dirname(homeDir), user) : homeDir}/`; }); From 218f193541b7df0c746cb239da3e5982173c442e Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 29 Oct 2018 15:51:04 -0700 Subject: [PATCH 0672/4053] rename getCommitMessageTemplate to fetchCommitMessageTemplate Co-Authored-By: Katrina Uychaco --- lib/get-repo-pipeline-manager.js | 2 +- lib/git-shell-out-strategy.js | 2 +- lib/models/repository-states/present.js | 8 ++++---- lib/models/repository-states/state.js | 4 ++-- lib/models/repository.js | 2 +- test/git-strategies.test.js | 8 ++++---- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/get-repo-pipeline-manager.js b/lib/get-repo-pipeline-manager.js index b153920ddc..365fc8dfd1 100644 --- a/lib/get-repo-pipeline-manager.js +++ b/lib/get-repo-pipeline-manager.js @@ -210,7 +210,7 @@ export default function({confirm, notificationManager, workspace}) { commitPipeline.addMiddleware('failed-to-commit-error', async (next, repository) => { try { const result = await next(); - const template = await repository.getCommitMessageTemplate(); + const template = await repository.fetchCommitMessageTemplate(); repository.setCommitMessage(template || ''); destroyFilePatchPaneItems({onlyStaged: true}, workspace); return result; diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index ffd642341c..f88979db98 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -433,7 +433,7 @@ export default class GitShellOutStrategy { return this.exec(args, {writeOperation: true}); } - async getCommitMessageTemplate() { + async fetchCommitMessageTemplate() { let templatePath = await this.getConfig('commit.template'); if (!templatePath) { return null; diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 14af4ae5ac..2aa24b3449 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -60,7 +60,7 @@ export default class Present extends State { async fetchInitialMessage() { const mergeMessage = await this.repository.getMergeMessage(); - const template = await this.getCommitMessageTemplate(); + const template = await this.fetchCommitMessageTemplate(); if (template) { this.commitMessageTemplate = template; } @@ -75,8 +75,8 @@ export default class Present extends State { return this.commitMessage; } - getCommitMessageTemplate() { - return this.git().getCommitMessageTemplate(); + fetchCommitMessageTemplate() { + return this.git().fetchCommitMessageTemplate(); } getOperationStates() { @@ -197,7 +197,7 @@ export default class Present extends State { if (endsWith('.git', 'config')) { // this won't catch changes made to the template file itself... - const template = await this.getCommitMessageTemplate(); + const template = await this.fetchCommitMessageTemplate(); if (this.commitMessageTemplate !== template) { this.setCommitMessageTemplate(template); this.setCommitMessage(template); diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 74d9a14c19..7e3e88988f 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -374,8 +374,8 @@ export default class State { return ''; } - getCommitMessageTemplate() { - return unsupportedOperationPromise(this, 'getCommitMessageTemplate'); + fetchCommitMessageTemplate() { + return unsupportedOperationPromise(this, 'fetchCommitMessageTemplate'); } // Cache diff --git a/lib/models/repository.js b/lib/models/repository.js index ef54885474..8dc10b022a 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -358,7 +358,7 @@ const delegates = [ 'setCommitMessage', 'getCommitMessage', - 'getCommitMessageTemplate', + 'fetchCommitMessageTemplate', 'getCache', ]; diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 9d0ccce2f4..dbb4ab3536 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -85,7 +85,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); }); - describe('getCommitMessageTemplate', function() { + describe('fetchCommitMessageTemplate', function() { it('gets commit message from template', async function() { const workingDirPath = await cloneRepository('three-files'); const git = createTestStrategy(workingDirPath); @@ -95,7 +95,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; await fs.writeFile(commitMsgTemplatePath, templateText, {encoding: 'utf8'}); await git.setConfig('commit.template', commitMsgTemplatePath); - assert.equal(await git.getCommitMessageTemplate(), templateText); + assert.equal(await git.fetchCommitMessageTemplate(), templateText); }); it('if config is not set return null', async function() { @@ -103,7 +103,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; const git = createTestStrategy(workingDirPath); assert.isNotOk(await git.getConfig('commit.template')); // falsy value of null or '' - assert.isNull(await git.getCommitMessageTemplate()); + assert.isNull(await git.fetchCommitMessageTemplate()); }); it('if config is set but file does not exist throw an error', async function() { @@ -113,7 +113,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; const nonExistentCommitTemplatePath = path.join(workingDirPath, 'file-that-doesnt-exist'); await git.setConfig('commit.template', nonExistentCommitTemplatePath); await assert.isRejected( - git.getCommitMessageTemplate(), + git.fetchCommitMessageTemplate(), `Invalid commit template path set in Git config: ${nonExistentCommitTemplatePath}`, ); }); From e2cc423d253fb785bf318954c79fa486b250b38c Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 29 Oct 2018 16:07:20 -0700 Subject: [PATCH 0673/4053] extract filePathEndsWith into helper function --- lib/helpers.js | 3 +++ lib/models/repository-states/present.js | 13 ++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/helpers.js b/lib/helpers.js index 37f1854de1..def4f8d70c 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -320,6 +320,9 @@ export function toGitPathSep(rawPath) { } } +export function filePathEndsWith(filePath, ...segments) { + return filePath.endsWith(path.join(...segments)); +} /** * Turns an array of things @kuychaco cannot eat diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 2aa24b3449..7cd9e196c3 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -16,6 +16,7 @@ import RemoteSet from '../remote-set'; import Commit from '../commit'; import OperationStates from '../operation-states'; import {addEvent} from '../../reporter-proxy'; +import {filePathEndsWith} from '../../helpers'; /** * State used when the working directory contains a valid git repository and can be interacted with. Performs @@ -115,10 +116,9 @@ export default class Present extends State { continue; } - const endsWith = (...segments) => fullPath.endsWith(path.join(...segments)); const includes = (...segments) => fullPath.includes(path.join(...segments)); - if (endsWith('.git', 'index')) { + if (filePathEndsWith(fullPath, '.git', 'index')) { keys.add(Keys.stagedChangesSinceParentCommit); keys.add(Keys.filePatch.all); keys.add(Keys.index.all); @@ -126,7 +126,7 @@ export default class Present extends State { continue; } - if (endsWith('.git', 'HEAD')) { + if (filePathEndsWith(fullPath, '.git', 'HEAD')) { keys.add(Keys.branches); keys.add(Keys.lastCommit); keys.add(Keys.recentCommits); @@ -152,7 +152,7 @@ export default class Present extends State { continue; } - if (endsWith('.git', 'config')) { + if (filePathEndsWith(fullPath, '.git', 'config')) { keys.add(Keys.remotes); keys.add(Keys.config.all); keys.add(Keys.statusBundle); @@ -183,9 +183,8 @@ export default class Present extends State { async updateCommitMessageAfterFileSystemChange(events) { for (let i = 0; i < events.length; i++) { const event = events[i]; - const endsWith = (...segments) => event.path.endsWith(path.join(...segments)); - if (endsWith('.git', 'MERGE_HEAD')) { + if (filePathEndsWith(event.path, '.git', 'MERGE_HEAD')) { if (event.action === 'created') { if (this.isCommitMessageClean()) { this.setCommitMessage(await this.repository.getMergeMessage()); @@ -195,7 +194,7 @@ export default class Present extends State { } } - if (endsWith('.git', 'config')) { + if (filePathEndsWith(event.path, '.git', 'config')) { // this won't catch changes made to the template file itself... const template = await this.fetchCommitMessageTemplate(); if (this.commitMessageTemplate !== template) { From b3c9ec74112253b3561744fe5c3032dae0e1c234 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 29 Oct 2018 19:01:04 -0700 Subject: [PATCH 0674/4053] Extract wireUpObserver and expectEvents to test helpers file Co-Authored-By: Tilde Ann Thurium --- test/helpers.js | 62 +++++++++- test/models/repository.test.js | 201 +++++++++------------------------ 2 files changed, 115 insertions(+), 148 deletions(-) diff --git a/test/helpers.js b/test/helpers.js index 4c58f0475a..4ed120522a 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -8,7 +8,7 @@ import React from 'react'; import ReactDom from 'react-dom'; import sinon from 'sinon'; import {Directory} from 'atom'; -import {Emitter} from 'event-kit'; +import {Emitter, CompositeDisposable, Disposable} from 'event-kit'; import Repository from '../lib/models/repository'; import GitShellOutStrategy from '../lib/git-shell-out-strategy'; @@ -16,6 +16,7 @@ import WorkerManager from '../lib/worker-manager'; import ContextMenuInterceptor from '../lib/context-menu-interceptor'; import getRepoPipelineManager from '../lib/get-repo-pipeline-manager'; import {clearRelayExpectations} from '../lib/relay-network-layer-manager'; +import FileSystemChangeObserver from '../lib/models/file-system-change-observer'; assert.autocrlfEqual = (actual, expected, ...args) => { const newActual = actual.replace(/\r\n/g, '\n'); @@ -382,3 +383,62 @@ export class ManualStateObserver { this.emitter.dispose(); } } + + +// File system event helpers +let observedEvents, eventCallback; + +export async function wireUpObserver(fixtureName = 'multi-commits-files', existingWorkdir = null) { + observedEvents = []; + eventCallback = () => {}; + + const workdir = existingWorkdir || await cloneRepository(fixtureName); + const repository = new Repository(workdir); + await repository.getLoadPromise(); + + const observer = new FileSystemChangeObserver(repository); + + const subscriptions = new CompositeDisposable( + new Disposable(async () => { + await observer.destroy(); + repository.destroy(); + }), + ); + + subscriptions.add(observer.onDidChange(events => { + observedEvents.push(...events); + eventCallback(); + })); + + return {repository, observer, subscriptions}; +} + +export function expectEvents(repository, ...suffixes) { + const pending = new Set(suffixes); + return new Promise((resolve, reject) => { + eventCallback = () => { + const matchingPaths = observedEvents + .filter(event => { + for (const suffix of pending) { + if (event.path.endsWith(suffix)) { + pending.delete(suffix); + return true; + } + } + return false; + }); + + if (matchingPaths.length > 0) { + repository.observeFilesystemChange(matchingPaths); + } + + if (pending.size === 0) { + resolve(); + } + }; + + if (observedEvents.length > 0) { + eventCallback(); + } + }); +} diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 6194cc26d9..1a60416bff 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -4,18 +4,16 @@ import dedent from 'dedent-js'; import temp from 'temp'; import compareSets from 'compare-sets'; import isEqual from 'lodash.isequal'; -import {CompositeDisposable, Disposable} from 'event-kit'; import Repository from '../../lib/models/repository'; import {nullCommit} from '../../lib/models/commit'; import {nullOperationStates} from '../../lib/models/operation-states'; -import FileSystemChangeObserver from '../../lib/models/file-system-change-observer'; import Author from '../../lib/models/author'; import * as reporterProxy from '../../lib/reporter-proxy'; import { cloneRepository, setUpLocalAndRemoteRepositories, getHeadCommitOnRemote, - assertDeepPropertyVals, assertEqualSortedArraysByKey, FAKE_USER, + assertDeepPropertyVals, assertEqualSortedArraysByKey, FAKE_USER, wireUpObserver, expectEvents, } from '../helpers'; import {getPackageRoot, getTempDir} from '../../lib/helpers'; @@ -1833,72 +1831,17 @@ describe('Repository', function() { }); describe('from filesystem events', function() { - let workdir, sub; - let observedEvents, eventCallback; - - async function wireUpObserver(fixtureName = 'multi-commits-files', existingWorkdir = null) { - observedEvents = []; - eventCallback = () => {}; - - workdir = existingWorkdir || await cloneRepository(fixtureName); - const repository = new Repository(workdir); - await repository.getLoadPromise(); - - const observer = new FileSystemChangeObserver(repository); - - sub = new CompositeDisposable( - new Disposable(async () => { - await observer.destroy(); - repository.destroy(); - }), - ); - - sub.add(observer.onDidChange(events => { - observedEvents.push(...events); - eventCallback(); - })); - - return {repository, observer}; - } - - function expectEvents(repository, ...suffixes) { - const pending = new Set(suffixes); - return new Promise((resolve, reject) => { - eventCallback = () => { - const matchingPaths = observedEvents - .filter(event => { - for (const suffix of pending) { - if (event.path.endsWith(suffix)) { - pending.delete(suffix); - return true; - } - } - return false; - }); - - if (matchingPaths.length > 0) { - repository.observeFilesystemChange(matchingPaths); - } - - if (pending.size === 0) { - resolve(); - } - }; - - if (observedEvents.length > 0) { - eventCallback(); - } - }); - } + let sub; afterEach(function() { sub && sub.dispose(); }); it('when staging files', async function() { - const {repository, observer} = await wireUpObserver(); + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; - await fs.writeFile(path.join(workdir, 'a.txt'), 'boop\n', {encoding: 'utf8'}); + await fs.writeFile(path.join(repository.getWorkingDirectoryPath(), 'a.txt'), 'boop\n', {encoding: 'utf8'}); await assertCorrectInvalidation({repository}, async () => { await observer.start(); @@ -1908,9 +1851,10 @@ describe('Repository', function() { }); it('when unstaging files', async function() { - const {repository, observer} = await wireUpObserver(); + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; - await fs.writeFile(path.join(workdir, 'a.txt'), 'boop\n', {encoding: 'utf8'}); + await fs.writeFile(path.join(repository.getWorkingDirectoryPath(), 'a.txt'), 'boop\n', {encoding: 'utf8'}); await repository.git.stageFiles(['a.txt']); await assertCorrectInvalidation({repository}, async () => { @@ -1921,7 +1865,8 @@ describe('Repository', function() { }); it('when staging files from a parent commit', async function() { - const {repository, observer} = await wireUpObserver(); + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; await assertCorrectInvalidation({repository}, async () => { await observer.start(); @@ -1931,9 +1876,10 @@ describe('Repository', function() { }); it('when applying a patch to the index', async function() { - const {repository, observer} = await wireUpObserver(); + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; - await fs.writeFile(path.join(workdir, 'a.txt'), 'boop\n', {encoding: 'utf8'}); + await fs.writeFile(path.join(repository.getWorkingDirectoryPath(), 'a.txt'), 'boop\n', {encoding: 'utf8'}); const patch = await repository.getFilePatchForPath('a.txt'); await assertCorrectInvalidation({repository}, async () => { @@ -1947,9 +1893,10 @@ describe('Repository', function() { }); it('when applying a patch to the working directory', async function() { - const {repository, observer} = await wireUpObserver(); + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; - await fs.writeFile(path.join(workdir, 'a.txt'), 'boop\n', {encoding: 'utf8'}); + await fs.writeFile(path.join(repository.getWorkingDirectoryPath(), 'a.txt'), 'boop\n', {encoding: 'utf8'}); const patch = (await repository.getFilePatchForPath('a.txt')).getUnstagePatchForLines(new Set([0])); await assertCorrectInvalidation({repository}, async () => { @@ -1963,9 +1910,10 @@ describe('Repository', function() { }); it('when committing', async function() { - const {repository, observer} = await wireUpObserver(); + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; - await fs.writeFile(path.join(workdir, 'a.txt'), 'boop\n', {encoding: 'utf8'}); + await fs.writeFile(path.join(repository.getWorkingDirectoryPath(), 'a.txt'), 'boop\n', {encoding: 'utf8'}); await repository.stageFiles(['a.txt']); await assertCorrectInvalidation({repository}, async () => { @@ -1980,7 +1928,8 @@ describe('Repository', function() { }); it('when merging', async function() { - const {repository, observer} = await wireUpObserver('merge-conflict'); + const {repository, observer, subscriptions} = await wireUpObserver('merge-conflict'); + sub = subscriptions; await assertCorrectInvalidation({repository}, async () => { await observer.start(); @@ -1995,7 +1944,8 @@ describe('Repository', function() { }); it('when aborting a merge', async function() { - const {repository, observer} = await wireUpObserver('merge-conflict'); + const {repository, observer, subscriptions} = await wireUpObserver('merge-conflict'); + sub = subscriptions; await assert.isRejected(repository.merge('origin/branch')); await assertCorrectInvalidation({repository}, async () => { @@ -2011,7 +1961,8 @@ describe('Repository', function() { }); it('when checking out a revision', async function() { - const {repository, observer} = await wireUpObserver(); + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; await assertCorrectInvalidation({repository}, async () => { await observer.start(); @@ -2027,7 +1978,8 @@ describe('Repository', function() { }); it('when checking out paths', async function() { - const {repository, observer} = await wireUpObserver(); + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; await assertCorrectInvalidation({repository}, async () => { await observer.start(); @@ -2042,7 +1994,8 @@ describe('Repository', function() { it('when fetching', async function() { const {localRepoPath} = await setUpLocalAndRemoteRepositories({remoteAhead: true}); - const {repository, observer} = await wireUpObserver(null, localRepoPath); + const {repository, observer, subscriptions} = await wireUpObserver(null, localRepoPath); + sub = subscriptions; await repository.commit('wat', {allowEmpty: true}); await repository.commit('huh', {allowEmpty: true}); @@ -2059,7 +2012,8 @@ describe('Repository', function() { it('when pulling', async function() { const {localRepoPath} = await setUpLocalAndRemoteRepositories({remoteAhead: true}); - const {repository, observer} = await wireUpObserver(null, localRepoPath); + const {repository, observer, subscriptions} = await wireUpObserver(null, localRepoPath); + sub = subscriptions; await fs.writeFile(path.join(localRepoPath, 'file.txt'), 'one\n', {encoding: 'utf8'}); await repository.stageFiles(['file.txt']); @@ -2080,7 +2034,8 @@ describe('Repository', function() { it('when pushing', async function() { const {localRepoPath} = await setUpLocalAndRemoteRepositories(); - const {repository, observer} = await wireUpObserver(null, localRepoPath); + const {repository, observer, subscriptions} = await wireUpObserver(null, localRepoPath); + sub = subscriptions; await fs.writeFile(path.join(localRepoPath, 'new-file.txt'), 'one\n', {encoding: 'utf8'}); await repository.stageFiles(['new-file.txt']); @@ -2097,7 +2052,8 @@ describe('Repository', function() { }); it('when setting a config option', async function() { - const {repository, observer} = await wireUpObserver(); + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; const optionNames = ['core.editor', 'color.ui']; await assertCorrectInvalidation({repository, optionNames}, async () => { @@ -2111,11 +2067,12 @@ describe('Repository', function() { }); it('when changing files in the working directory', async function() { - const {repository, observer} = await wireUpObserver(); + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; await assertCorrectInvalidation({repository}, async () => { await observer.start(); - await fs.writeFile(path.join(workdir, 'b.txt'), 'new contents\n', {encoding: 'utf8'}); + await fs.writeFile(path.join(repository.getWorkingDirectoryPath(), 'b.txt'), 'new contents\n', {encoding: 'utf8'}); await expectEvents( repository, 'b.txt', @@ -2126,63 +2083,7 @@ describe('Repository', function() { }); describe('updating commit message', function() { - let workdir, sub; - let observedEvents, eventCallback; - - async function wireUpObserver(fixtureName = 'multi-commits-files', existingWorkdir = null) { - observedEvents = []; - eventCallback = () => {}; - - workdir = existingWorkdir || await cloneRepository(fixtureName); - const repository = new Repository(workdir); - await repository.getLoadPromise(); - - const observer = new FileSystemChangeObserver(repository); - - sub = new CompositeDisposable( - new Disposable(async () => { - await observer.destroy(); - repository.destroy(); - }), - ); - - sub.add(observer.onDidChange(events => { - observedEvents.push(...events); - eventCallback(); - })); - - return {repository, observer}; - } - - function expectEvents(repository, ...suffixes) { - const pending = new Set(suffixes); - return new Promise((resolve, reject) => { - eventCallback = () => { - const matchingPaths = observedEvents - .filter(event => { - for (const suffix of pending) { - if (event.path.endsWith(suffix)) { - pending.delete(suffix); - return true; - } - } - return false; - }); - - if (matchingPaths.length > 0) { - repository.observeFilesystemChange(matchingPaths); - } - - if (pending.size === 0) { - resolve(); - } - }; - - if (observedEvents.length > 0) { - eventCallback(); - } - }); - } + let sub; afterEach(function() { sub && sub.dispose(); @@ -2190,12 +2091,13 @@ describe('Repository', function() { describe('config commit.template change', function() { it('updates commit messages to new template', async function() { - const {repository, observer} = await wireUpObserver(); + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; await observer.start(); assert.strictEqual(repository.getCommitMessage(), ''); - const templatePath = path.join(workdir, 'a.txt'); + const templatePath = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); await repository.git.setConfig('commit.template', templatePath); await expectEvents( repository, @@ -2208,7 +2110,8 @@ describe('Repository', function() { describe('merge events', function() { describe('when commit message is empty', function() { it('merge message is set as new commit message', async function() { - const {repository, observer} = await wireUpObserver('merge-conflict'); + const {repository, observer, subscriptions} = await wireUpObserver('merge-conflict'); + sub = subscriptions; await observer.start(); assert.strictEqual(repository.getCommitMessage(), ''); @@ -2223,10 +2126,11 @@ describe('Repository', function() { describe('when commit message contains unmodified template', function() { it('merge message is set as new commit message', async function() { - const {repository, observer} = await wireUpObserver('merge-conflict'); + const {repository, observer, subscriptions} = await wireUpObserver('merge-conflict'); + sub = subscriptions; await observer.start(); - const templatePath = path.join(workdir, 'added-to-both.txt'); + const templatePath = path.join(repository.getWorkingDirectoryPath(), 'added-to-both.txt'); const templateText = fs.readFileSync(templatePath, 'utf8'); await repository.git.setConfig('commit.template', templatePath); await expectEvents( @@ -2247,7 +2151,8 @@ describe('Repository', function() { describe('when commit message is "dirty"', function() { it('leaves commit message as is', async function() { - const {repository, observer} = await wireUpObserver('merge-conflict'); + const {repository, observer, subscriptions} = await wireUpObserver('merge-conflict'); + sub = subscriptions; await observer.start(); const dirtyMessage = 'foo bar baz'; @@ -2263,7 +2168,8 @@ describe('Repository', function() { describe('when merge is aborted', function() { it('merge message gets cleared', async function() { - const {repository, observer} = await wireUpObserver('merge-conflict'); + const {repository, observer, subscriptions} = await wireUpObserver('merge-conflict'); + sub = subscriptions; await observer.start(); await assert.isRejected(repository.git.merge('origin/branch')); await expectEvents( @@ -2283,10 +2189,11 @@ describe('Repository', function() { describe('when commit message template is present', function() { it('sets template as commit message', async function() { - const {repository, observer} = await wireUpObserver('merge-conflict'); + const {repository, observer, subscriptions} = await wireUpObserver('merge-conflict'); + sub = subscriptions; await observer.start(); - const templatePath = path.join(workdir, 'added-to-both.txt'); + const templatePath = path.join(repository.getWorkingDirectoryPath(), 'added-to-both.txt'); const templateText = fs.readFileSync(templatePath, 'utf8'); await repository.git.setConfig('commit.template', templatePath); await expectEvents( From 37fbb445ac1b883c773adbaa3e4eae3bbd73377e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 29 Oct 2018 19:01:52 -0700 Subject: [PATCH 0675/4053] Access local config in spec mode Tests were failing because my global config was being used to look up the commit template --- lib/git-shell-out-strategy.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index f88979db98..ef573aa9f1 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -23,6 +23,7 @@ import GitTimingsView from './views/git-timings-view'; import WorkerManager from './worker-manager'; const MAX_STATUS_OUTPUT_LENGTH = 1024 * 1024 * 10; +const SPEC_MODE = atom.inSpecMode(); let headless = null; let execPathPromise = null; @@ -182,7 +183,7 @@ export default class GitShellOutStrategy { env.ATOM_GITHUB_ORIGINAL_GIT_ASKPASS = process.env.GIT_ASKPASS || ''; env.ATOM_GITHUB_ORIGINAL_SSH_ASKPASS = process.env.SSH_ASKPASS || ''; env.ATOM_GITHUB_ORIGINAL_GIT_SSH_COMMAND = process.env.GIT_SSH_COMMAND || ''; - env.ATOM_GITHUB_SPEC_MODE = atom.inSpecMode() ? 'true' : 'false'; + env.ATOM_GITHUB_SPEC_MODE = SPEC_MODE ? 'true' : 'false'; env.SSH_ASKPASS = normalizeGitHelperPath(gitTempDir.getAskPassSh()); env.GIT_ASKPASS = normalizeGitHelperPath(gitTempDir.getAskPassSh()); @@ -923,7 +924,7 @@ export default class GitShellOutStrategy { let output; try { let args = ['config']; - if (local) { args.push('--local'); } + if (local || SPEC_MODE) { args.push('--local'); } args = args.concat(option); output = await this.exec(args); } catch (err) { From 6299ae248c7aa36eece71682e7f263fcbde0dff0 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 29 Oct 2018 19:18:56 -0700 Subject: [PATCH 0676/4053] Don't break snapshotting by accessing `atom` in global scope Co-Authored-By: Ash Wilson --- lib/git-shell-out-strategy.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index ef573aa9f1..7c8ceb5187 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -23,7 +23,6 @@ import GitTimingsView from './views/git-timings-view'; import WorkerManager from './worker-manager'; const MAX_STATUS_OUTPUT_LENGTH = 1024 * 1024 * 10; -const SPEC_MODE = atom.inSpecMode(); let headless = null; let execPathPromise = null; @@ -183,7 +182,7 @@ export default class GitShellOutStrategy { env.ATOM_GITHUB_ORIGINAL_GIT_ASKPASS = process.env.GIT_ASKPASS || ''; env.ATOM_GITHUB_ORIGINAL_SSH_ASKPASS = process.env.SSH_ASKPASS || ''; env.ATOM_GITHUB_ORIGINAL_GIT_SSH_COMMAND = process.env.GIT_SSH_COMMAND || ''; - env.ATOM_GITHUB_SPEC_MODE = SPEC_MODE ? 'true' : 'false'; + env.ATOM_GITHUB_SPEC_MODE = atom.inSpecMode() ? 'true' : 'false'; env.SSH_ASKPASS = normalizeGitHelperPath(gitTempDir.getAskPassSh()); env.GIT_ASKPASS = normalizeGitHelperPath(gitTempDir.getAskPassSh()); @@ -924,7 +923,7 @@ export default class GitShellOutStrategy { let output; try { let args = ['config']; - if (local || SPEC_MODE) { args.push('--local'); } + if (local || atom.inSpecMode()) { args.push('--local'); } args = args.concat(option); output = await this.exec(args); } catch (err) { From 8aaf2ac9b55eb7b3fd98b271d2f785c8f2b81ab3 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 29 Oct 2018 19:47:04 -0700 Subject: [PATCH 0677/4053] Test flakes --- test/integration/file-patch.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 7341d3fcfb..2c7346ce3f 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -274,7 +274,7 @@ describe('integration: file patches', function() { await clickFileInGitTab('staged', 'added-file.txt'); }); - it('may be partially unstaged', async function() { + it.stress(10, 'may be partially unstaged', async function() { getPatchEditor('staged', 'added-file.txt').setSelectedBufferRange([[3, 0], [4, 3]]); wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); @@ -298,7 +298,7 @@ describe('integration: file patches', function() { ); }); - it('may be completely unstaged', async function() { + it.stress(10, 'may be completely unstaged', async function() { getPatchEditor('staged', 'added-file.txt').selectAll(); wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); From 3e1cb407b46200450cb985c06781e0be459eefad Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 29 Oct 2018 19:50:21 -0700 Subject: [PATCH 0678/4053] Retry file-patch test flakes --- test/integration/file-patch.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 2c7346ce3f..9bfa828c05 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -274,7 +274,8 @@ describe('integration: file patches', function() { await clickFileInGitTab('staged', 'added-file.txt'); }); - it.stress(10, 'may be partially unstaged', async function() { + it('may be partially unstaged', async function() { + this.retries(5); // FLAKE getPatchEditor('staged', 'added-file.txt').setSelectedBufferRange([[3, 0], [4, 3]]); wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); @@ -298,7 +299,8 @@ describe('integration: file patches', function() { ); }); - it.stress(10, 'may be completely unstaged', async function() { + it('may be completely unstaged', async function() { + this.retries(5); // FLAKE getPatchEditor('staged', 'added-file.txt').selectAll(); wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); From c9f273a58d48116f2617a73843f356c0ddff823b Mon Sep 17 00:00:00 2001 From: Vanessa Yuen <6842965+vanessayuenn@users.noreply.github.com> Date: Tue, 30 Oct 2018 12:58:43 +0100 Subject: [PATCH 0679/4053] Update react-component-classification.md fix broken links --- docs/react-component-classification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/react-component-classification.md b/docs/react-component-classification.md index 4580547704..75c3f97fd1 100644 --- a/docs/react-component-classification.md +++ b/docs/react-component-classification.md @@ -10,9 +10,9 @@ These live within [`lib/items/`](/lib/items), are tested within [`test/items/`]( ## Containers -**Containers** are responsible for statefully fetching asynchronous data and rendering their children appropriately. They handle the logic for how subtrees handle loading operations (displaying a loading spinner, passing null objects to their children) and how errors are reported. Containers should mostly be thin wrappers around things like [``](lib/views/observe-model.js), [``](https://facebook.github.io/relay/docs/en/query-renderer.html), or [context](https://reactjs.org/docs/context.html) providers +**Containers** are responsible for statefully fetching asynchronous data and rendering their children appropriately. They handle the logic for how subtrees handle loading operations (displaying a loading spinner, passing null objects to their children) and how errors are reported. Containers should mostly be thin wrappers around things like [``](/lib/views/observe-model.js), [``](https://facebook.github.io/relay/docs/en/query-renderer.html), or [context](https://reactjs.org/docs/context.html) providers - These live within [`lib/containers`]/(lib/containers), are tested within [`test/containers`](/test/containers), and are named with a `Container` suffix. Examples: `PrInfoContainer`, `GitTabContainer`. + These live within [`lib/containers`](/lib/containers), are tested within [`test/containers`](/test/containers), and are named with a `Container` suffix. Examples: `PrInfoContainer`, `GitTabContainer`. ## Controllers From cca6f09c9ed130c8b8442d23b1e8c91160236cf1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 09:34:51 -0400 Subject: [PATCH 0680/4053] CommitPreviewItem skeleton --- lib/items/commit-preview-item.js | 53 ++++++++++++++++++++++ test/items/commit-preview-item.test.js | 63 ++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 lib/items/commit-preview-item.js create mode 100644 test/items/commit-preview-item.test.js diff --git a/lib/items/commit-preview-item.js b/lib/items/commit-preview-item.js new file mode 100644 index 0000000000..1b6165203b --- /dev/null +++ b/lib/items/commit-preview-item.js @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Emitter} from 'event-kit'; + +import {WorkdirContextPoolPropType} from '../prop-types'; + +export default class CommitPreviewItem extends React.Component { + static propTypes = { + workdirContextPool: WorkdirContextPoolPropType.isRequired, + workingDirectory: PropTypes.string.isRequired, + } + + static uriPattern = 'atom-github://commit-preview?workdir={workingDirectory}' + + static buildURI(relPath, workingDirectory) { + return `atom-github://commit-preview?workdir=${encodeURIComponent(workingDirectory)}`; + } + + constructor(props) { + super(props); + + this.emitter = new Emitter(); + this.isDestroyed = false; + this.hasTerminatedPendingState = false; + } + + terminatePendingState() { + if (!this.hasTerminatedPendingState) { + this.emitter.emit('did-terminate-pending-state'); + this.hasTerminatedPendingState = true; + } + } + + onDidTerminatePendingState(callback) { + return this.emitter.on('did-terminate-pending-state', callback); + } + + destroy() { + /* istanbul ignore else */ + if (!this.isDestroyed) { + this.emitter.emit('did-destroy'); + this.isDestroyed = true; + } + } + + onDidDestroy(callback) { + return this.emitter.on('did-destroy', callback); + } + + render() { + return null; + } +} diff --git a/test/items/commit-preview-item.test.js b/test/items/commit-preview-item.test.js new file mode 100644 index 0000000000..28d2b42a81 --- /dev/null +++ b/test/items/commit-preview-item.test.js @@ -0,0 +1,63 @@ +import React from 'react'; +import {mount} from 'enzyme'; + +import CommitPreviewItem from '../../lib/items/commit-preview-item'; +import PaneItem from '../../lib/atom/pane-item'; +import WorkdirContextPool from '../../lib/models/workdir-context-pool'; +import {cloneRepository} from '../helpers'; + +describe('CommitPreviewItem', function() { + let atomEnv, repository, pool; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + const workdir = await cloneRepository(); + + pool = new WorkdirContextPool({ + workspace: atomEnv.workspace, + }); + + repository = pool.add(workdir).getRepository(); + }); + + afterEach(function() { + atomEnv.destroy(); + pool.clear(); + }); + + function buildPaneApp(override = {}) { + const props = { + workdirContextPool: pool, + ...override, + }; + + return ( + + {({itemHolder, params}) => { + return ( + + ); + }} + + ); + } + + function open(wrapper, options = {}) { + const opts = { + workingDirectory: repository.getWorkingDirectoryPath(), + ...options, + }; + const uri = CommitPreviewItem.buildURI(opts.workingDirectory); + return atomEnv.workspace.open(uri); + } + + it('constructs and opens the correct URI', async function() { + const wrapper = mount(buildPaneApp()); + await open(wrapper); + assert.isTrue(wrapper.update().find('CommitPreviewItem').exists()); + }); +}); From 9f91774eab351ad7cf8f0b04d1a8e2dbd2f6becb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 09:53:01 -0400 Subject: [PATCH 0681/4053] Stub out the CommitPreviewContainer --- lib/containers/commit-preview-container.js | 12 ++++++++ .../commit-preview-container.test.js | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 lib/containers/commit-preview-container.js create mode 100644 test/containers/commit-preview-container.test.js diff --git a/lib/containers/commit-preview-container.js b/lib/containers/commit-preview-container.js new file mode 100644 index 0000000000..46022cca3f --- /dev/null +++ b/lib/containers/commit-preview-container.js @@ -0,0 +1,12 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default class CommitPreviewContainer extends React.Component { + static propTypes = { + repository: PropTypes.object.isRequired, + } + + render() { + return null; + } +} diff --git a/test/containers/commit-preview-container.test.js b/test/containers/commit-preview-container.test.js new file mode 100644 index 0000000000..49a91323e2 --- /dev/null +++ b/test/containers/commit-preview-container.test.js @@ -0,0 +1,28 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import CommitPreviewContainer from '../lib/'; + +describe('CommitPreviewContainer', function() { + let atomEnv; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(override = {}) { + const props = { + ...override, + }; + + return ; + } + + it('renders a loading spinner while the repository is loading'); + + it('renders a loading spinner while the diff is being fetched'); +}); From 7d44ddd2d9d8b7a39d122ac8dff3839a0dc08109 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 09:55:26 -0400 Subject: [PATCH 0682/4053] CommitPreviewController skeleton --- lib/controllers/commit-preview-controller.js | 12 +++++++++ .../commit-preview-controller.test.js | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 lib/controllers/commit-preview-controller.js create mode 100644 test/controllers/commit-preview-controller.test.js diff --git a/lib/controllers/commit-preview-controller.js b/lib/controllers/commit-preview-controller.js new file mode 100644 index 0000000000..c3d34b6261 --- /dev/null +++ b/lib/controllers/commit-preview-controller.js @@ -0,0 +1,12 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default class CommitPreviewController extends React.Component { + static propTypes = { + multiFilePatch: PropTypes.object.isRequired, + } + + render() { + return null; + } +} diff --git a/test/controllers/commit-preview-controller.test.js b/test/controllers/commit-preview-controller.test.js new file mode 100644 index 0000000000..44a697831f --- /dev/null +++ b/test/controllers/commit-preview-controller.test.js @@ -0,0 +1,26 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import CommitPreviewController from '../lib/controllers/commit-preview-controller'; + +describe('CommitPreviewController', function() { + let atomEnv; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(override = {}) { + const props = { + ...override, + }; + + return ; + } + + it('renders the CommitPreviewView and passes extra props through'); +}); From 0ac071487092b938b7f80961a03332456d3f9d88 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 09:55:54 -0400 Subject: [PATCH 0683/4053] It helps if you import the right path --- test/containers/commit-preview-container.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/containers/commit-preview-container.test.js b/test/containers/commit-preview-container.test.js index 49a91323e2..5b05ddb50c 100644 --- a/test/containers/commit-preview-container.test.js +++ b/test/containers/commit-preview-container.test.js @@ -1,7 +1,7 @@ import React from 'react'; import {shallow} from 'enzyme'; -import CommitPreviewContainer from '../lib/'; +import CommitPreviewContainer from '../lib/containers/commit-preview-container'; describe('CommitPreviewContainer', function() { let atomEnv; From 315d3cf78244f7b4a82d8c452a6309c626c84f64 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 10:05:14 -0400 Subject: [PATCH 0684/4053] Relative paths are hard okay --- test/containers/commit-preview-container.test.js | 2 +- test/controllers/commit-preview-controller.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/containers/commit-preview-container.test.js b/test/containers/commit-preview-container.test.js index 5b05ddb50c..ffbc020239 100644 --- a/test/containers/commit-preview-container.test.js +++ b/test/containers/commit-preview-container.test.js @@ -1,7 +1,7 @@ import React from 'react'; import {shallow} from 'enzyme'; -import CommitPreviewContainer from '../lib/containers/commit-preview-container'; +import CommitPreviewContainer from '../../lib/containers/commit-preview-container'; describe('CommitPreviewContainer', function() { let atomEnv; diff --git a/test/controllers/commit-preview-controller.test.js b/test/controllers/commit-preview-controller.test.js index 44a697831f..e86834e3db 100644 --- a/test/controllers/commit-preview-controller.test.js +++ b/test/controllers/commit-preview-controller.test.js @@ -1,7 +1,7 @@ import React from 'react'; import {shallow} from 'enzyme'; -import CommitPreviewController from '../lib/controllers/commit-preview-controller'; +import CommitPreviewController from '../../lib/controllers/commit-preview-controller'; describe('CommitPreviewController', function() { let atomEnv; From cdfe186b680f8cd25ec2c36f4e76132a03aba615 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 10:19:07 -0400 Subject: [PATCH 0685/4053] Basic Item behavior and tests --- lib/items/commit-preview-item.js | 18 +++++++- test/items/commit-preview-item.test.js | 61 ++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/lib/items/commit-preview-item.js b/lib/items/commit-preview-item.js index 1b6165203b..2574abac0c 100644 --- a/lib/items/commit-preview-item.js +++ b/lib/items/commit-preview-item.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import {Emitter} from 'event-kit'; import {WorkdirContextPoolPropType} from '../prop-types'; +import CommitPreviewContainer from '../containers/commit-preview-container'; export default class CommitPreviewItem extends React.Component { static propTypes = { @@ -48,6 +49,21 @@ export default class CommitPreviewItem extends React.Component { } render() { - return null; + const repository = this.props.workdirContextPool.getContext(this.props.workingDirectory).getRepository(); + + return ( + + ); + } + + getTitle() { + return 'Commit preview'; + } + + getIconName() { + return 'git-commit'; } } diff --git a/test/items/commit-preview-item.test.js b/test/items/commit-preview-item.test.js index 28d2b42a81..ce5c7df0bf 100644 --- a/test/items/commit-preview-item.test.js +++ b/test/items/commit-preview-item.test.js @@ -58,6 +58,67 @@ describe('CommitPreviewItem', function() { it('constructs and opens the correct URI', async function() { const wrapper = mount(buildPaneApp()); await open(wrapper); + assert.isTrue(wrapper.update().find('CommitPreviewItem').exists()); }); + + it('passes extra props to its container', async function() { + const extra = Symbol('extra'); + const wrapper = mount(buildPaneApp({extra})); + await open(wrapper); + + assert.strictEqual(wrapper.update().find('CommitPreviewContainer').prop('extra'), extra); + }); + + it('locates the repository from the context pool', async function() { + const wrapper = mount(buildPaneApp()); + await open(wrapper); + + assert.strictEqual(wrapper.update().find('CommitPreviewContainer').prop('repository'), repository); + }); + + it('passes an absent repository if the working directory is unrecognized', async function() { + const wrapper = mount(buildPaneApp()); + await open(wrapper, {workingDirectory: '/nah'}); + + assert.isTrue(wrapper.update().find('CommitPreviewContainer').prop('repository').isAbsent()); + }); + + it('returns a fixed title and icon', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(wrapper); + + assert.strictEqual(item.getTitle(), 'Commit preview'); + assert.strictEqual(item.getIconName(), 'git-commit'); + }); + + it('terminates pending state', async function() { + const wrapper = mount(buildPaneApp()); + + const item = await open(wrapper); + const callback = sinon.spy(); + const sub = item.onDidTerminatePendingState(callback); + + assert.strictEqual(callback.callCount, 0); + item.terminatePendingState(); + assert.strictEqual(callback.callCount, 1); + item.terminatePendingState(); + assert.strictEqual(callback.callCount, 1); + + sub.dispose(); + }); + + it('may be destroyed once', async function() { + const wrapper = mount(buildPaneApp()); + + const item = await open(wrapper); + const callback = sinon.spy(); + const sub = item.onDidDestroy(callback); + + assert.strictEqual(callback.callCount, 0); + item.destroy(); + assert.strictEqual(callback.callCount, 1); + + sub.dispose(); + }); }); From ea24bbecd53b5d7d415d06714cf318871824235c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 10:19:21 -0400 Subject: [PATCH 0686/4053] Correct copy/paste fail --- lib/items/commit-preview-item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/items/commit-preview-item.js b/lib/items/commit-preview-item.js index 2574abac0c..33d093a3f1 100644 --- a/lib/items/commit-preview-item.js +++ b/lib/items/commit-preview-item.js @@ -13,7 +13,7 @@ export default class CommitPreviewItem extends React.Component { static uriPattern = 'atom-github://commit-preview?workdir={workingDirectory}' - static buildURI(relPath, workingDirectory) { + static buildURI(workingDirectory) { return `atom-github://commit-preview?workdir=${encodeURIComponent(workingDirectory)}`; } From fa9143cd587c10e181850b9936c8e244967dbad0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 10:25:07 -0400 Subject: [PATCH 0687/4053] Serialize CommitPreview items --- lib/items/commit-preview-item.js | 7 +++++++ test/items/commit-preview-item.test.js | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/items/commit-preview-item.js b/lib/items/commit-preview-item.js index 33d093a3f1..32dd2c4019 100644 --- a/lib/items/commit-preview-item.js +++ b/lib/items/commit-preview-item.js @@ -66,4 +66,11 @@ export default class CommitPreviewItem extends React.Component { getIconName() { return 'git-commit'; } + + serialize() { + return { + deserializer: 'CommitPreviewStub', + uri: CommitPreviewItem.buildURI(this.props.workingDirectory), + }; + } } diff --git a/test/items/commit-preview-item.test.js b/test/items/commit-preview-item.test.js index ce5c7df0bf..c6fb420a07 100644 --- a/test/items/commit-preview-item.test.js +++ b/test/items/commit-preview-item.test.js @@ -121,4 +121,19 @@ describe('CommitPreviewItem', function() { sub.dispose(); }); + + it('serializes itself as a CommitPreviewStub', async function() { + const wrapper = mount(buildPaneApp()); + const item0 = await open(wrapper, {workingDirectory: '/dir0'}); + assert.deepEqual(item0.serialize(), { + deserializer: 'CommitPreviewStub', + uri: 'atom-github://commit-preview?workdir=%2Fdir0', + }); + + const item1 = await open(wrapper, {workingDirectory: '/dir1'}); + assert.deepEqual(item1.serialize(), { + deserializer: 'CommitPreviewStub', + uri: 'atom-github://commit-preview?workdir=%2Fdir1', + }); + }); }); From a2d37ce79dcb5a75c659660e4d5d497afc27a610 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 10:25:32 -0400 Subject: [PATCH 0688/4053] Atom wiring for CommitPreview deserialization --- lib/github-package.js | 10 ++++++++++ package.json | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/github-package.js b/lib/github-package.js index b5b65be47e..64b9b0bcf4 100644 --- a/lib/github-package.js +++ b/lib/github-package.js @@ -379,6 +379,16 @@ export default class GithubPackage { return item; } + createCommitPreviewStub({uri} = {}) { + const item = StubItem.create('git-commit-preview', { + title: 'Commit preview', + }, uri); + if (this.controller) { + this.rerender(); + } + return item; + } + destroyGitTabItem() { if (this.gitTabStubItem) { this.gitTabStubItem.destroy(); diff --git a/package.json b/package.json index 3504ad6e5f..3617fd9f9d 100644 --- a/package.json +++ b/package.json @@ -195,6 +195,7 @@ "IssueishPaneItem": "createIssueishPaneItemStub", "GitDockItem": "createDockItemStub", "GithubDockItem": "createDockItemStub", - "FilePatchControllerStub": "createFilePatchControllerStub" + "FilePatchControllerStub": "createFilePatchControllerStub", + "CommitPreviewStub": "createCommitPreviewStub" } } From 0e84b9b5eb3e7314e779c7c10a2766355db59cd1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 10:28:54 -0400 Subject: [PATCH 0689/4053] Play nicely with GithubPackage's working directory tracking --- lib/items/commit-preview-item.js | 4 ++++ test/items/commit-preview-item.test.js | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/lib/items/commit-preview-item.js b/lib/items/commit-preview-item.js index 32dd2c4019..b03aaa793d 100644 --- a/lib/items/commit-preview-item.js +++ b/lib/items/commit-preview-item.js @@ -67,6 +67,10 @@ export default class CommitPreviewItem extends React.Component { return 'git-commit'; } + getWorkingDirectory() { + return this.props.workingDirectory; + } + serialize() { return { deserializer: 'CommitPreviewStub', diff --git a/test/items/commit-preview-item.test.js b/test/items/commit-preview-item.test.js index c6fb420a07..79bdce5a20 100644 --- a/test/items/commit-preview-item.test.js +++ b/test/items/commit-preview-item.test.js @@ -136,4 +136,10 @@ describe('CommitPreviewItem', function() { uri: 'atom-github://commit-preview?workdir=%2Fdir1', }); }); + + it('has an item-level accessor for the current working directory', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(wrapper, {workingDirectory: '/dir7'}); + assert.strictEqual(item.getWorkingDirectory(), '/dir7'); + }); }); From 8b09ae5b4e53119768c27da1c6ca9f0950351a35 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 10:50:53 -0400 Subject: [PATCH 0690/4053] Container loading behavior --- lib/containers/commit-preview-container.js | 30 ++++++++++++++++++- .../commit-preview-container.test.js | 18 +++++++---- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/lib/containers/commit-preview-container.js b/lib/containers/commit-preview-container.js index 46022cca3f..ef6fd11170 100644 --- a/lib/containers/commit-preview-container.js +++ b/lib/containers/commit-preview-container.js @@ -1,12 +1,40 @@ import React from 'react'; import PropTypes from 'prop-types'; +import yubikiri from 'yubikiri'; + +import ObserveModel from '../views/observe-model'; +import LoadingView from '../views/loading-view'; +import CommitPreviewController from '../controllers/commit-preview-controller'; export default class CommitPreviewContainer extends React.Component { static propTypes = { repository: PropTypes.object.isRequired, } + fetchData = repository => { + return yubikiri({ + multiFilePatch: {}, + }); + } + render() { - return null; + return ( + + {this.renderResult} + + ); + } + + renderResult = data => { + if (this.props.repository.isLoading() || data === null) { + return ; + } + + return ( + + ); } } diff --git a/test/containers/commit-preview-container.test.js b/test/containers/commit-preview-container.test.js index ffbc020239..5953d2d1ab 100644 --- a/test/containers/commit-preview-container.test.js +++ b/test/containers/commit-preview-container.test.js @@ -1,13 +1,17 @@ import React from 'react'; -import {shallow} from 'enzyme'; +import {mount} from 'enzyme'; import CommitPreviewContainer from '../../lib/containers/commit-preview-container'; +import {cloneRepository, buildRepository} from '../helpers'; describe('CommitPreviewContainer', function() { - let atomEnv; + let atomEnv, repository; - beforeEach(function() { + beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); + + const workdir = await cloneRepository(); + repository = await buildRepository(workdir); }); afterEach(function() { @@ -16,13 +20,15 @@ describe('CommitPreviewContainer', function() { function buildApp(override = {}) { const props = { + repository, ...override, }; return ; } - it('renders a loading spinner while the repository is loading'); - - it('renders a loading spinner while the diff is being fetched'); + it('renders a loading spinner while the repository is loading', function() { + const wrapper = mount(buildApp()); + assert.isTrue(wrapper.find('LoadingView').exists()); + }); }); From 0f06d442e1a1ac7a0c31934123e4dedd680479b7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 11:10:12 -0400 Subject: [PATCH 0691/4053] A trivial kind of MultiFilePatch --- lib/models/patch/multi-file-patch.js | 9 +++ test/models/patch/multi-file-patch.test.js | 65 ++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 lib/models/patch/multi-file-patch.js create mode 100644 test/models/patch/multi-file-patch.test.js diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js new file mode 100644 index 0000000000..b47b90f545 --- /dev/null +++ b/lib/models/patch/multi-file-patch.js @@ -0,0 +1,9 @@ +export default class MultiFilePatch { + constructor(filePatches) { + this.filePatches = filePatches; + } + + getFilePatches() { + return this.filePatches; + } +} diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js new file mode 100644 index 0000000000..3c0a38f525 --- /dev/null +++ b/test/models/patch/multi-file-patch.test.js @@ -0,0 +1,65 @@ +import {TextBuffer} from 'atom'; + +import MultiFilePatch from '../../../lib/models/patch/multi-file-patch'; +import FilePatch from '../../../lib/models/patch/file-patch'; +import File from '../../../lib/models/patch/file'; +import Patch from '../../../lib/models/patch/patch'; +import Hunk from '../../../lib/models/patch/hunk'; +import {Unchanged, Addition, Deletion} from '../../../lib/models/patch/region'; + +describe('MultiFilePatch', function() { + it('has an accessor for its file patches', function() { + const filePatches = [buildFilePatchFixture(0), buildFilePatchFixture(1)]; + const mp = new MultiFilePatch(filePatches); + assert.strictEqual(mp.getFilePatches(), filePatches); + }); +}); + +function buildFilePatchFixture(index) { + const buffer = new TextBuffer(); + for (let i = 0; i < 8; i++) { + buffer.append(`file-${index} line-${i}\n`); + } + + const layers = { + hunk: buffer.addMarkerLayer(), + unchanged: buffer.addMarkerLayer(), + addition: buffer.addMarkerLayer(), + deletion: buffer.addMarkerLayer(), + noNewline: buffer.addMarkerLayer(), + }; + + const mark = (layer, start, end = start) => layer.markRange([[start, 0], [end, Infinity]]); + + const hunks = [ + new Hunk({ + oldStartRow: 0, newStartRow: 0, oldRowCount: 3, newRowCount: 3, + sectionHeading: `file-${index} hunk-0`, + marker: mark(layers.hunk, 0, 3), + regions: [ + new Unchanged(mark(layers.unchanged, 0)), + new Addition(mark(layers.addition, 1)), + new Deletion(mark(layers.deletion, 2)), + new Unchanged(mark(layers.unchanged, 3)), + ], + }), + new Hunk({ + oldStartRow: 10, newStartRow: 10, oldRowCount: 3, newRowCount: 3, + sectionHeading: `file-${index} hunk-1`, + marker: mark(layers.hunk, 4, 7), + regions: [ + new Unchanged(mark(layers.unchanged, 4)), + new Addition(mark(layers.addition, 5)), + new Deletion(mark(layers.deletion, 6)), + new Unchanged(mark(layers.unchanged, 7)), + ], + }), + ]; + + const patch = new Patch({status: 'modified', hunks, buffer, layers}); + + const oldFile = new File({path: `file-${index}.txt`, mode: '100644'}); + const newFile = new File({path: `file-${index}.txt`, mode: '100644'}); + + return new FilePatch(oldFile, newFile, patch); +} From d6fc872553cb4dcbfdbde8ae84a51b3a04119f14 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 11:14:36 -0400 Subject: [PATCH 0692/4053] Custom PropType for MultiFilePatches --- lib/containers/commit-preview-container.js | 3 ++- lib/controllers/commit-preview-controller.js | 5 +++-- lib/prop-types.js | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/containers/commit-preview-container.js b/lib/containers/commit-preview-container.js index ef6fd11170..d8af3a424d 100644 --- a/lib/containers/commit-preview-container.js +++ b/lib/containers/commit-preview-container.js @@ -5,6 +5,7 @@ import yubikiri from 'yubikiri'; import ObserveModel from '../views/observe-model'; import LoadingView from '../views/loading-view'; import CommitPreviewController from '../controllers/commit-preview-controller'; +import MultiFilePatch from '../models/patch/multi-file-patch'; export default class CommitPreviewContainer extends React.Component { static propTypes = { @@ -13,7 +14,7 @@ export default class CommitPreviewContainer extends React.Component { fetchData = repository => { return yubikiri({ - multiFilePatch: {}, + multiFilePatch: new MultiFilePatch([]), }); } diff --git a/lib/controllers/commit-preview-controller.js b/lib/controllers/commit-preview-controller.js index c3d34b6261..8642647b26 100644 --- a/lib/controllers/commit-preview-controller.js +++ b/lib/controllers/commit-preview-controller.js @@ -1,9 +1,10 @@ import React from 'react'; -import PropTypes from 'prop-types'; + +import {MultiFilePatchPropType} from '../prop-types'; export default class CommitPreviewController extends React.Component { static propTypes = { - multiFilePatch: PropTypes.object.isRequired, + multiFilePatch: MultiFilePatchPropType.isRequired, } render() { diff --git a/lib/prop-types.js b/lib/prop-types.js index 13d0b4d394..f00c709f8f 100644 --- a/lib/prop-types.js +++ b/lib/prop-types.js @@ -130,6 +130,10 @@ export const FilePatchItemPropType = PropTypes.shape({ status: PropTypes.string.isRequired, }); +export const MultiFilePatchPropType = PropTypes.shape({ + getFilePatches: PropTypes.func.isRequired, +}); + const statusNames = [ 'added', 'deleted', From db731e1abd0925f1d816c26ddee376c9a9187e85 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 12:59:36 -0400 Subject: [PATCH 0693/4053] Parse individual FilePatches in a set --- lib/models/patch/builder.js | 8 ++- lib/models/patch/index.js | 2 +- test/models/patch/builder.test.js | 102 +++++++++++++++++++++++++++++- 3 files changed, 108 insertions(+), 4 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index a773e3d976..0d44ee5d0d 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -5,8 +5,9 @@ import File, {nullFile} from './file'; import Patch from './patch'; import {Unchanged, Addition, Deletion, NoNewline} from './region'; import FilePatch from './file-patch'; +import MultiFilePatch from './multi-file-patch'; -export default function buildFilePatch(diffs) { +export function buildFilePatch(diffs) { if (diffs.length === 0) { return emptyDiffFilePatch(); } else if (diffs.length === 1) { @@ -18,6 +19,11 @@ export default function buildFilePatch(diffs) { } } +export function buildMultiFilePatch(diffs) { + // TODO: handle symlink/content pairs + return new MultiFilePatch(diffs.map(singleDiffFilePatch)); +} + function emptyDiffFilePatch() { return FilePatch.createNull(); } diff --git a/lib/models/patch/index.js b/lib/models/patch/index.js index 596fcfde50..525043dbc4 100644 --- a/lib/models/patch/index.js +++ b/lib/models/patch/index.js @@ -1 +1 @@ -export {default as buildFilePatch} from './builder'; +export {buildFilePatch, buildMultiFilePatch} from './builder'; diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 6b2ce62401..60eab52a71 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -1,5 +1,5 @@ -import {buildFilePatch} from '../../../lib/models/patch'; -import {assertInPatch} from '../../helpers'; +import {buildFilePatch, buildMultiFilePatch} from '../../../lib/models/patch'; +import {assertInPatch, assertInFilePatch} from '../../helpers'; describe('buildFilePatch', function() { it('returns a null patch for an empty diff list', function() { @@ -501,6 +501,104 @@ describe('buildFilePatch', function() { }); }); + describe('with multiple diffs', function() { + it('creates a MultiFilePatch containing each', function() { + const mp = buildMultiFilePatch([ + { + oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100755', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 2, newStartLine: 1, newLineCount: 4, + lines: [ + ' line-0', + '+line-1', + '+line-2', + ' line-3', + ], + }, + { + oldStartLine: 10, oldLineCount: 3, newStartLine: 12, newLineCount: 2, + lines: [ + ' line-4', + '-line-5', + ' line-6', + ], + }, + ], + }, + { + oldPath: 'second', oldMode: '100644', newPath: 'second', newMode: '100644', status: 'modified', + hunks: [ + { + oldStartLine: 5, oldLineCount: 3, newStartLine: 5, newLineCount: 3, + lines: [ + ' line-5', + '+line-6', + '-line-7', + ' line-8', + ], + }, + ], + }, + { + oldPath: 'third', oldMode: '100755', newPath: 'third', newMode: '100755', status: 'added', + hunks: [ + { + oldStartLine: 1, oldLineCount: 0, newStartLine: 1, newLineCount: 3, + lines: [ + '+line-0', + '+line-1', + '+line-2', + ], + }, + ], + }, + ]); + + assert.lengthOf(mp.getFilePatches(), 3); + assert.strictEqual(mp.getFilePatches()[0].getOldPath(), 'first'); + assertInFilePatch(mp.getFilePatches()[0]).hunks( + { + startRow: 0, endRow: 3, header: '@@ -1,2 +1,4 @@', regions: [ + {kind: 'unchanged', string: ' line-0\n', range: [[0, 0], [0, 6]]}, + {kind: 'addition', string: '+line-1\n+line-2\n', range: [[1, 0], [2, 6]]}, + {kind: 'unchanged', string: ' line-3\n', range: [[3, 0], [3, 6]]}, + ], + }, + { + startRow: 4, endRow: 6, header: '@@ -10,3 +12,2 @@', regions: [ + {kind: 'unchanged', string: ' line-4\n', range: [[4, 0], [4, 6]]}, + {kind: 'deletion', string: '-line-5\n', range: [[5, 0], [5, 6]]}, + {kind: 'unchanged', string: ' line-6\n', range: [[6, 0], [6, 6]]}, + ], + }, + ); + assert.strictEqual(mp.getFilePatches()[1].getOldPath(), 'second'); + assertInFilePatch(mp.getFilePatches()[1]).hunks( + { + startRow: 0, endRow: 3, header: '@@ -5,3 +5,3 @@', regions: [ + {kind: 'unchanged', string: ' line-5\n', range: [[0, 0], [0, 6]]}, + {kind: 'addition', string: '+line-6\n', range: [[1, 0], [1, 6]]}, + {kind: 'deletion', string: '-line-7\n', range: [[2, 0], [2, 6]]}, + {kind: 'unchanged', string: ' line-8\n', range: [[3, 0], [3, 6]]}, + ], + }, + ); + assert.strictEqual(mp.getFilePatches()[2].getOldPath(), 'third'); + assertInFilePatch(mp.getFilePatches()[2]).hunks( + { + startRow: 0, endRow: 2, header: '@@ -1,0 +1,3 @@', regions: [ + {kind: 'addition', string: '+line-0\n+line-1\n+line-2\n', range: [[0, 0], [2, 6]]}, + ], + }, + ); + }); + + it('identifies a file that was deleted and replaced by a symlink'); + + it('identifies a symlink that was deleted and replaced by a file'); + }); + it('throws an error with an unexpected number of diffs', function() { assert.throws(() => buildFilePatch([1, 2, 3]), /Unexpected number of diffs: 3/); }); From f73ba516b39c671c358c7a51689121a9929e3025 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 10:45:30 -0700 Subject: [PATCH 0694/4053] Tweak merge criteria based on @smashwilson's comment > I'm kind of thinking of it as "we merge the RFC PR when we, the core team, move on to something else and aren't working on it directly for a while," rather than when we hit some "completeness" threshold? Put another way - I think that us starting to work on new things is a good, emergent, arbitrary way for us to call a feature "shipped", even if there's still stuff in the document that we haven't gotten to yet. Co-Authored-By: Ash Wilson --- docs/how-we-work.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-we-work.md b/docs/how-we-work.md index e79ced1a1d..582434a065 100644 --- a/docs/how-we-work.md +++ b/docs/how-we-work.md @@ -78,7 +78,7 @@ We use a lightweight RFC (request for comments) process to ensure that folks hav The goal is to suss out important considerations and valuable ideas as early as possible and encourage more holistic / bigger picture thinking. The goal is NOT to flesh out the perfect design or come to complete consensus before we start building. -Development work on the feature may start at any point once the RFC pull request has been opened with a description of the feature. The RFC is merged once the feature work is merged. +Development work on the feature may start at any point once the Feature Request pull request has been opened with a description of the feature. The Feature Request is merged once the team decides to move on to the next feature and is no longer actively working on the Feature Request feature. Merging Feature Requests with unfinished work is fine, and we may choose to pick up work again in the future. The RFC is meant to be a living document that will be modified over the duration of development as things evolve, new information is discovered, and UXR is conducted. From 42c833fba730e2b78a294b17a364571711f27491 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 13:49:30 -0400 Subject: [PATCH 0695/4053] Identify paired mode+content diffs in multi-diffs --- lib/models/patch/builder.js | 35 ++++++++++- test/models/patch/builder.test.js | 100 +++++++++++++++++++++++++++++- 2 files changed, 131 insertions(+), 4 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 0d44ee5d0d..c2bef5fe72 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -20,8 +20,39 @@ export function buildFilePatch(diffs) { } export function buildMultiFilePatch(diffs) { - // TODO: handle symlink/content pairs - return new MultiFilePatch(diffs.map(singleDiffFilePatch)); + const byPath = new Map(); + const filePatches = []; + + let index = 0; + for (const diff of diffs) { + const thePath = diff.oldPath || diff.newPath; + + if (diff.status === 'added' || diff.status === 'deleted') { + // Potential paired diff. Either a symlink deletion + content addition or a symlink addition + + // content deletion. + const otherHalf = byPath.get(thePath); + if (otherHalf) { + // The second half. Complete the paired diff, or fail if they have unexpected statuses or modes. + const [otherDiff, otherIndex] = otherHalf; + filePatches[otherIndex] = dualDiffFilePatch(diff, otherDiff); + byPath.delete(thePath); + } else { + // The first half we've seen. + byPath.set(thePath, [diff, index]); + index++; + } + } else { + filePatches[index] = singleDiffFilePatch(diff); + index++; + } + } + + // Populate unpaired diffs that looked like they could be part of a pair, but weren't. + for (const [unpairedDiff, originalIndex] of byPath.values()) { + filePatches[originalIndex] = singleDiffFilePatch(unpairedDiff); + } + + return new MultiFilePatch(filePatches); } function emptyDiffFilePatch() { diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 60eab52a71..987194ef82 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -594,9 +594,105 @@ describe('buildFilePatch', function() { ); }); - it('identifies a file that was deleted and replaced by a symlink'); + it('identifies mode and content change pairs within the patch list', function() { + const mp = buildMultiFilePatch([ + { + oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100755', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 2, newStartLine: 1, newLineCount: 3, + lines: [ + ' line-0', + '+line-1', + ' line-2', + ], + }, + ], + }, + { + oldPath: 'was-non-symlink', oldMode: '100644', newPath: 'was-non-symlink', newMode: '000000', status: 'deleted', + hunks: [ + { + oldStartLine: 1, oldLineCount: 2, newStartLine: 1, newLineCount: 0, + lines: ['-line-0', '-line-1'], + }, + ], + }, + { + oldPath: 'was-symlink', oldMode: '000000', newPath: 'was-symlink', newMode: '100755', status: 'added', + hunks: [ + { + oldStartLine: 1, oldLineCount: 0, newStartLine: 1, newLineCount: 2, + lines: ['+line-0', '+line-1'], + }, + ], + }, + { + oldMode: '100644', newPath: 'third', newMode: '100644', status: 'deleted', + hunks: [ + { + oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 0, + lines: ['-line-0', '-line-1', '-line-2'], + }, + ], + }, + { + oldPath: 'was-symlink', oldMode: '120000', newPath: 'was-non-symlink', newMode: '000000', status: 'deleted', + hunks: [ + { + oldStartLine: 1, oldLineCount: 0, newStartLine: 0, newLineCount: 0, + lines: ['-was-symlink-destination'], + }, + ], + }, + { + oldPath: 'was-non-symlink', oldMode: '000000', newPath: 'was-non-symlink', newMode: '120000', status: 'added', + hunks: [ + { + oldStartLine: 1, oldLineCount: 0, newStartLine: 1, newLineCount: 1, + lines: ['+was-non-symlink-destination'], + }, + ], + }, + ]); - it('identifies a symlink that was deleted and replaced by a file'); + assert.lengthOf(mp.getFilePatches(), 4); + const [fp0, fp1, fp2, fp3] = mp.getFilePatches(); + + assert.strictEqual(fp0.getOldPath(), 'first'); + assertInFilePatch(fp0).hunks({ + startRow: 0, endRow: 2, header: '@@ -1,2 +1,3 @@', regions: [ + {kind: 'unchanged', string: ' line-0\n', range: [[0, 0], [0, 6]]}, + {kind: 'addition', string: '+line-1\n', range: [[1, 0], [1, 6]]}, + {kind: 'unchanged', string: ' line-2\n', range: [[2, 0], [2, 6]]}, + ], + }); + + assert.strictEqual(fp1.getOldPath(), 'was-non-symlink'); + assert.isTrue(fp1.hasTypechange()); + assert.strictEqual(fp1.getNewSymlink(), 'was-non-symlink-destination'); + assertInFilePatch(fp1).hunks({ + startRow: 0, endRow: 1, header: '@@ -1,2 +1,0 @@', regions: [ + {kind: 'deletion', string: '-line-0\n-line-1\n', range: [[0, 0], [1, 6]]}, + ], + }); + + assert.strictEqual(fp2.getOldPath(), 'was-symlink'); + assert.isTrue(fp2.hasTypechange()); + assert.strictEqual(fp2.getOldSymlink(), 'was-symlink-destination'); + assertInFilePatch(fp2).hunks({ + startRow: 0, endRow: 1, header: '@@ -1,0 +1,2 @@', regions: [ + {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 6]]}, + ], + }); + + assert.strictEqual(fp3.getNewPath(), 'third'); + assertInFilePatch(fp3).hunks({ + startRow: 0, endRow: 2, header: '@@ -1,3 +1,0 @@', regions: [ + {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n', range: [[0, 0], [2, 6]]}, + ], + }); + }); }); it('throws an error with an unexpected number of diffs', function() { From cffb2b062879eb41e0cab86d852b30a5ef13f393 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 10:49:46 -0700 Subject: [PATCH 0696/4053] RFC --> Feature Request Co-Authored-By: Vanessa Yuen --- CONTRIBUTING.md | 2 +- .../000-template.md | 8 +- .../001-recent-commits.md | 0 .../002-issueish-list.md | 2 +- .../003-pull-request-review.md | 4 +- docs/how-we-work.md | 20 +- docs/vision/README.md | 6 +- graphql/schema.graphql | 184 +++++++++--------- 8 files changed, 113 insertions(+), 113 deletions(-) rename docs/{rfcs => feature-requests}/000-template.md (88%) rename docs/{rfcs => feature-requests}/001-recent-commits.md (100%) rename docs/{rfcs => feature-requests}/002-issueish-list.md (99%) rename docs/{rfcs => feature-requests}/003-pull-request-review.md (99%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d071b7764..389fefee47 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ For general contributing information, see the [Atom contributing guide](https:// In particular, the GitHub package is under constant development by a portion of the core Atom team, and there is currently a clear vision for short- to medium-term features and enhancements. That doesn't mean we won't merge pull requests or fix other issues, but it *does* mean that you should consider discussing things with us first so that you don't spend time implementing things in a way that differs from the patterns we want to establish or build a feature that we're already working on. -Feel free to [open an issue](https://github.com/atom/github/issues) if you want to discuss anything with us. Depending on the scope and complexity of your proposal we may ask you to reframe it as an [RFC (request for comments)](/docs/how-we-work.md#new-features). If you're curious what we're working on and will be working on in the near future, you can take a look at [our most recent sprint project](https://github.com/atom/github/project) or [accepted RFCs](/docs/rfcs/). +Feel free to [open an issue](https://github.com/atom/github/issues) if you want to discuss anything with us. Depending on the scope and complexity of your proposal we may ask you to reframe it as a [Feature Request](/docs/how-we-work.md#new-features). If you're curious what we're working on and will be working on in the near future, you can take a look at [our most recent sprint project](https://github.com/atom/github/project) or [accepted Feature Requests](/docs/feature-requests/). ## Getting started diff --git a/docs/rfcs/000-template.md b/docs/feature-requests/000-template.md similarity index 88% rename from docs/rfcs/000-template.md rename to docs/feature-requests/000-template.md index 9dc134a684..a40b5788d9 100644 --- a/docs/rfcs/000-template.md +++ b/docs/feature-requests/000-template.md @@ -1,4 +1,4 @@ - @@ -28,7 +28,7 @@ Explain the proposal as if it was already implemented in the GitHub package and - Design mock-ups or diagrams depicting any new UI that will be introduced. -**_Part 2 - Additional information_** +**_Part 2 - Additional information_** ## Drawbacks @@ -42,9 +42,9 @@ Why should we *not* do this? ## Unresolved questions -- What unresolved questions do you expect to resolve through the RFC process before this gets merged? +- What unresolved questions do you expect to resolve through the Feature Request process before this gets merged? - What unresolved questions do you expect to resolve through the implementation of this feature before it is released in a new version of the package? -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? +- What related issues do you consider out of scope for this Feature Request that could be addressed in the future independently of the solution that comes out of this Feature Request? ## Implementation phases diff --git a/docs/rfcs/001-recent-commits.md b/docs/feature-requests/001-recent-commits.md similarity index 100% rename from docs/rfcs/001-recent-commits.md rename to docs/feature-requests/001-recent-commits.md diff --git a/docs/rfcs/002-issueish-list.md b/docs/feature-requests/002-issueish-list.md similarity index 99% rename from docs/rfcs/002-issueish-list.md rename to docs/feature-requests/002-issueish-list.md index 99ebc70ff1..b58410d8d6 100644 --- a/docs/rfcs/002-issueish-list.md +++ b/docs/feature-requests/002-issueish-list.md @@ -97,7 +97,7 @@ With that said, the choices for the specific lists we show are a bit arbitrary. ## Unresolved questions -### Before RFC merge: +### Before Feature Request merge: - [x] What else from the existing issueish pane should we keep? Comments, timeline events? - [x] Are there other pull request actions it would be useful to support? diff --git a/docs/rfcs/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md similarity index 99% rename from docs/rfcs/003-pull-request-review.md rename to docs/feature-requests/003-pull-request-review.md index 06eb3bd368..103dbbce60 100644 --- a/docs/rfcs/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -219,7 +219,7 @@ When editing diffs: * Do we edit the underlying buffer or file directly, or do we mark the `PullRequestDetailItem` as "modified" and require a "save" action to persist changes? * Do we disallow edits of removed lines, or do we re-introduce the removed line as an addition on modification? -### Questions I consider out of scope of this RFC +### Questions I consider out of scope of this Feature Request What other pull request information can we add to the GitHub pane item? @@ -229,6 +229,6 @@ How can we notify users when new information, including reviews, is available, p ![dependency-graph](https://user-images.githubusercontent.com/17565/46475622-019e6a80-c7b4-11e8-9bf5-8223d5c6631f.png) -## Related features out of scope of this RFC +## Related features out of scope of this Feature Request * "Find" input field for filtering based on search term (which could be a file name, an author, a variable name, etc) diff --git a/docs/how-we-work.md b/docs/how-we-work.md index 582434a065..f55678bf8d 100644 --- a/docs/how-we-work.md +++ b/docs/how-we-work.md @@ -32,7 +32,7 @@ This includes work like typos in comments or documentation, localized work, or r ### Bug fixes -Addressing unhandled exceptions, lock-ups, or correcting other unintended behavior in established functionality follows this process. For bug fixes that have UX, substantial UI, or package scope implications or tradeoffs, consider following [the new feature RFC process](#new-features) instead, to ensure we have a chance to collect design and community feedback before we proceed with a fix. +Addressing unhandled exceptions, lock-ups, or correcting other unintended behavior in established functionality follows this process. For bug fixes that have UX, substantial UI, or package scope implications or tradeoffs, consider following [the new feature "Feature Request" process](#new-features) instead, to ensure we have a chance to collect design and community feedback before we proceed with a fix. ##### Process @@ -72,35 +72,35 @@ Major, cross-cutting refactoring efforts fit within this category. Our goals wit To introduce brand-new functionality into the package, follow this guide. -##### On using RFCs +##### On using our Feature Request process -We use a lightweight RFC (request for comments) process to ensure that folks have an opportunity to weigh in on design, alternatives, drawbacks, questions, and concerns. It provides a quick and easily scannable summary of what was discussed and decided. +We use a Feature Request process to ensure that folks have an opportunity to weigh in on design, alternatives, drawbacks, questions, and concerns. It provides a quick and easily scannable summary of what was discussed and decided. The goal is to suss out important considerations and valuable ideas as early as possible and encourage more holistic / bigger picture thinking. The goal is NOT to flesh out the perfect design or come to complete consensus before we start building. Development work on the feature may start at any point once the Feature Request pull request has been opened with a description of the feature. The Feature Request is merged once the team decides to move on to the next feature and is no longer actively working on the Feature Request feature. Merging Feature Requests with unfinished work is fine, and we may choose to pick up work again in the future. -The RFC is meant to be a living document that will be modified over the duration of development as things evolve, new information is discovered, and UXR is conducted. +The Feature Request is meant to be a living document that will be modified over the duration of development as things evolve, new information is discovered, and UXR is conducted. -_We encourage community members wanting to contribute new features to follow this process._ This will help our team collaborate with you and give us an opportunity to provide valuable feedback that could inform your development process. You can run your idea by us by simply filling out the first three sections of the RFC template (summary, motivation, and explanation). Feel free to leave the rest blank -- more info would be welcome but is not necessary. +_We encourage community members wanting to contribute new features to follow this process._ This will help our team collaborate with you and give us an opportunity to provide valuable feedback that could inform your development process. You can run your idea by us by simply filling out the first three sections of the Feature Request template (summary, motivation, and explanation). Feel free to leave the rest blank -- more info would be welcome but is not necessary. ##### Process -1. On a feature branch, write a proposal as a markdown document beneath [`docs/rfcs`](/docs/rfcs) in this repository. Copy the [template](/docs/rfcs/000-template.md) to begin. Open a pull request. The RFC document should include: +1. On a feature branch, write a proposal as a markdown document beneath [`docs/feature-requests`](/docs/feature-requests) in this repository. Copy the [template](/docs/feature-requests/000-template.md) to begin. Open a pull request. The Feature Request document should include: * A description of the feature, written as though it already exists; * An analysis of the risks and drawbacks; * A specification of when the feature will be considered "done"; * Unresolved questions or possible follow-on work; * A sequence of discrete phases that can be used to realize the full feature; 1. @-mention @simurai on the open pull request for design input. Begin hashing out mock-ups, look and feel, specific user interaction details, and decide on a high-level direction for the feature. -1. Feature development may begin at any point after the RFC pull request has been opened. -1. Work on the RFC's implementation is performed in one or more pull requests. Try to break out work into smaller pull requests as much as possible to ship incremental changes. Remember to add each pull request to the current sprint project. +1. Feature development may begin at any point after the Feature Request pull request has been opened. +1. Work on the Feature Request's implementation is performed in one or more pull requests. Try to break out work into smaller pull requests as much as possible to ship incremental changes. Remember to add each pull request to the current sprint project. * Consider gating your work behind a feature flag or a configuration option. * Write tests for your new work. * Optionally [request reviewers](#how-we-review) if you want feedback. Ping @simurai for ongoing UI/UX considerations if appropriate. * Merge your pull request yourself when CI is green and any reviewers you have requested have approved the PR. - * As the design evolves and opinions change, modify the existing RFC to stay accurate. -1. When the feature is complete, update the RFC to a "completed" state and merge it. For any outstanding work that didn't get implemented, open issues or start new RFCs. + * As the design evolves and opinions change, modify the existing Feature Request to stay accurate. +1. When the feature is complete, update the Feature Request to a "completed" state and merge it. For any outstanding work that didn't get implemented, open issues or start new Feature Requests. ##### FAQ diff --git a/docs/vision/README.md b/docs/vision/README.md index edc0ed07a3..76073ba321 100644 --- a/docs/vision/README.md +++ b/docs/vision/README.md @@ -4,7 +4,7 @@ This directory contains notes on ideas for the longer-term vision of the @atom/g * Articulate the objectives we have for the package as a whole. The features we add should contribute to a cohesive experience, not be an amalgamation of unrelated things that sounded cool at the time. * Delineate our boundaries. We should be able to reference these documents to say why we _won't_ work on a feature in addition to why we will. -* Incubate that long tail of ideas we're excited about that we aren't ready to write in [RFC](../how-we-work.md#new-features) form yet. +* Incubate that long tail of ideas we're excited about that we aren't ready to write in [Feature Request](../how-we-work.md#new-features) form yet. * Inform our quarterly and weekly planning cycles. We intend to revisit these often as part of our team's planning cadence, both to keep them accurate and timely and to cherry-pick from when we can. * Share our vision with the world and let the world share its vision with us. :earth_americas: @@ -24,7 +24,7 @@ If you want to see our plans for what we _are_ working on in the very near term, I'm glad you asked! -The first step in tackling any of these would be to [submit an RFC](../how-we-work.md#new-features). The ideas described here are very rough - before we can get to work shipping any of them, we need to reach consensus on scope, graphic design direction, user experience, and many other details. If one of our bullet points sparks your imagination, start a draft of the writeup following [the template we provide](https://github.com/atom/github/blob/master/docs/rfcs/000-template.md). It doesn't have to be complete, but it's a great way to get involved and start a more in-depth conversation. +The first step in tackling any of these would be to [submit a Feature Request](../how-we-work.md#new-features). The ideas described here are very rough - before we can get to work shipping any of them, we need to reach consensus on scope, graphic design direction, user experience, and many other details. If one of our bullet points sparks your imagination, start a draft of the writeup following [the template we provide](https://github.com/atom/github/blob/master/docs/feature-requests/000-template.md). It doesn't have to be complete, but it's a great way to get involved and start a more in-depth conversation. If that sounds like not much fun to you, and you'd rather just write some code: try making a proof-of-concept as a separate Atom package! Tell us about it in an issue and show us what you've done. If we like it and you're okay with it, we can help you merge it into this package, or we can help provide the proper plumbing to make it an independent thing. @@ -33,4 +33,4 @@ If that sounds like not much fun to you, and you'd rather just write some code: * [`who.md`: Our target audience](./who.md) * [`git.md`: Git integration](./git.md) * [`github.md`: GitHub integration](./github.md) -* [`ideas.md`: Proto RFC incubator](./ideas.md) +* [`ideas.md`: Proto Feature Request incubator](./ideas.md) diff --git a/graphql/schema.graphql b/graphql/schema.graphql index dfadf986b3..8cdb5e40d8 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -17,12 +17,12 @@ type AcceptTopicSuggestionPayload { """ The accepted topic. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `topic` will change from `Topic!` to `Topic`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ topic: Topic! } @@ -66,34 +66,34 @@ type AddCommentPayload { """ The edge from the subject's comment connection. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `commentEdge` will change from `IssueCommentEdge!` to `IssueCommentEdge`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ commentEdge: IssueCommentEdge! """ The subject - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `subject` will change from `Node!` to `Node`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ subject: Node! """ The edge from the subject's timeline connection. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `timelineEdge` will change from `IssueTimelineItemEdge!` to `IssueTimelineItemEdge`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ timelineEdge: IssueTimelineItemEdge! } @@ -134,12 +134,12 @@ input AddProjectCardInput { type AddProjectCardPayload { """ The edge from the ProjectColumn's card connection. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `cardEdge` will change from `ProjectCardEdge!` to `ProjectCardEdge`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ cardEdge: ProjectCardEdge! @@ -148,12 +148,12 @@ type AddProjectCardPayload { """ The ProjectColumn - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `projectColumn` will change from `Project!` to `Project`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ projectColumn: Project! } @@ -177,23 +177,23 @@ type AddProjectColumnPayload { """ The edge from the project's column connection. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `columnEdge` will change from `ProjectColumnEdge!` to `ProjectColumnEdge`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ columnEdge: ProjectColumnEdge! """ The project - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `project` will change from `Project!` to `Project`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ project: Project! } @@ -229,24 +229,24 @@ type AddPullRequestReviewCommentPayload { """ The newly created comment. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `comment` will change from `PullRequestReviewComment!` to `PullRequestReviewComment`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ comment: PullRequestReviewComment! """ The edge from the review's comment connection. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `commentEdge` will change from `PullRequestReviewCommentEdge!` to `PullRequestReviewCommentEdge`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ commentEdge: PullRequestReviewCommentEdge! } @@ -279,23 +279,23 @@ type AddPullRequestReviewPayload { """ The newly created pull request review. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `pullRequestReview` will change from `PullRequestReview!` to `PullRequestReview`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ pullRequestReview: PullRequestReview! """ The edge from the pull request's review connection. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `reviewEdge` will change from `PullRequestReviewEdge!` to `PullRequestReviewEdge`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ reviewEdge: PullRequestReviewEdge! } @@ -319,23 +319,23 @@ type AddReactionPayload { """ The reaction object. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `reaction` will change from `Reaction!` to `Reaction`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ reaction: Reaction! """ The reactable subject. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `subject` will change from `Reactable!` to `Reactable`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ subject: Reactable! } @@ -356,12 +356,12 @@ type AddStarPayload { """ The starrable. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `starrable` will change from `Starrable!` to `Starrable`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ starrable: Starrable! } @@ -1226,12 +1226,12 @@ type CreateProjectPayload { """ The new project. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `project` will change from `Project!` to `Project`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ project: Project! } @@ -1295,12 +1295,12 @@ type DeclineTopicSuggestionPayload { """ The declined topic. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `topic` will change from `Topic!` to `Topic`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ topic: Topic! } @@ -1342,23 +1342,23 @@ type DeleteProjectCardPayload { """ The column the deleted card was in. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `column` will change from `ProjectColumn!` to `ProjectColumn`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ column: ProjectColumn! """ The deleted card ID. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `deletedCardId` will change from `ID!` to `ID`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ deletedCardId: ID! } @@ -1379,23 +1379,23 @@ type DeleteProjectColumnPayload { """ The deleted column ID. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `deletedColumnId` will change from `ID!` to `ID`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ deletedColumnId: ID! """ The project the deleted column was in. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `project` will change from `Project!` to `Project`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ project: Project! } @@ -1416,12 +1416,12 @@ type DeleteProjectPayload { """ The repository or organization the project was removed from. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `owner` will change from `ProjectOwner!` to `ProjectOwner`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ owner: ProjectOwner! } @@ -1442,12 +1442,12 @@ type DeletePullRequestReviewPayload { """ The deleted pull request review. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `pullRequestReview` will change from `PullRequestReview!` to `PullRequestReview`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ pullRequestReview: PullRequestReview! } @@ -1731,12 +1731,12 @@ type DismissPullRequestReviewPayload { """ The dismissed pull request review. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `pullRequestReview` will change from `PullRequestReview!` to `PullRequestReview`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ pullRequestReview: PullRequestReview! } @@ -2639,7 +2639,7 @@ type IssueComment implements Node & Comment & Deletable & Updatable & UpdatableC """ Returns the pull request associated with the comment, if this comment was made on a pull request. - + """ pullRequest: PullRequest @@ -3358,7 +3358,7 @@ type MarketplaceListing implements Node { """ Can the current viewer edit the primary and secondary category of this Marketplace listing. - + """ viewerCanEditCategories: Boolean! @@ -3368,40 +3368,40 @@ type MarketplaceListing implements Node { """ Can the current viewer return this Marketplace listing to draft state so it becomes editable again. - + """ viewerCanRedraft: Boolean! """ Can the current viewer reject this Marketplace listing by returning it to an editable draft state or rejecting it entirely. - + """ viewerCanReject: Boolean! """ Can the current viewer request this listing be reviewed for display in the Marketplace. - + """ viewerCanRequestApproval: Boolean! """ Indicates whether the current user has an active subscription to this Marketplace listing. - + """ viewerHasPurchased: Boolean! """ Indicates if the current user has purchased a subscription to this Marketplace listing for all of the organizations the user owns. - + """ viewerHasPurchasedForAllOrganizations: Boolean! """ Does the current viewer role allow them to administer this Marketplace listing. - + """ viewerIsListingAdmin: Boolean! } @@ -3704,12 +3704,12 @@ input MoveProjectCardInput { type MoveProjectCardPayload { """ The new edge of the moved card. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `cardEdge` will change from `ProjectCardEdge!` to `ProjectCardEdge`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ cardEdge: ProjectCardEdge! @@ -3738,12 +3738,12 @@ type MoveProjectColumnPayload { """ The new edge of the moved column. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `columnEdge` will change from `ProjectColumnEdge!` to `ProjectColumnEdge`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ columnEdge: ProjectColumnEdge! } @@ -4387,7 +4387,7 @@ type ProjectCard implements Node { project column at a time. The column field will be null if the card is created in a pending state and has yet to be associated with a column. Once cards are associated with a column, they will not become pending in the future. - + """ column: ProjectColumn @@ -5790,7 +5790,7 @@ type Query { """ Select listings to which user has admin access. If omitted, listings visible to the viewer are returned. - + """ viewerCanAdmin: Boolean @@ -5803,7 +5803,7 @@ type Query { """ Select listings visible to the viewer even if they are not approved. If omitted or false, only approved listings will be returned. - + """ allStates: Boolean @@ -6451,12 +6451,12 @@ type RemoveOutsideCollaboratorPayload { """ The user that was removed as an outside collaborator. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `removedUser` will change from `User!` to `User`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ removedUser: User! } @@ -6480,23 +6480,23 @@ type RemoveReactionPayload { """ The reaction object. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `reaction` will change from `Reaction!` to `Reaction`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ reaction: Reaction! """ The reactable subject. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `subject` will change from `Reactable!` to `Reactable`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ subject: Reactable! } @@ -6517,12 +6517,12 @@ type RemoveStarPayload { """ The starrable. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `starrable` will change from `Starrable!` to `Starrable`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ starrable: Starrable! } @@ -7648,23 +7648,23 @@ type RequestReviewsPayload { """ The pull request that is getting requests. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `pullRequest` will change from `PullRequest!` to `PullRequest`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ pullRequest: PullRequest! """ The edge from the pull request to the requested reviewers. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `requestedReviewersEdge` will change from `UserEdge!` to `UserEdge`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ requestedReviewersEdge: UserEdge! } @@ -8084,12 +8084,12 @@ type SubmitPullRequestReviewPayload { """ The submitted pull request review. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `pullRequestReview` will change from `PullRequestReview!` to `PullRequestReview`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ pullRequestReview: PullRequestReview! } @@ -8607,7 +8607,7 @@ type Topic implements Node { """ A list of related topics, including aliases of this topic, sorted with the most relevant first. - + """ relatedTopics: [Topic!]! } @@ -8841,12 +8841,12 @@ type UpdateProjectCardPayload { """ The updated ProjectCard. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `projectCard` will change from `ProjectCard!` to `ProjectCard`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ projectCard: ProjectCard! } @@ -8870,12 +8870,12 @@ type UpdateProjectColumnPayload { """ The updated project column. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `projectColumn` will change from `ProjectColumn!` to `ProjectColumn`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ projectColumn: ProjectColumn! } @@ -8908,12 +8908,12 @@ type UpdateProjectPayload { """ The updated project. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `project` will change from `Project!` to `Project`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ project: Project! } @@ -8937,13 +8937,13 @@ type UpdatePullRequestReviewCommentPayload { """ The updated comment. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `pullRequestReviewComment` will change from `PullRequestReviewComment!` to `PullRequestReviewComment`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ pullRequestReviewComment: PullRequestReviewComment! } @@ -8967,12 +8967,12 @@ type UpdatePullRequestReviewPayload { """ The updated pull request review. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `pullRequestReview` will change from `PullRequestReview!` to `PullRequestReview`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ pullRequestReview: PullRequestReview! } @@ -8996,12 +8996,12 @@ type UpdateSubscriptionPayload { """ The input subscribable entity. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `subscribable` will change from `Subscribable!` to `Subscribable`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ subscribable: Subscribable! } @@ -9028,12 +9028,12 @@ type UpdateTopicsPayload { """ The updated repository. - + **Upcoming Change on 2019-01-01 UTC** **Description:** Type for `repository` will change from `Repository!` to `Repository`. **Reason:** In preparation for an upcoming change to the way we report mutation errors, non-nullable payload fields are becoming nullable. - + """ repository: Repository! } From d626f7af616388c9f4599b48e788dc354f341937 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 10:54:35 -0700 Subject: [PATCH 0697/4053] Emojify RFC template :smile: Co-Authored-By: Vanessa Yuen --- docs/feature-requests/000-template.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/feature-requests/000-template.md b/docs/feature-requests/000-template.md index a40b5788d9..a976c58dec 100644 --- a/docs/feature-requests/000-template.md +++ b/docs/feature-requests/000-template.md @@ -6,19 +6,19 @@ For community contributors -- Please fill out Part 1 of the following template. # Feature title -## Status +## :tipping_hand_woman: Status Proposed -## Summary +## :memo: Summary One paragraph explanation of the feature. -## Motivation +## :checkered_flag: Motivation Why are we doing this? What use cases does it support? What is the expected outcome? -## Explanation +## 🤯 Explanation Explain the proposal as if it was already implemented in the GitHub package and you were describing it to an Atom user. That generally means: @@ -30,28 +30,31 @@ Explain the proposal as if it was already implemented in the GitHub package and **_Part 2 - Additional information_** -## Drawbacks +## :anchor: Drawbacks Why should we *not* do this? -## Rationale and alternatives +## :thinking: Rationale and alternatives - Why is this approach the best in the space of possible approaches? - What other approaches have been considered and what is the rationale for not choosing them? - What is the impact of not doing this? -## Unresolved questions +## :question: Unresolved questions - What unresolved questions do you expect to resolve through the Feature Request process before this gets merged? - What unresolved questions do you expect to resolve through the implementation of this feature before it is released in a new version of the package? + +## :warning: Out of Scope + - What related issues do you consider out of scope for this Feature Request that could be addressed in the future independently of the solution that comes out of this Feature Request? -## Implementation phases +## :construction: Implementation phases - Can this functionality be introduced in multiple, distinct, self-contained pull requests? - A specification for when the feature is considered "done." -## Feature description for Atom release blog post +## :white_check_mark: Feature description for Atom release blog post - When this feature is shipped, what would we like to say or show in our Atom release blog post (example: http://blog.atom.io/2018/07/31/atom-1-29.html) - Feel free to drop ideas and gifs here during development From ce12fe80b76a75362a46b23f379718f4d5a28523 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 10:55:35 -0700 Subject: [PATCH 0698/4053] Oops. Delete sentence fragment --- docs/how-we-work.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/how-we-work.md b/docs/how-we-work.md index f55678bf8d..afb67ddb90 100644 --- a/docs/how-we-work.md +++ b/docs/how-we-work.md @@ -102,12 +102,6 @@ _We encourage community members wanting to contribute new features to follow thi * As the design evolves and opinions change, modify the existing Feature Request to stay accurate. 1. When the feature is complete, update the Feature Request to a "completed" state and merge it. For any outstanding work that didn't get implemented, open issues or start new Feature Requests. -##### FAQ - -Q: Why not just use issues? -In the past we've used issues for these discussions and the issue body quickly becomes outdated by discussion in comments and it's hard to - - ### Expansions or retractions of package scope As a team, we maintain a [shared understanding](/docs/vision) of what we will and will not build as part of this package, which we use to guide our decisions about accepting new features. Like everything else, this understanding is itself fluid. From 1ae716f7b822c8c30c929b4e151d298cd11c1abe Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 11:08:14 -0700 Subject: [PATCH 0699/4053] Add why we use pull requests instead of issues for Feature Requests Co-Authored-By: Ash Wilson --- docs/how-we-work.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-we-work.md b/docs/how-we-work.md index afb67ddb90..91d6336f29 100644 --- a/docs/how-we-work.md +++ b/docs/how-we-work.md @@ -74,7 +74,7 @@ To introduce brand-new functionality into the package, follow this guide. ##### On using our Feature Request process -We use a Feature Request process to ensure that folks have an opportunity to weigh in on design, alternatives, drawbacks, questions, and concerns. It provides a quick and easily scannable summary of what was discussed and decided. +We use a Feature Request process to ensure that folks have an opportunity to weigh in on design, alternatives, drawbacks, questions, and concerns. It provides a quick and easily scannable summary of what was discussed and decided. We discuss Feature Requests in pull requests rather than issues to record an evolving consensus and have a single file that represents the current state of the Feature Request. The goal is to suss out important considerations and valuable ideas as early as possible and encourage more holistic / bigger picture thinking. The goal is NOT to flesh out the perfect design or come to complete consensus before we start building. From a231aa186efd0c3a35b38bcd736d03ff120dd3c9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 14:32:02 -0400 Subject: [PATCH 0700/4053] GitShellOutStrategy implementation of getStagedChangesPatch() --- lib/git-shell-out-strategy.js | 17 +++++++++++++++++ test/git-strategies.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index e6a4e6b697..a2b5693dfb 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -622,6 +622,23 @@ export default class GitShellOutStrategy { return rawDiffs; } + async getStagedChangesPatch() { + const output = await this.exec([ + 'diff', '--staged', '--no-prefix', '--no-ext-diff', '--no-renames', '--diff-filter=u', + ]); + + if (!output) { + return []; + } + + const diffs = parseDiff(output); + for (const diff of diffs) { + diff.oldPath = toNativePathSep(diff.oldPath); + diff.newPath = toNativePathSep(diff.newPath); + } + return diffs; + } + /** * Miscellaneous getters */ diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 635b09dafd..2c3bdcb7cc 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -627,6 +627,32 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); }); + describe('getStagedChangesPatch', function() { + it('returns an empty patch if there are no staged files', async function() { + const workdir = await cloneRepository('three-files'); + const git = createTestStrategy(workdir); + const mp = await git.getStagedChangesPatch(); + assert.lengthOf(mp, 0); + }); + + it('returns a combined diff of all staged files', async function() { + const workdir = await cloneRepository('each-staging-group'); + const git = createTestStrategy(workdir); + + await assert.isRejected(git.merge('origin/branch')); + await fs.writeFile(path.join(workdir, 'unstaged-1.txt'), 'Unstaged file'); + await fs.writeFile(path.join(workdir, 'unstaged-2.txt'), 'Unstaged file'); + + await fs.writeFile(path.join(workdir, 'staged-1.txt'), 'Staged file'); + await fs.writeFile(path.join(workdir, 'staged-2.txt'), 'Staged file'); + await fs.writeFile(path.join(workdir, 'staged-3.txt'), 'Staged file'); + await git.stageFiles(['staged-1.txt', 'staged-2.txt', 'staged-3.txt']); + + const diffs = await git.getStagedChangesPatch(); + assert.deepEqual(diffs.map(diff => diff.newPath), ['staged-1.txt', 'staged-2.txt', 'staged-3.txt']); + }); + }); + describe('isMerging', function() { it('returns true if `.git/MERGE_HEAD` exists', async function() { const workingDirPath = await cloneRepository('merge-conflict'); From fbc71352aea73fc91db51ed03c5a9e322d951c97 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 14:55:29 -0400 Subject: [PATCH 0701/4053] Cached getStagedChangesPatch() method on Repository --- lib/models/repository-states/present.js | 12 +++++++++++- lib/models/repository-states/state.js | 5 +++++ lib/models/repository.js | 1 + test/models/repository.test.js | 19 +++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index ed25c56e49..6bef2053b0 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -6,7 +6,7 @@ import State from './state'; import {LargeRepoError} from '../../git-shell-out-strategy'; import {FOCUS} from '../workspace-change-observer'; -import {buildFilePatch} from '../patch'; +import {buildFilePatch, buildMultiFilePatch} from '../patch'; import DiscardHistory from '../discard-history'; import Branch, {nullBranch} from '../branch'; import Author from '../author'; @@ -92,6 +92,7 @@ export default class Present extends State { const includes = (...segments) => fullPath.includes(path.join(...segments)); if (endsWith('.git', 'index')) { + keys.add(Keys.staged); keys.add(Keys.stagedChangesSinceParentCommit); keys.add(Keys.filePatch.all); keys.add(Keys.index.all); @@ -626,6 +627,12 @@ export default class Present extends State { }); } + getStagedChangesPatch() { + return this.cache.getOrSet(Keys.stagedChanges, () => { + return this.git().getStagedChangesPatch().then(buildMultiFilePatch); + }); + } + readFileFromIndex(filePath) { return this.cache.getOrSet(Keys.index.oneWith(filePath), () => { return this.git().readFileFromIndex(filePath); @@ -950,6 +957,8 @@ class GroupKey { const Keys = { statusBundle: new CacheKey('status-bundle'), + stagedChanges: new CacheKey('staged-changes'), + stagedChangesSinceParentCommit: new CacheKey('staged-changes-since-parent-commit'), filePatch: { @@ -1029,6 +1038,7 @@ const Keys = { ...Keys.workdirOperationKeys(fileNames), ...Keys.filePatch.eachWithFileOpts(fileNames, [{staged: true}]), ...fileNames.map(Keys.index.oneWith), + Keys.stagedChanges, Keys.stagedChangesSinceParentCommit, ], diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index dc3b8e9bb4..ccefa2cd1b 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -3,6 +3,7 @@ import BranchSet from '../branch-set'; import RemoteSet from '../remote-set'; import {nullOperationStates} from '../operation-states'; import FilePatch from '../patch/file-patch'; +import MultiFilePatch from '../patch/multi-file-patch'; /** * Map of registered subclasses to allow states to transition to one another without circular dependencies. @@ -278,6 +279,10 @@ export default class State { return Promise.resolve(FilePatch.createNull()); } + getStagedChangesPatch() { + return Promise.resolve(new MultiFilePatch([])); + } + readFileFromIndex(filePath) { return Promise.reject(new Error(`fatal: Path ${filePath} does not exist (neither on disk nor in the index).`)); } diff --git a/lib/models/repository.js b/lib/models/repository.js index ab50db5f52..6801b95262 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -326,6 +326,7 @@ const delegates = [ 'getStatusBundle', 'getStatusesForChangedFiles', 'getFilePatchForPath', + 'getStagedChangesPatch', 'readFileFromIndex', 'getLastCommit', diff --git a/test/models/repository.test.js b/test/models/repository.test.js index d1ad3a84c4..8fc548625d 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -440,6 +440,25 @@ describe('Repository', function() { }); }); + describe('getStagedChangesPatch', function() { + it('computes a multi-file patch of the staged changes', async function() { + const workdir = await cloneRepository('each-staging-group'); + const repo = new Repository(workdir); + await repo.getLoadPromise(); + + await fs.writeFile(path.join(workdir, 'unstaged-1.txt'), 'Unstaged file'); + + await fs.writeFile(path.join(workdir, 'staged-1.txt'), 'Staged file'); + await fs.writeFile(path.join(workdir, 'staged-2.txt'), 'Staged file'); + await repo.stageFiles(['staged-1.txt', 'staged-2.txt']); + + const mp = await repo.getStagedChangesPatch(); + + assert.lengthOf(mp.getFilePatches(), 2); + assert.deepEqual(mp.getFilePatches().map(fp => fp.getPath()), ['staged-1.txt', 'staged-2.txt']); + }); + }); + describe('isPartiallyStaged(filePath)', function() { it('returns true if specified file path is partially staged', async function() { const workingDirPath = await cloneRepository('three-files'); From ad28ab8b0f1e53b36ceb7d847347df1a9a4c0e74 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 15:07:41 -0400 Subject: [PATCH 0702/4053] Invalidate cached staged changes --- lib/models/repository-states/present.js | 7 ++++++- test/models/repository.test.js | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 6bef2053b0..a226ef11e4 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -92,7 +92,7 @@ export default class Present extends State { const includes = (...segments) => fullPath.includes(path.join(...segments)); if (endsWith('.git', 'index')) { - keys.add(Keys.staged); + keys.add(Keys.stagedChanges); keys.add(Keys.stagedChangesSinceParentCommit); keys.add(Keys.filePatch.all); keys.add(Keys.index.all); @@ -232,6 +232,7 @@ export default class Present extends State { ...Keys.filePatch.eachWithOpts({staged: true}), Keys.headDescription, Keys.branches, + Keys.stagedChanges, ], // eslint-disable-next-line no-shadow () => this.executePipelineAction('COMMIT', async (message, options = {}) => { @@ -277,6 +278,7 @@ export default class Present extends State { return this.invalidate( () => [ Keys.statusBundle, + Keys.stagedChanges, Keys.stagedChangesSinceParentCommit, Keys.filePatch.all, Keys.index.all, @@ -298,6 +300,7 @@ export default class Present extends State { () => [ Keys.statusBundle, Keys.stagedChangesSinceParentCommit, + Keys.stagedChanges, ...Keys.filePatch.eachWithFileOpts([filePath], [{staged: false}, {staged: true}]), Keys.index.oneWith(filePath), ], @@ -310,6 +313,7 @@ export default class Present extends State { checkout(revision, options = {}) { return this.invalidate( () => [ + Keys.stagedChanges, Keys.stagedChangesSinceParentCommit, Keys.lastCommit, Keys.recentCommits, @@ -331,6 +335,7 @@ export default class Present extends State { return this.invalidate( () => [ Keys.statusBundle, + Keys.stagedChanges, Keys.stagedChangesSinceParentCommit, ...paths.map(fileName => Keys.index.oneWith(fileName)), ...Keys.filePatch.eachWithFileOpts(paths, [{staged: true}]), diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 8fc548625d..f0fb42c4e0 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -1492,6 +1492,10 @@ describe('Repository', function() { 'getRemotes', () => repository.getRemotes(), ); + calls.set( + 'getStagedChangesPatch', + () => repository.getStagedChangesPatch(), + ); const withFile = fileName => { calls.set( From c23bbd25fa99480837d2824ec70af65c33ff192a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 15:12:10 -0400 Subject: [PATCH 0703/4053] Load the real staged changes patch in CommitPreviewContainer --- lib/containers/commit-preview-container.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/containers/commit-preview-container.js b/lib/containers/commit-preview-container.js index d8af3a424d..c7165e7e33 100644 --- a/lib/containers/commit-preview-container.js +++ b/lib/containers/commit-preview-container.js @@ -5,7 +5,6 @@ import yubikiri from 'yubikiri'; import ObserveModel from '../views/observe-model'; import LoadingView from '../views/loading-view'; import CommitPreviewController from '../controllers/commit-preview-controller'; -import MultiFilePatch from '../models/patch/multi-file-patch'; export default class CommitPreviewContainer extends React.Component { static propTypes = { @@ -14,7 +13,7 @@ export default class CommitPreviewContainer extends React.Component { fetchData = repository => { return yubikiri({ - multiFilePatch: new MultiFilePatch([]), + multiFilePatch: repository.getStagedChangesPatch(), }); } From 0da683c65fcbd3f513ef695d18579e20c6caa0e6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 30 Oct 2018 15:26:16 -0400 Subject: [PATCH 0704/4053] Register the CommitPreviewItem opener --- lib/controllers/root-controller.js | 11 +++++++++++ test/controllers/root-controller.test.js | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 3b4a6ad9f0..b88534d4c1 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -17,6 +17,7 @@ import Commands, {Command} from '../atom/commands'; import GitTimingsView from '../views/git-timings-view'; import FilePatchItem from '../items/file-patch-item'; import IssueishDetailItem from '../items/issueish-detail-item'; +import CommitPreviewItem from '../items/commit-preview-item'; import GitTabItem from '../items/git-tab-item'; import GitHubTabItem from '../items/github-tab-item'; import StatusBarTileController from './status-bar-tile-controller'; @@ -338,6 +339,16 @@ export default class RootController extends React.Component { /> )} + + {({itemHolder, params}) => ( + + )} + {({itemHolder, params}) => ( Date: Tue, 30 Oct 2018 15:32:57 -0400 Subject: [PATCH 0705/4053] Super secret dev-mode only command to open the preview item Don't tell anyone :speak_no_evil: --- lib/controllers/root-controller.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index b88534d4c1..b609d42bf2 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -129,6 +129,15 @@ export default class RootController extends React.Component { return ( {devMode && } + {devMode && ( + { + const workdir = this.props.repository.getWorkingDirectoryPath(); + this.props.workspace.toggle(CommitPreviewItem.buildURI(workdir)); + }} + /> + )} From 8517481d61e49c6875b06b3c3e06845e0a11be89 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 13:53:28 -0700 Subject: [PATCH 0706/4053] Retry flaky test, reported in #1769 --- test/integration/file-patch.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 7341d3fcfb..2b2133eb46 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -275,6 +275,8 @@ describe('integration: file patches', function() { }); it('may be partially unstaged', async function() { + this.retries(5); // FLAKE + getPatchEditor('staged', 'added-file.txt').setSelectedBufferRange([[3, 0], [4, 3]]); wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); From 868d2ffca8bc07ffb0a61d0f21029e5bac44c309 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 30 Oct 2018 15:19:58 -0700 Subject: [PATCH 0707/4053] render multiple `FilePatchContainer`s in a `CommitPreviewItem` Co-Authored-By: Katrina Uychaco --- lib/controllers/commit-preview-controller.js | 7 ++++++- lib/controllers/root-controller.js | 8 ++++++++ lib/items/commit-preview-item.js | 1 + lib/views/commit-preview-view.js | 20 ++++++++++++++++++++ styles/file-patch-view.less | 3 ++- 5 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 lib/views/commit-preview-view.js diff --git a/lib/controllers/commit-preview-controller.js b/lib/controllers/commit-preview-controller.js index 8642647b26..6016272d47 100644 --- a/lib/controllers/commit-preview-controller.js +++ b/lib/controllers/commit-preview-controller.js @@ -1,6 +1,7 @@ import React from 'react'; import {MultiFilePatchPropType} from '../prop-types'; +import CommitPreviewView from '../views/commit-preview-view'; export default class CommitPreviewController extends React.Component { static propTypes = { @@ -8,6 +9,10 @@ export default class CommitPreviewController extends React.Component { } render() { - return null; + return ( + + ); } } diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index b609d42bf2..b7285655f9 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -355,6 +355,14 @@ export default class RootController extends React.Component { workdirContextPool={this.props.workdirContextPool} workingDirectory={params.workingDirectory} + workspace={this.props.workspace} + commands={this.props.commandRegistry} + keymaps={this.props.keymaps} + tooltips={this.props.tooltips} + config={this.props.config} + discardLines={this.discardLines} + undoLastDiscard={this.undoLastDiscard} + surfaceFileAtPath={this.surfaceFromFileAtPath} /> )} diff --git a/lib/items/commit-preview-item.js b/lib/items/commit-preview-item.js index b03aaa793d..9634d3a4c5 100644 --- a/lib/items/commit-preview-item.js +++ b/lib/items/commit-preview-item.js @@ -55,6 +55,7 @@ export default class CommitPreviewItem extends React.Component { ); } diff --git a/lib/views/commit-preview-view.js b/lib/views/commit-preview-view.js new file mode 100644 index 0000000000..60f02c79ce --- /dev/null +++ b/lib/views/commit-preview-view.js @@ -0,0 +1,20 @@ +import React from 'react'; +import FilePatchContainer from '../containers/file-patch-container'; + +export default class CommitPreviewView extends React.Component { + render() { + + return this.props.multiFilePatch.getFilePatches().map(filePatch => { + const relPath = filePatch.getNewFile().getPath() + return ( + + ); + }); + } +} diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index c04a3445b0..418de093ad 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -12,7 +12,8 @@ cursor: default; flex: 1; min-width: 0; - height: 100%; + // hack hack hack + height: 500px; &--blank &-container { flex: 1; From ba7645f6e45f2dfbbe80f71bf84f5ab1dcc8602b Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 30 Oct 2018 15:43:33 -0700 Subject: [PATCH 0708/4053] properly bind destroy function in `CommitPreviewItem` Co-Authored-By: Katrina Uychaco --- lib/items/commit-preview-item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/items/commit-preview-item.js b/lib/items/commit-preview-item.js index 9634d3a4c5..ebd3a42c68 100644 --- a/lib/items/commit-preview-item.js +++ b/lib/items/commit-preview-item.js @@ -36,7 +36,7 @@ export default class CommitPreviewItem extends React.Component { return this.emitter.on('did-terminate-pending-state', callback); } - destroy() { + destroy = () => { /* istanbul ignore else */ if (!this.isDestroyed) { this.emitter.emit('did-destroy'); From 31e5935a6b46eb9fa80a896709b3b0488c3da000 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 19:52:52 -0700 Subject: [PATCH 0709/4053] FilePatchItem --> ChangedFileItem Now that we will have other file patch related items we should specify that this item is specifically to display changed files. This also decouples what used to be the FilePatchItem from the FilePatchController and FilePatchView which we are hoping to generalize and use for all file patch item types (ChangedFileItem, CommitPreview, CommitView, CodeReviewDiff, etc) --- docs/react-component-classification.md | 2 +- lib/controllers/file-patch-controller.js | 4 ++-- lib/controllers/root-controller.js | 18 ++++++++--------- ...ile-patch-item.js => changed-file-item.js} | 4 ++-- lib/views/staging-view.js | 14 ++++++------- test/controllers/root-controller.test.js | 20 +++++++++---------- test/integration/file-patch.test.js | 6 +++--- ...item.test.js => changed-file-item.test.js} | 12 +++++------ test/views/staging-view.test.js | 16 +++++++-------- 9 files changed, 48 insertions(+), 48 deletions(-) rename lib/items/{file-patch-item.js => changed-file-item.js} (94%) rename test/items/{file-patch-item.test.js => changed-file-item.test.js} (92%) diff --git a/docs/react-component-classification.md b/docs/react-component-classification.md index 4580547704..3bf6cc7635 100644 --- a/docs/react-component-classification.md +++ b/docs/react-component-classification.md @@ -6,7 +6,7 @@ This is a high-level summary of the organization and implementation of our React **Items** are intended to be used as top-level components within subtrees that are rendered into some [Portal](https://reactjs.org/docs/portals.html) and passed to the Atom API, like pane items, dock items, or tooltips. They are mostly responsible for implementing the [Atom "item" contract](https://github.com/atom/atom/blob/a3631f0dafac146185289ac5e37eaff17b8b0209/src/workspace.js#L29-L174). -These live within [`lib/items/`](/lib/items), are tested within [`test/items/`](/test/items), and are named with an `Item` suffix. Examples: `PullRequestDetailItem`, `FilePatchItem`. +These live within [`lib/items/`](/lib/items), are tested within [`test/items/`](/test/items), and are named with an `Item` suffix. Examples: `PullRequestDetailItem`, `ChangedFileItem`. ## Containers diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 14dc10db17..56491549da 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -4,7 +4,7 @@ import path from 'path'; import {autobind, equalSets} from '../helpers'; import {addEvent} from '../reporter-proxy'; -import FilePatchItem from '../items/file-patch-item'; +import ChangedFileItem from '../items/changed-file-item'; import FilePatchView from '../views/file-patch-view'; export default class FilePatchController extends React.Component { @@ -96,7 +96,7 @@ export default class FilePatchController extends React.Component { diveIntoMirrorPatch() { const mirrorStatus = this.withStagingStatus({staged: 'unstaged', unstaged: 'staged'}); const workingDirectory = this.props.repository.getWorkingDirectoryPath(); - const uri = FilePatchItem.buildURI(this.props.relPath, workingDirectory, mirrorStatus); + const uri = ChangedFileItem.buildURI(this.props.relPath, workingDirectory, mirrorStatus); this.props.destroy(); return this.props.workspace.open(uri); diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index b7285655f9..6ceef27e1e 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -15,7 +15,7 @@ import InitDialog from '../views/init-dialog'; import CredentialDialog from '../views/credential-dialog'; import Commands, {Command} from '../atom/commands'; import GitTimingsView from '../views/git-timings-view'; -import FilePatchItem from '../items/file-patch-item'; +import ChangedFileItem from '../items/changed-file-item'; import IssueishDetailItem from '../items/issueish-detail-item'; import CommitPreviewItem from '../items/commit-preview-item'; import GitTabItem from '../items/git-tab-item'; @@ -326,9 +326,9 @@ export default class RootController extends React.Component { + uriPattern={ChangedFileItem.uriPattern}> {({itemHolder, params}) => ( - Promise.resolve(), getFilePatchLoadedPromise: () => Promise.resolve(), }; - sinon.stub(workspace, 'open').returns(filePatchItem); + sinon.stub(workspace, 'open').returns(changedFileItem); await wrapper.instance().viewUnstagedChangesForCurrentFile(); await assert.async.equal(workspace.open.callCount, 1); @@ -1004,9 +1004,9 @@ describe('RootController', function() { `atom-github://file-patch/a.txt?workdir=${encodeURIComponent(workdirPath)}&stagingStatus=unstaged`, {pending: true, activatePane: true, activateItem: true}, ]); - await assert.async.equal(filePatchItem.goToDiffLine.callCount, 1); - assert.deepEqual(filePatchItem.goToDiffLine.args[0], [8]); - assert.equal(filePatchItem.focus.callCount, 1); + await assert.async.equal(changedFileItem.goToDiffLine.callCount, 1); + assert.deepEqual(changedFileItem.goToDiffLine.args[0], [8]); + assert.equal(changedFileItem.focus.callCount, 1); }); it('does nothing on an untitled buffer', async function() { @@ -1035,13 +1035,13 @@ describe('RootController', function() { editor.setCursorBufferPosition([7, 0]); // TODO: too implementation-detail-y - const filePatchItem = { + const changedFileItem = { goToDiffLine: sinon.spy(), focus: sinon.spy(), getRealItemPromise: () => Promise.resolve(), getFilePatchLoadedPromise: () => Promise.resolve(), }; - sinon.stub(workspace, 'open').returns(filePatchItem); + sinon.stub(workspace, 'open').returns(changedFileItem); await wrapper.instance().viewStagedChangesForCurrentFile(); await assert.async.equal(workspace.open.callCount, 1); @@ -1049,9 +1049,9 @@ describe('RootController', function() { `atom-github://file-patch/a.txt?workdir=${encodeURIComponent(workdirPath)}&stagingStatus=staged`, {pending: true, activatePane: true, activateItem: true}, ]); - await assert.async.equal(filePatchItem.goToDiffLine.callCount, 1); - assert.deepEqual(filePatchItem.goToDiffLine.args[0], [8]); - assert.equal(filePatchItem.focus.callCount, 1); + await assert.async.equal(changedFileItem.goToDiffLine.callCount, 1); + assert.deepEqual(changedFileItem.goToDiffLine.args[0], [8]); + assert.equal(changedFileItem.focus.callCount, 1); }); it('does nothing on an untitled buffer', async function() { diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 7341d3fcfb..d91aaf82d4 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -73,15 +73,15 @@ describe('integration: file patches', function() { listItem.simulate('mousedown', {button: 0, persist() {}}); window.dispatchEvent(new MouseEvent('mouseup')); - const itemSelector = `FilePatchItem[relPath="${relativePath}"][stagingStatus="${stagingStatus}"]`; + const itemSelector = `ChangedFileItem[relPath="${relativePath}"][stagingStatus="${stagingStatus}"]`; await until( () => wrapper.update().find(itemSelector).find('.github-FilePatchView').exists(), - `the FilePatchItem for ${relativePath} arrives and loads`, + `the ChangedFileItem for ${relativePath} arrives and loads`, ); } function getPatchItem(stagingStatus, relativePath) { - return wrapper.update().find(`FilePatchItem[relPath="${relativePath}"][stagingStatus="${stagingStatus}"]`); + return wrapper.update().find(`ChangedFileItem[relPath="${relativePath}"][stagingStatus="${stagingStatus}"]`); } function getPatchEditor(stagingStatus, relativePath) { diff --git a/test/items/file-patch-item.test.js b/test/items/changed-file-item.test.js similarity index 92% rename from test/items/file-patch-item.test.js rename to test/items/changed-file-item.test.js index 00467f1d2f..466cce067d 100644 --- a/test/items/file-patch-item.test.js +++ b/test/items/changed-file-item.test.js @@ -3,11 +3,11 @@ import React from 'react'; import {mount} from 'enzyme'; import PaneItem from '../../lib/atom/pane-item'; -import FilePatchItem from '../../lib/items/file-patch-item'; +import ChangedFileItem from '../../lib/items/changed-file-item'; import WorkdirContextPool from '../../lib/models/workdir-context-pool'; import {cloneRepository} from '../helpers'; -describe('FilePatchItem', function() { +describe('ChangedFileItem', function() { let atomEnv, repository, pool; beforeEach(async function() { @@ -41,10 +41,10 @@ describe('FilePatchItem', function() { }; return ( - + {({itemHolder, params}) => { return ( - filePatchItem, - querySelector: () => filePatchItem, + const changedFileItem = { + getElement: () => changedFileItem, + querySelector: () => changedFileItem, focus: sinon.spy(), }; - workspace.open.returns(filePatchItem); + workspace.open.returns(changedFileItem); await wrapper.instance().showFilePatchItem('file.txt', 'staged', {activate: true}); @@ -238,15 +238,15 @@ describe('StagingView', function() { `atom-github://file-patch/file.txt?workdir=${encodeURIComponent(workingDirectoryPath)}&stagingStatus=staged`, {pending: true, activatePane: true, pane: undefined, activateItem: true}, ]); - assert.isTrue(filePatchItem.focus.called); + assert.isTrue(changedFileItem.focus.called); }); it('makes the item visible if activate is false', async function() { const wrapper = mount(app); const focus = sinon.spy(); - const filePatchItem = {focus}; - workspace.open.returns(filePatchItem); + const changedFileItem = {focus}; + workspace.open.returns(changedFileItem); const activateItem = sinon.spy(); workspace.paneForItem.returns({activateItem}); @@ -259,7 +259,7 @@ describe('StagingView', function() { ]); assert.isFalse(focus.called); assert.equal(activateItem.callCount, 1); - assert.equal(activateItem.args[0][0], filePatchItem); + assert.equal(activateItem.args[0][0], changedFileItem); }); }); }); From 1e86c511783c9a2d7b96b5f7c1364bc63aec23ff Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 20:14:39 -0700 Subject: [PATCH 0710/4053] FilePatchContainer --> ChangedFileContainer --- .../{file-patch-container.js => changed-file-container.js} | 2 +- lib/items/changed-file-item.js | 4 ++-- lib/views/commit-preview-view.js | 4 ++-- ...tch-container.test.js => changed-file-container.test.js} | 6 +++--- test/items/changed-file-item.test.js | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) rename lib/containers/{file-patch-container.js => changed-file-container.js} (96%) rename test/containers/{file-patch-container.test.js => changed-file-container.test.js} (94%) diff --git a/lib/containers/file-patch-container.js b/lib/containers/changed-file-container.js similarity index 96% rename from lib/containers/file-patch-container.js rename to lib/containers/changed-file-container.js index 3b3865f08c..b6de5d3b2a 100644 --- a/lib/containers/file-patch-container.js +++ b/lib/containers/changed-file-container.js @@ -7,7 +7,7 @@ import ObserveModel from '../views/observe-model'; import LoadingView from '../views/loading-view'; import FilePatchController from '../controllers/file-patch-controller'; -export default class FilePatchContainer extends React.Component { +export default class ChangedFileContainer extends React.Component { static propTypes = { repository: PropTypes.object.isRequired, stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), diff --git a/lib/items/changed-file-item.js b/lib/items/changed-file-item.js index 8ba0359b83..57ddc3302b 100644 --- a/lib/items/changed-file-item.js +++ b/lib/items/changed-file-item.js @@ -4,7 +4,7 @@ import {Emitter} from 'event-kit'; import {WorkdirContextPoolPropType} from '../prop-types'; import {autobind} from '../helpers'; -import FilePatchContainer from '../containers/file-patch-container'; +import ChangedFileContainer from '../containers/changed-file-container'; export default class ChangedFileItem extends React.Component { static propTypes = { @@ -77,7 +77,7 @@ export default class ChangedFileItem extends React.Component { const repository = this.props.workdirContextPool.getContext(this.props.workingDirectory).getRepository(); return ( - { const relPath = filePatch.getNewFile().getPath() return ( - ; + return ; } it('renders a loading spinner before file patch data arrives', function() { diff --git a/test/items/changed-file-item.test.js b/test/items/changed-file-item.test.js index 466cce067d..59f8d13101 100644 --- a/test/items/changed-file-item.test.js +++ b/test/items/changed-file-item.test.js @@ -72,14 +72,14 @@ describe('ChangedFileItem', function() { const wrapper = mount(buildPaneApp()); await open(wrapper); - assert.strictEqual(wrapper.update().find('FilePatchContainer').prop('repository'), repository); + assert.strictEqual(wrapper.update().find('ChangedFileContainer').prop('repository'), repository); }); it('passes an absent repository if the working directory is unrecognized', async function() { const wrapper = mount(buildPaneApp()); await open(wrapper, {workingDirectory: '/nope'}); - assert.isTrue(wrapper.update().find('FilePatchContainer').prop('repository').isAbsent()); + assert.isTrue(wrapper.update().find('ChangedFileContainer').prop('repository').isAbsent()); }); it('passes other props to the container', async function() { @@ -87,7 +87,7 @@ describe('ChangedFileItem', function() { const wrapper = mount(buildPaneApp({other})); await open(wrapper); - assert.strictEqual(wrapper.update().find('FilePatchContainer').prop('other'), other); + assert.strictEqual(wrapper.update().find('ChangedFileContainer').prop('other'), other); }); describe('getTitle()', function() { From 35f824817b0d72ff94d716d6729ab19ef6116618 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 20:11:15 -0700 Subject: [PATCH 0711/4053] CommitPreviewController --> MultiFilepatchController Generalize component. This will iterate over file patches passed in and create `FilePatchController`s for each --- lib/containers/commit-preview-container.js | 4 ++-- ...preview-controller.js => multi-file-patch-controller.js} | 2 +- ...ntroller.test.js => multi-file-patch-controller.test.js} | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename lib/controllers/{commit-preview-controller.js => multi-file-patch-controller.js} (81%) rename test/controllers/{commit-preview-controller.test.js => multi-file-patch-controller.test.js} (66%) diff --git a/lib/containers/commit-preview-container.js b/lib/containers/commit-preview-container.js index c7165e7e33..c1c5d9a70f 100644 --- a/lib/containers/commit-preview-container.js +++ b/lib/containers/commit-preview-container.js @@ -4,7 +4,7 @@ import yubikiri from 'yubikiri'; import ObserveModel from '../views/observe-model'; import LoadingView from '../views/loading-view'; -import CommitPreviewController from '../controllers/commit-preview-controller'; +import MultiFilePatchController from '../controllers/multi-file-patch-controller'; export default class CommitPreviewContainer extends React.Component { static propTypes = { @@ -31,7 +31,7 @@ export default class CommitPreviewContainer extends React.Component { } return ( - diff --git a/lib/controllers/commit-preview-controller.js b/lib/controllers/multi-file-patch-controller.js similarity index 81% rename from lib/controllers/commit-preview-controller.js rename to lib/controllers/multi-file-patch-controller.js index 6016272d47..f10b7ddfaa 100644 --- a/lib/controllers/commit-preview-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -3,7 +3,7 @@ import React from 'react'; import {MultiFilePatchPropType} from '../prop-types'; import CommitPreviewView from '../views/commit-preview-view'; -export default class CommitPreviewController extends React.Component { +export default class MultiFilePatchController extends React.Component { static propTypes = { multiFilePatch: MultiFilePatchPropType.isRequired, } diff --git a/test/controllers/commit-preview-controller.test.js b/test/controllers/multi-file-patch-controller.test.js similarity index 66% rename from test/controllers/commit-preview-controller.test.js rename to test/controllers/multi-file-patch-controller.test.js index e86834e3db..78864d23ed 100644 --- a/test/controllers/commit-preview-controller.test.js +++ b/test/controllers/multi-file-patch-controller.test.js @@ -1,9 +1,9 @@ import React from 'react'; import {shallow} from 'enzyme'; -import CommitPreviewController from '../../lib/controllers/commit-preview-controller'; +import MultiFilePatchController from '../../lib/controllers/multi-file-patch-controller'; -describe('CommitPreviewController', function() { +describe('MultiFilePatchController', function() { let atomEnv; beforeEach(function() { @@ -19,7 +19,7 @@ describe('CommitPreviewController', function() { ...override, }; - return ; + return ; } it('renders the CommitPreviewView and passes extra props through'); From cd95e4f68bf87107afd7e10790032b376a694b3b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 20:37:14 -0700 Subject: [PATCH 0712/4053] Use MultiFilePatchController in ChangedFileContainer Render FilePatchController in MultiFilePatchController and :fire: CommitPreviewView --- lib/containers/changed-file-container.js | 9 +++++---- .../multi-file-patch-controller.js | 19 ++++++++++++------ lib/models/repository-states/present.js | 6 ++++++ lib/models/repository-states/state.js | 4 ++++ lib/models/repository.js | 1 + lib/views/commit-preview-view.js | 20 ------------------- 6 files changed, 29 insertions(+), 30 deletions(-) delete mode 100644 lib/views/commit-preview-view.js diff --git a/lib/containers/changed-file-container.js b/lib/containers/changed-file-container.js index b6de5d3b2a..22d73e70a2 100644 --- a/lib/containers/changed-file-container.js +++ b/lib/containers/changed-file-container.js @@ -5,7 +5,8 @@ import yubikiri from 'yubikiri'; import {autobind} from '../helpers'; import ObserveModel from '../views/observe-model'; import LoadingView from '../views/loading-view'; -import FilePatchController from '../controllers/file-patch-controller'; +import MultiFilePatchController from '../controllers/multi-file-patch-controller'; +import MultiFilePatch from '../models/patch/multi-file-patch'; export default class ChangedFileContainer extends React.Component { static propTypes = { @@ -31,7 +32,7 @@ export default class ChangedFileContainer extends React.Component { fetchData(repository) { return yubikiri({ - filePatch: repository.getFilePatchForPath(this.props.relPath, {staged: this.props.stagingStatus === 'staged'}), + multiFilePatch: repository.getChangedFilePatch(this.props.relPath, {staged: this.props.stagingStatus === 'staged'}), isPartiallyStaged: repository.isPartiallyStaged(this.props.relPath), hasUndoHistory: repository.hasDiscardHistory(this.props.relPath), }); @@ -51,8 +52,8 @@ export default class ChangedFileContainer extends React.Component { } return ( - - ); + return this.props.multiFilePatch.getFilePatches().map(filePatch => { + const relPath = filePatch.getNewFile().getPath(); + return ( + + ); + }); } } diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index a226ef11e4..863c59a1e3 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -14,6 +14,7 @@ import BranchSet from '../branch-set'; import Remote from '../remote'; import RemoteSet from '../remote-set'; import Commit from '../commit'; +import MultiFilePatch from '../patch/multi-file-patch'; import OperationStates from '../operation-states'; import {addEvent} from '../../reporter-proxy'; @@ -625,6 +626,11 @@ export default class Present extends State { return {stagedFiles, unstagedFiles, mergeConflictFiles}; } + // hack hack hack + async getChangedFilePatch(...args) { + return new MultiFilePatch([await this.getFilePatchForPath(...args)]); + } + getFilePatchForPath(filePath, {staged} = {staged: false}) { return this.cache.getOrSet(Keys.filePatch.oneWith(filePath, {staged}), async () => { const diffs = await this.git().getDiffsForFilePath(filePath, {staged}); diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index ccefa2cd1b..791b52174d 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -279,6 +279,10 @@ export default class State { return Promise.resolve(FilePatch.createNull()); } + getChangedFilePatch() { + return Promise.resolve(new MultiFilePatch([])); + } + getStagedChangesPatch() { return Promise.resolve(new MultiFilePatch([])); } diff --git a/lib/models/repository.js b/lib/models/repository.js index 6801b95262..08970a981d 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -328,6 +328,7 @@ const delegates = [ 'getFilePatchForPath', 'getStagedChangesPatch', 'readFileFromIndex', + 'getChangedFilePatch', 'getLastCommit', 'getRecentCommits', diff --git a/lib/views/commit-preview-view.js b/lib/views/commit-preview-view.js deleted file mode 100644 index f14f59aa01..0000000000 --- a/lib/views/commit-preview-view.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import ChangedFileContainer from '../containers/changed-file-container'; - -export default class CommitPreviewView extends React.Component { - render() { - - return this.props.multiFilePatch.getFilePatches().map(filePatch => { - const relPath = filePatch.getNewFile().getPath() - return ( - - ); - }); - } -} From 921df33769a5bb95e769e2167787839d46f69c5a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 20:38:06 -0700 Subject: [PATCH 0713/4053] Make some FilePatchController props optional --- lib/controllers/file-patch-controller.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 56491549da..6fffce65e1 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -22,9 +22,9 @@ export default class FilePatchController extends React.Component { config: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, - discardLines: PropTypes.func.isRequired, - undoLastDiscard: PropTypes.func.isRequired, - surfaceFileAtPath: PropTypes.func.isRequired, + discardLines: PropTypes.func, + undoLastDiscard: PropTypes.func, + surfaceFileAtPath: PropTypes.func, } constructor(props) { From 941bcea1b85f728d0103da392efb26a92bb6213b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 20:39:20 -0700 Subject: [PATCH 0714/4053] Make aheadCount prop optional --- lib/controllers/github-tab-controller.js | 2 +- lib/views/github-tab-view.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/github-tab-controller.js b/lib/controllers/github-tab-controller.js index f8eeb45b51..153b723d28 100644 --- a/lib/controllers/github-tab-controller.js +++ b/lib/controllers/github-tab-controller.js @@ -19,7 +19,7 @@ export default class GitHubTabController extends React.Component { allRemotes: RemoteSetPropType.isRequired, branches: BranchSetPropType.isRequired, selectedRemoteName: PropTypes.string, - aheadCount: PropTypes.number.isRequired, + aheadCount: PropTypes.number, pushInProgress: PropTypes.bool.isRequired, isLoading: PropTypes.bool.isRequired, } diff --git a/lib/views/github-tab-view.js b/lib/views/github-tab-view.js index cac4e26b2c..4c8bc73a4b 100644 --- a/lib/views/github-tab-view.js +++ b/lib/views/github-tab-view.js @@ -22,7 +22,7 @@ export default class GitHubTabView extends React.Component { remotes: RemoteSetPropType.isRequired, currentRemote: RemotePropType.isRequired, manyRemotesAvailable: PropTypes.bool.isRequired, - aheadCount: PropTypes.number.isRequired, + aheadCount: PropTypes.number, pushInProgress: PropTypes.bool.isRequired, isLoading: PropTypes.bool.isRequired, From 0a0358546b403d508010520c9b9ec4fed197cfe9 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 30 Oct 2018 20:40:53 -0700 Subject: [PATCH 0715/4053] Make hasUndoHistory prop optional --- lib/controllers/file-patch-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 6fffce65e1..f28277ec58 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -13,7 +13,7 @@ export default class FilePatchController extends React.Component { stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), relPath: PropTypes.string.isRequired, filePatch: PropTypes.object.isRequired, - hasUndoHistory: PropTypes.bool.isRequired, + hasUndoHistory: PropTypes.bool, workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, From 5c6781563c8608b307cd5d2f577dab3bb9d24377 Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 31 Oct 2018 17:29:09 +0900 Subject: [PATCH 0716/4053] Style commit-preview-view --- styles/commit-preview-view.less | 18 ++++++++++++++++++ styles/file-patch-view.less | 3 +-- 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 styles/commit-preview-view.less diff --git a/styles/commit-preview-view.less b/styles/commit-preview-view.less new file mode 100644 index 0000000000..36a66c3cbd --- /dev/null +++ b/styles/commit-preview-view.less @@ -0,0 +1,18 @@ +@import "variables"; + +.github-StubItem-git-commit-preview { // TODO Rename class + .github-FilePatchView { + border-bottom: 1px solid @base-border-color; + + & + .github-FilePatchView { + margin-top: @component-padding; + border-top: 1px solid @base-border-color; + } + } + + + // hack hack hack + .github-FilePatchView { + height: 500px; + } +} diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 418de093ad..c04a3445b0 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -12,8 +12,7 @@ cursor: default; flex: 1; min-width: 0; - // hack hack hack - height: 500px; + height: 100%; &--blank &-container { flex: 1; From e0fd4b3c2c37e0acea5c6be92cf7408ae0cb9c53 Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 31 Oct 2018 17:31:19 +0900 Subject: [PATCH 0717/4053] Enable scrolling of the whole pane --- styles/commit-preview-view.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/styles/commit-preview-view.less b/styles/commit-preview-view.less index 36a66c3cbd..20e9b7c368 100644 --- a/styles/commit-preview-view.less +++ b/styles/commit-preview-view.less @@ -1,6 +1,8 @@ @import "variables"; .github-StubItem-git-commit-preview { // TODO Rename class + overflow: auto; + .github-FilePatchView { border-bottom: 1px solid @base-border-color; From 1032a7477977378ae19fc3db5bd6c6ed56298b1e Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 31 Oct 2018 19:48:16 +0900 Subject: [PATCH 0718/4053] Remove last border --- styles/commit-preview-view.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/styles/commit-preview-view.less b/styles/commit-preview-view.less index 20e9b7c368..6589a1ba00 100644 --- a/styles/commit-preview-view.less +++ b/styles/commit-preview-view.less @@ -6,6 +6,10 @@ .github-FilePatchView { border-bottom: 1px solid @base-border-color; + &:last-child { + border-bottom: none; + } + & + .github-FilePatchView { margin-top: @component-padding; border-top: 1px solid @base-border-color; From 2212d049551799c2e244411e85b237c247ba63ec Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 31 Oct 2018 19:49:35 +0900 Subject: [PATCH 0719/4053] Switch to auto height --- styles/commit-preview-view.less | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/styles/commit-preview-view.less b/styles/commit-preview-view.less index 6589a1ba00..087ef76832 100644 --- a/styles/commit-preview-view.less +++ b/styles/commit-preview-view.less @@ -4,6 +4,7 @@ overflow: auto; .github-FilePatchView { + height: auto; border-bottom: 1px solid @base-border-color; &:last-child { @@ -16,9 +17,4 @@ } } - - // hack hack hack - .github-FilePatchView { - height: 500px; - } } From 1060c24e11ef081e36e60fa46890b8903a20da68 Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 31 Oct 2018 19:54:09 +0900 Subject: [PATCH 0720/4053] Temporarly enable autoHeight --- lib/views/file-patch-view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 8736496e21..071c14157d 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -228,7 +228,8 @@ export default class FilePatchView extends React.Component { buffer={this.props.filePatch.getBuffer()} lineNumberGutterVisible={false} autoWidth={false} - autoHeight={false} + // TODO only set to true for commit previews, but not for single FilePatchViews + autoHeight={true} readOnly={true} softWrapped={true} From 4d364c1d864d591f587543040ef7a8b98d2b56ea Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 31 Oct 2018 08:50:58 -0400 Subject: [PATCH 0721/4053] Use .getPath() to get the relPath from either old or new files --- lib/controllers/multi-file-patch-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 7119f2e893..a35f673b45 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -10,7 +10,7 @@ export default class MultiFilePatchController extends React.Component { render() { return this.props.multiFilePatch.getFilePatches().map(filePatch => { - const relPath = filePatch.getNewFile().getPath(); + const relPath = filePatch.getPath(); return ( Date: Wed, 31 Oct 2018 09:02:50 -0400 Subject: [PATCH 0722/4053] Give the CommitPreviewItem root
a stable className --- lib/controllers/root-controller.js | 5 ++++- styles/commit-preview-view.less | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 6ceef27e1e..a0a3a0385e 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -348,7 +348,10 @@ export default class RootController extends React.Component { /> )} - + {({itemHolder, params}) => ( Date: Wed, 31 Oct 2018 09:20:34 -0400 Subject: [PATCH 0723/4053] Conditionally enable autoHeight on the patch editor --- lib/views/file-patch-view.js | 8 ++++++-- test/views/file-patch-view.test.js | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 071c14157d..8a0d1492b9 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -35,6 +35,7 @@ export default class FilePatchView extends React.Component { selectedRows: PropTypes.object.isRequired, repository: PropTypes.object.isRequired, hasUndoHistory: PropTypes.bool.isRequired, + useEditorAutoHeight: PropTypes.bool, workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, @@ -55,6 +56,10 @@ export default class FilePatchView extends React.Component { discardRows: PropTypes.func.isRequired, } + defaultProps = { + useEditorAutoHeight: false, + } + constructor(props) { super(props); autobind( @@ -228,8 +233,7 @@ export default class FilePatchView extends React.Component { buffer={this.props.filePatch.getBuffer()} lineNumberGutterVisible={false} autoWidth={false} - // TODO only set to true for commit previews, but not for single FilePatchViews - autoHeight={true} + autoHeight={this.props.useEditorAutoHeight} readOnly={true} softWrapped={true} diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index f0c70afd89..8964aac090 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -99,6 +99,15 @@ describe('FilePatchView', function() { assert.strictEqual(editor.instance().getModel().getText(), filePatch.getBuffer().getText()); }); + it('enables autoHeight on the editor when requested', function() { + const wrapper = mount(buildApp({useEditorAutoHeight: true})); + + assert.isTrue(wrapper.find('AtomTextEditor').prop('autoHeight')); + + wrapper.setProps({useEditorAutoHeight: false}); + assert.isFalse(wrapper.find('AtomTextEditor').prop('autoHeight')); + }); + it('sets the root class when in hunk selection mode', function() { const wrapper = shallow(buildApp({selectionMode: 'line'})); assert.isFalse(wrapper.find('.github-FilePatchView--hunkMode').exists()); From 8598f7c91f4bf2a137c5af93e3adbd17b938572b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 31 Oct 2018 09:23:01 -0400 Subject: [PATCH 0724/4053] Set useEditorAutoHeight to true in CommitPreview, but not ChangedFile --- lib/containers/changed-file-container.js | 1 + lib/containers/commit-preview-container.js | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/containers/changed-file-container.js b/lib/containers/changed-file-container.js index 22d73e70a2..f2cc89db52 100644 --- a/lib/containers/changed-file-container.js +++ b/lib/containers/changed-file-container.js @@ -56,6 +56,7 @@ export default class ChangedFileContainer extends React.Component { multiFilePatch={data.multiFilePatch} isPartiallyStaged={data.isPartiallyStaged} hasUndoHistory={data.hasUndoHistory} + useEditorAutoHeight={false} {...this.props} /> ); diff --git a/lib/containers/commit-preview-container.js b/lib/containers/commit-preview-container.js index c1c5d9a70f..877fa76cfb 100644 --- a/lib/containers/commit-preview-container.js +++ b/lib/containers/commit-preview-container.js @@ -32,6 +32,7 @@ export default class CommitPreviewContainer extends React.Component { return ( From 96b21b9c745e19de1f62e12eda0b43f3624afa34 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 31 Oct 2018 15:26:27 +0100 Subject: [PATCH 0725/4053] add commit preview button Co-Authored-By: Ash Wilson --- lib/views/commit-view.js | 7 +++++++ test/views/commit-view.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index 7690da29d9..6338ab489a 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -51,6 +51,7 @@ export default class CommitView extends React.Component { abortMerge: PropTypes.func.isRequired, prepareToCommit: PropTypes.func.isRequired, toggleExpandedCommitMessageEditor: PropTypes.func.isRequired, + previewCommit: PropTypes.func.isRequired, }; constructor(props, context) { @@ -157,6 +158,12 @@ export default class CommitView extends React.Component { +
Date: Wed, 31 Oct 2018 15:50:43 +0100 Subject: [PATCH 0726/4053] logic to open commit preview Co-Authored-By: Ash Wilson --- lib/controllers/commit-controller.js | 9 ++++++++- test/controllers/commit-controller.test.js | 12 ++++++++++++ test/views/commit-view.test.js | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index c4d637c56b..176138220a 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -8,6 +8,7 @@ import fs from 'fs-extra'; import CommitView from '../views/commit-view'; import RefHolder from '../models/ref-holder'; +import CommitPreviewItem from '../items/commit-preview-item'; import {AuthorPropType, UserStorePropType} from '../prop-types'; import {autobind} from '../helpers'; import {addEvent} from '../reporter-proxy'; @@ -43,7 +44,8 @@ export default class CommitController extends React.Component { constructor(props, context) { super(props, context); - autobind(this, 'commit', 'handleMessageChange', 'toggleExpandedCommitMessageEditor', 'grammarAdded'); + autobind(this, 'commit', 'handleMessageChange', 'toggleExpandedCommitMessageEditor', 'grammarAdded', + 'previewCommit'); this.subscriptions = new CompositeDisposable(); this.refCommitView = new RefHolder(); @@ -110,6 +112,7 @@ export default class CommitController extends React.Component { userStore={this.props.userStore} selectedCoAuthors={this.props.selectedCoAuthors} updateSelectedCoAuthors={this.props.updateSelectedCoAuthors} + previewCommit={this.previewCommit} /> ); } @@ -256,6 +259,10 @@ export default class CommitController extends React.Component { hasFocusEditor() { return this.refCommitView.map(view => view.hasFocusEditor()).getOr(false); } + + previewCommit() { + return this.props.workspace.open(CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath())); + } } function wrapCommitMessage(message) { diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 3e716ea10d..24ccd2b7a5 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -8,6 +8,7 @@ import {nullBranch} from '../../lib/models/branch'; import UserStore from '../../lib/models/user-store'; import CommitController, {COMMIT_GRAMMAR_SCOPE} from '../../lib/controllers/commit-controller'; +import CommitPreviewItem from '../../lib/items/commit-preview-item'; import {cloneRepository, buildRepository, buildRepositoryWithPipeline} from '../helpers'; import * as reporterProxy from '../../lib/reporter-proxy'; @@ -410,4 +411,15 @@ describe('CommitController', function() { assert.isFalse(wrapper.instance().hasFocusEditor()); }); }); + + it('opens commit preview pane', async function() { + const workdir = await cloneRepository('three-files'); + const repository = await buildRepository(workdir); + + sinon.spy(workspace, 'open'); + + const wrapper = shallow(React.cloneElement(app, {repository})); + await wrapper.find('CommitView').prop('previewCommit')(); + assert.isTrue(workspace.open.calledWith(CommitPreviewItem.buildURI(workdir))); + }); }); diff --git a/test/views/commit-view.test.js b/test/views/commit-view.test.js index 6d8fb94b84..d2e8eb58a3 100644 --- a/test/views/commit-view.test.js +++ b/test/views/commit-view.test.js @@ -485,5 +485,5 @@ describe('CommitView', function() { wrapper.find('.github-CommitView-commitPreview').simulate('click'); assert.isTrue(previewCommit.called); }); - }) + }); }); From f4b22bfc2ba6ca42b4b71664d8d92133572823e9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 31 Oct 2018 11:14:25 -0400 Subject: [PATCH 0727/4053] defaultProps needs to be static --- lib/views/file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 8a0d1492b9..93fab72a36 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -56,7 +56,7 @@ export default class FilePatchView extends React.Component { discardRows: PropTypes.func.isRequired, } - defaultProps = { + static defaultProps = { useEditorAutoHeight: false, } From ba4022700c063bb3887861cf5a45680da01d99f7 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 31 Oct 2018 16:28:48 +0100 Subject: [PATCH 0728/4053] focus management Co-Authored-By: Ash Wilson --- lib/controllers/commit-controller.js | 4 ++++ lib/views/commit-view.js | 17 +++++++++++++++++ lib/views/git-tab-view.js | 4 ++-- test/views/commit-view.test.js | 11 +++++++++++ test/views/git-tab-view.test.js | 8 ++++---- 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index 176138220a..ca162bd587 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -260,6 +260,10 @@ export default class CommitController extends React.Component { return this.refCommitView.map(view => view.hasFocusEditor()).getOr(false); } + hasFocusPreviewButton() { + return this.refCommitView.map(view => view.hasFocusPreviewButton()).getOr(false); + } + previewCommit() { return this.props.workspace.open(CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath())); } diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index 6338ab489a..22e544d404 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -23,6 +23,7 @@ let FakeKeyDownEvent; export default class CommitView extends React.Component { static focus = { + COMMIT_PREVIEW_BUTTON: Symbol('commit-preview-button'), EDITOR: Symbol('commit-editor'), COAUTHOR_INPUT: Symbol('coauthor-input'), ABORT_MERGE_BUTTON: Symbol('commit-abort-merge-button'), @@ -74,6 +75,7 @@ export default class CommitView extends React.Component { this.subscriptions = new CompositeDisposable(); this.refRoot = new RefHolder(); + this.refCommitPreviewButton = new RefHolder(); this.refExpandButton = new RefHolder(); this.refCommitButton = new RefHolder(); this.refHardWrapButton = new RefHolder(); @@ -159,6 +161,7 @@ export default class CommitView extends React.Component { +
+ +
Date: Wed, 31 Oct 2018 15:26:31 -0400 Subject: [PATCH 0747/4053] :fire: Etch-era "integration" tests --- test/controllers/git-tab-controller.test.js | 161 +------------------- 1 file changed, 1 insertion(+), 160 deletions(-) diff --git a/test/controllers/git-tab-controller.test.js b/test/controllers/git-tab-controller.test.js index 27838dd427..2a0d62dd37 100644 --- a/test/controllers/git-tab-controller.test.js +++ b/test/controllers/git-tab-controller.test.js @@ -13,7 +13,7 @@ import Author from '../../lib/models/author'; import ResolutionProgress from '../../lib/models/conflicts/resolution-progress'; import {GitError} from '../../lib/git-shell-out-strategy'; -describe('GitTabController', function() { +describe.only('GitTabController', function() { let atomEnvironment, workspace, workspaceElement, commandRegistry, notificationManager; let resolutionProgress, refreshResolutionProgress; @@ -277,165 +277,6 @@ describe('GitTabController', function() { }); }); - describe('keyboard navigation commands', function() { - let wrapper, rootElement, gitTab, stagingView, commitView, commitController, focusElement; - const focuses = GitTabController.focus; - - const extractReferences = () => { - rootElement = wrapper.instance().refRoot.get(); - gitTab = wrapper.instance().refView.get(); - stagingView = wrapper.instance().refStagingView.get(); - commitController = gitTab.refCommitController.get(); - commitView = commitController.refCommitView.get(); - focusElement = stagingView.element; - - const commitViewElements = []; - commitView.refEditorComponent.map(e => commitViewElements.push(e)); - commitView.refAbortMergeButton.map(e => commitViewElements.push(e)); - commitView.refCommitButton.map(e => commitViewElements.push(e)); - - const stubFocus = element => { - sinon.stub(element, 'focus').callsFake(() => { - focusElement = element; - }); - }; - stubFocus(stagingView.refRoot.get()); - for (const e of commitViewElements) { - stubFocus(e); - } - - sinon.stub(commitController, 'hasFocus').callsFake(() => { - return commitViewElements.includes(focusElement); - }); - }; - - const assertSelected = paths => { - const selectionPaths = Array.from(stagingView.state.selection.getSelectedItems()).map(item => item.filePath); - assert.deepEqual(selectionPaths, paths); - }; - - const assertAsyncSelected = paths => { - return assert.async.deepEqual( - Array.from(stagingView.state.selection.getSelectedItems()).map(item => item.filePath), - paths, - ); - }; - - describe('with conflicts and staged files', function() { - beforeEach(async function() { - const workdirPath = await cloneRepository('each-staging-group'); - const repository = await buildRepository(workdirPath); - - // Merge with conflicts - assert.isRejected(repository.git.merge('origin/branch')); - - fs.writeFileSync(path.join(workdirPath, 'unstaged-1.txt'), 'This is an unstaged file.'); - fs.writeFileSync(path.join(workdirPath, 'unstaged-2.txt'), 'This is an unstaged file.'); - fs.writeFileSync(path.join(workdirPath, 'unstaged-3.txt'), 'This is an unstaged file.'); - - // Three staged files - fs.writeFileSync(path.join(workdirPath, 'staged-1.txt'), 'This is a file with some changes staged for commit.'); - fs.writeFileSync(path.join(workdirPath, 'staged-2.txt'), 'This is another file staged for commit.'); - fs.writeFileSync(path.join(workdirPath, 'staged-3.txt'), 'This is a third file staged for commit.'); - await repository.stageFiles(['staged-1.txt', 'staged-2.txt', 'staged-3.txt']); - repository.refresh(); - - wrapper = mount(await buildApp(repository)); - await assert.async.lengthOf(wrapper.update().find('GitTabView').prop('unstagedChanges'), 3); - - extractReferences(); - }); - - it('blurs on tool-panel:unfocus', function() { - sinon.spy(workspace.getActivePane(), 'activate'); - - commandRegistry.dispatch(wrapper.find('.github-Git').getDOMNode(), 'tool-panel:unfocus'); - - assert.isTrue(workspace.getActivePane().activate.called); - }); - - it('advances focus through StagingView groups and CommitView, but does not cycle', async function() { - assertSelected(['unstaged-1.txt']); - - commandRegistry.dispatch(rootElement, 'core:focus-next'); - assertSelected(['conflict-1.txt']); - - commandRegistry.dispatch(rootElement, 'core:focus-next'); - assertSelected(['staged-1.txt']); - - commandRegistry.dispatch(rootElement, 'core:focus-next'); - assertSelected(['staged-1.txt']); - await assert.async.strictEqual(focusElement, wrapper.find('AtomTextEditor').instance()); - - // This should be a no-op. (Actually, it'll insert a tab in the CommitView editor.) - commandRegistry.dispatch(rootElement, 'core:focus-next'); - assertSelected(['staged-1.txt']); - assert.strictEqual(focusElement, wrapper.find('AtomTextEditor').instance()); - }); - - it('retreats focus from the CommitView through StagingView groups, but does not cycle', async function() { - gitTab.setFocus(focuses.EDITOR); - sinon.stub(commitView, 'hasFocusEditor').returns(true); - - commandRegistry.dispatch(rootElement, 'core:focus-previous'); - await assert.async.strictEqual(focusElement, stagingView.refRoot.get()); - assertSelected(['staged-1.txt']); - - commandRegistry.dispatch(rootElement, 'core:focus-previous'); - await assertAsyncSelected(['conflict-1.txt']); - - commandRegistry.dispatch(rootElement, 'core:focus-previous'); - await assertAsyncSelected(['unstaged-1.txt']); - - // This should be a no-op. - commandRegistry.dispatch(rootElement, 'core:focus-previous'); - await assertAsyncSelected(['unstaged-1.txt']); - }); - }); - - describe('with staged changes', function() { - let repository; - - beforeEach(async function() { - const workdirPath = await cloneRepository('each-staging-group'); - repository = await buildRepository(workdirPath); - - // A staged file - fs.writeFileSync(path.join(workdirPath, 'staged-1.txt'), 'This is a file with some changes staged for commit.'); - await repository.stageFiles(['staged-1.txt']); - repository.refresh(); - - const prepareToCommit = () => Promise.resolve(true); - const ensureGitTab = () => Promise.resolve(false); - - wrapper = mount(await buildApp(repository, {ensureGitTab, prepareToCommit})); - - extractReferences(); - await assert.async.isTrue(commitView.props.stagedChangesExist); - }); - - it('focuses the CommitView on github:commit with an empty commit message', async function() { - commitView.refEditorModel.map(e => e.setText('')); - sinon.spy(wrapper.instance(), 'commit'); - wrapper.update(); - - commandRegistry.dispatch(workspaceElement, 'github:commit'); - - await assert.async.strictEqual(focusElement, wrapper.find('AtomTextEditor').instance()); - assert.isFalse(wrapper.instance().commit.called); - }); - - it('creates a commit on github:commit with a nonempty commit message', async function() { - commitView.refEditorModel.map(e => e.setText('I fixed the things')); - sinon.spy(repository, 'commit'); - - commandRegistry.dispatch(workspaceElement, 'github:commit'); - - await until('Commit method called', () => repository.commit.calledWith('I fixed the things')); - }); - }); - }); - describe('integration tests', function() { it('can stage and unstage files and commit', async function() { const workdirPath = await cloneRepository('three-files'); From a2c201b1ff4258e2804520dfe2290bc22f80efac Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 31 Oct 2018 15:35:55 -0400 Subject: [PATCH 0748/4053] The tests assume -previewCommit is on the actual button :eyes: --- lib/views/commit-view.js | 4 ++-- styles/commit-view.less | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index 6531ea3714..0932fbf4e1 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -160,10 +160,10 @@ export default class CommitView extends React.Component { -
+
diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index ac9f6174d3..1b705c5828 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -422,7 +422,7 @@ describe('CommitController', function() { sinon.spy(workspace, 'open'); const wrapper = shallow(React.cloneElement(app, {repository})); - await wrapper.find('CommitView').prop('previewCommit')(); + await wrapper.find('CommitView').prop('toggleCommitPreview')(); assert.isTrue(workspace.open.calledWith(CommitPreviewItem.buildURI(workdir))); }); }); diff --git a/test/views/commit-view.test.js b/test/views/commit-view.test.js index 6b8c106e77..7510cf84a5 100644 --- a/test/views/commit-view.test.js +++ b/test/views/commit-view.test.js @@ -641,15 +641,15 @@ describe('CommitView', function() { }); it('calls a callback when the button is clicked', function() { - const previewCommit = sinon.spy(); + const toggleCommitPreview = sinon.spy(); const wrapper = shallow(React.cloneElement(app, { - previewCommit, + toggleCommitPreview, stagedChangesExist: true, })); wrapper.find('.github-CommitView-commitPreview').simulate('click'); - assert.isTrue(previewCommit.called); + assert.isTrue(toggleCommitPreview.called); }); }); }); From 05b6b5047563ff96986dbf1a432325d1201375dd Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 31 Oct 2018 18:57:57 -0700 Subject: [PATCH 0759/4053] Add tests for toggling commit preview open Co-Authored-By: Tilde Ann Thurium --- test/controllers/commit-controller.test.js | 28 ++++++++++++++++------ test/views/commit-view.test.js | 14 +++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 1b705c5828..516d1fa388 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -415,14 +415,28 @@ describe('CommitController', function() { }); }); - it('opens commit preview pane', async function() { - const workdir = await cloneRepository('three-files'); - const repository = await buildRepository(workdir); + describe('toggleCommitPreview', function() { + it('opens and closes commit preview pane', async function() { + const workdir = await cloneRepository('three-files'); + const repository = await buildRepository(workdir); - sinon.spy(workspace, 'open'); + const wrapper = shallow(React.cloneElement(app, {repository})); - const wrapper = shallow(React.cloneElement(app, {repository})); - await wrapper.find('CommitView').prop('toggleCommitPreview')(); - assert.isTrue(workspace.open.calledWith(CommitPreviewItem.buildURI(workdir))); + sinon.spy(workspace, 'toggle'); + + assert.isFalse(wrapper.state('commitPreviewOpen')); + + await wrapper.find('CommitView').prop('toggleCommitPreview')(); + assert.isTrue(workspace.toggle.calledWith(CommitPreviewItem.buildURI(workdir))); + assert.isTrue(wrapper.state('commitPreviewOpen')); + + await wrapper.find('CommitView').prop('toggleCommitPreview')(); + assert.isTrue(workspace.toggle.calledTwice); + assert.isFalse(wrapper.state('commitPreviewOpen')); + + await wrapper.find('CommitView').prop('toggleCommitPreview')(); + assert.isTrue(workspace.toggle.calledThrice); + assert.isTrue(wrapper.state('commitPreviewOpen')); + }); }); }); diff --git a/test/views/commit-view.test.js b/test/views/commit-view.test.js index 7510cf84a5..f7567a4e62 100644 --- a/test/views/commit-view.test.js +++ b/test/views/commit-view.test.js @@ -651,5 +651,19 @@ describe('CommitView', function() { wrapper.find('.github-CommitView-commitPreview').simulate('click'); assert.isTrue(toggleCommitPreview.called); }); + + it('displays correct button text depending on prop value', function() { + const wrapper = shallow(React.cloneElement(app, { + stagedChangesExist: false, + })); + + assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'Preview Commit'); + + wrapper.setProps({commitPreviewOpen: true}); + assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'Close Commit Preview'); + + wrapper.setProps({commitPreviewOpen: false}); + assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'Preview Commit'); + }); }); }); From 25046778b87cd1ac9c6f7843081f7188f235948d Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 31 Oct 2018 19:08:32 -0700 Subject: [PATCH 0760/4053] Make `buildFilePatch` return a `MultiFilePatch` instance --- lib/containers/changed-file-container.js | 2 +- lib/models/patch/builder.js | 6 +++--- lib/models/repository-states/present.js | 5 ----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/containers/changed-file-container.js b/lib/containers/changed-file-container.js index 6ee00e7df3..2c76b1349f 100644 --- a/lib/containers/changed-file-container.js +++ b/lib/containers/changed-file-container.js @@ -33,7 +33,7 @@ export default class ChangedFileContainer extends React.Component { const staged = this.props.stagingStatus === 'staged'; return yubikiri({ - multiFilePatch: repository.getChangedFilePatch(this.props.relPath, {staged}), + multiFilePatch: repository.getFilePatchForPath(this.props.relPath, {staged}), isPartiallyStaged: repository.isPartiallyStaged(this.props.relPath), hasUndoHistory: repository.hasDiscardHistory(this.props.relPath), }); diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index c2bef5fe72..4a8ba43fb1 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -9,11 +9,11 @@ import MultiFilePatch from './multi-file-patch'; export function buildFilePatch(diffs) { if (diffs.length === 0) { - return emptyDiffFilePatch(); + return new MultiFilePatch(emptyDiffFilePatch()); } else if (diffs.length === 1) { - return singleDiffFilePatch(diffs[0]); + return new MultiFilePatch(singleDiffFilePatch(diffs[0])); } else if (diffs.length === 2) { - return dualDiffFilePatch(...diffs); + return new MultiFilePatch(dualDiffFilePatch(...diffs)); } else { throw new Error(`Unexpected number of diffs: ${diffs.length}`); } diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 863c59a1e3..97ebccd24e 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -626,11 +626,6 @@ export default class Present extends State { return {stagedFiles, unstagedFiles, mergeConflictFiles}; } - // hack hack hack - async getChangedFilePatch(...args) { - return new MultiFilePatch([await this.getFilePatchForPath(...args)]); - } - getFilePatchForPath(filePath, {staged} = {staged: false}) { return this.cache.getOrSet(Keys.filePatch.oneWith(filePath, {staged}), async () => { const diffs = await this.git().getDiffsForFilePath(filePath, {staged}); From 89c965e626322abfe39ffdb2f21f2932dad87d2f Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 31 Oct 2018 19:18:22 -0700 Subject: [PATCH 0761/4053] Revert "Make `buildFilePatch` return a `MultiFilePatch` instance" This reverts commit 25046778b87cd1ac9c6f7843081f7188f235948d. --- lib/containers/changed-file-container.js | 2 +- lib/models/patch/builder.js | 6 +++--- lib/models/repository-states/present.js | 5 +++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/containers/changed-file-container.js b/lib/containers/changed-file-container.js index 2c76b1349f..6ee00e7df3 100644 --- a/lib/containers/changed-file-container.js +++ b/lib/containers/changed-file-container.js @@ -33,7 +33,7 @@ export default class ChangedFileContainer extends React.Component { const staged = this.props.stagingStatus === 'staged'; return yubikiri({ - multiFilePatch: repository.getFilePatchForPath(this.props.relPath, {staged}), + multiFilePatch: repository.getChangedFilePatch(this.props.relPath, {staged}), isPartiallyStaged: repository.isPartiallyStaged(this.props.relPath), hasUndoHistory: repository.hasDiscardHistory(this.props.relPath), }); diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 4a8ba43fb1..c2bef5fe72 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -9,11 +9,11 @@ import MultiFilePatch from './multi-file-patch'; export function buildFilePatch(diffs) { if (diffs.length === 0) { - return new MultiFilePatch(emptyDiffFilePatch()); + return emptyDiffFilePatch(); } else if (diffs.length === 1) { - return new MultiFilePatch(singleDiffFilePatch(diffs[0])); + return singleDiffFilePatch(diffs[0]); } else if (diffs.length === 2) { - return new MultiFilePatch(dualDiffFilePatch(...diffs)); + return dualDiffFilePatch(...diffs); } else { throw new Error(`Unexpected number of diffs: ${diffs.length}`); } diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 97ebccd24e..863c59a1e3 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -626,6 +626,11 @@ export default class Present extends State { return {stagedFiles, unstagedFiles, mergeConflictFiles}; } + // hack hack hack + async getChangedFilePatch(...args) { + return new MultiFilePatch([await this.getFilePatchForPath(...args)]); + } + getFilePatchForPath(filePath, {staged} = {staged: false}) { return this.cache.getOrSet(Keys.filePatch.oneWith(filePath, {staged}), async () => { const diffs = await this.git().getDiffsForFilePath(filePath, {staged}); From 9980a4ccae0ab254795550dee5000fe4b0421403 Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 1 Nov 2018 16:09:26 +0900 Subject: [PATCH 0762/4053] Fix scrollbar on macOS --- styles/commit-preview-view.less | 1 + 1 file changed, 1 insertion(+) diff --git a/styles/commit-preview-view.less b/styles/commit-preview-view.less index 63a07f36a2..a0fa33e21b 100644 --- a/styles/commit-preview-view.less +++ b/styles/commit-preview-view.less @@ -2,6 +2,7 @@ .github-CommitPreview-root { overflow: auto; + z-index: 1; // Fixes scrollbar on macOS .github-FilePatchView { height: auto; From 4160aec4657108474ad2b749720f562136db649c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 08:15:57 -0400 Subject: [PATCH 0763/4053] :shirt: add dangling comma --- lib/views/file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 69c2d2099f..92b9b08e4b 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -69,7 +69,7 @@ export default class FilePatchView extends React.Component { 'didMouseDownOnHeader', 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', 'didConfirm', 'didToggleSelectionMode', 'selectNextHunk', 'selectPreviousHunk', 'didOpenFile', 'didAddSelection', 'didChangeSelectionRange', 'didDestroySelection', - 'oldLineNumberLabel', 'newLineNumberLabel', 'handleMouseDown' + 'oldLineNumberLabel', 'newLineNumberLabel', 'handleMouseDown', ); this.mouseSelectionInProgress = false; From 197f0c131c8e09cc9fa4c9c85d723027bdf3bd26 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 08:17:27 -0400 Subject: [PATCH 0764/4053] :shirt: Restore missing prop --- lib/views/commit-view.js | 1 + test/views/commit-view.test.js | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index ebf83c698b..400c96c4d0 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -42,6 +42,7 @@ export default class CommitView extends React.Component { mergeConflictsExist: PropTypes.bool.isRequired, stagedChangesExist: PropTypes.bool.isRequired, isCommitting: PropTypes.bool.isRequired, + commitPreviewOpen: PropTypes.bool.isRequired, deactivateCommitBox: PropTypes.bool.isRequired, maximumCharacterLimit: PropTypes.number.isRequired, messageBuffer: PropTypes.object.isRequired, // FIXME more specific proptype diff --git a/test/views/commit-view.test.js b/test/views/commit-view.test.js index f7567a4e62..ad542a6837 100644 --- a/test/views/commit-view.test.js +++ b/test/views/commit-view.test.js @@ -43,6 +43,7 @@ describe('CommitView', function() { stagedChangesExist={false} mergeConflictsExist={false} isCommitting={false} + commitPreviewOpen={false} deactivateCommitBox={false} maximumCharacterLimit={72} messageBuffer={messageBuffer} From 417f260e69c9c84a0786d5cace76ec0cca16e7ee Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 08:25:39 -0400 Subject: [PATCH 0765/4053] Button caption tests don't depend on stagedChangesExist --- test/views/commit-view.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/views/commit-view.test.js b/test/views/commit-view.test.js index ad542a6837..fd182e4e50 100644 --- a/test/views/commit-view.test.js +++ b/test/views/commit-view.test.js @@ -654,9 +654,7 @@ describe('CommitView', function() { }); it('displays correct button text depending on prop value', function() { - const wrapper = shallow(React.cloneElement(app, { - stagedChangesExist: false, - })); + const wrapper = shallow(app); assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'Preview Commit'); From 1e9e487721f34c6e9f88f15cb8ae664f012574f4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 08:29:59 -0400 Subject: [PATCH 0766/4053] Assert against the view prop instead of directly against state --- test/controllers/commit-controller.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 516d1fa388..ffd4c1c9be 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -424,19 +424,19 @@ describe('CommitController', function() { sinon.spy(workspace, 'toggle'); - assert.isFalse(wrapper.state('commitPreviewOpen')); + assert.isFalse(wrapper.find('CommitView').prop('commitPreviewOpen')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); assert.isTrue(workspace.toggle.calledWith(CommitPreviewItem.buildURI(workdir))); - assert.isTrue(wrapper.state('commitPreviewOpen')); + assert.isTrue(wrapper.find('CommitView').prop('commitPreviewOpen')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); assert.isTrue(workspace.toggle.calledTwice); - assert.isFalse(wrapper.state('commitPreviewOpen')); + assert.isFalse(wrapper.find('CommitView').prop('commitPreviewOpen')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); assert.isTrue(workspace.toggle.calledThrice); - assert.isTrue(wrapper.state('commitPreviewOpen')); + assert.isTrue(wrapper.find('CommitView').prop('commitPreviewOpen')); }); }); }); From 555c144e9b0b87007814549fee8eca5d37808235 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 08:37:32 -0400 Subject: [PATCH 0767/4053] Just mark all of "integration: file patches" as flaky :disappointed: --- test/integration/file-patch.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 2b2133eb46..ca2acba9a8 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -15,6 +15,8 @@ describe('integration: file patches', function() { this.timeout(Math.max(this.timeout(), 10000)); + this.retries(5); // FLAKE + beforeEach(function() { // These tests take a little longer because they rely on real filesystem events and git operations. until.setDefaultTimeout(9000); @@ -275,8 +277,6 @@ describe('integration: file patches', function() { }); it('may be partially unstaged', async function() { - this.retries(5); // FLAKE - getPatchEditor('staged', 'added-file.txt').setSelectedBufferRange([[3, 0], [4, 3]]); wrapper.find('.github-HunkHeaderView-stageButton').simulate('click'); @@ -734,8 +734,6 @@ describe('integration: file patches', function() { }); it('may be partially staged', async function() { - this.retries(5); // FLAKE - getPatchEditor('unstaged', 'sample.js').setSelectedBufferRanges([ [[2, 0], [2, 0]], [[10, 0], [10, 0]], From d218af9ad6d307a79e85a05d4f9a6472222c519f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 09:35:11 -0400 Subject: [PATCH 0768/4053] watchWorkspaceItem to track open pane items in React component state --- lib/watch-workspace-item.js | 40 ++++++++++ test/watch-workspace-item.test.js | 125 ++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 lib/watch-workspace-item.js create mode 100644 test/watch-workspace-item.test.js diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js new file mode 100644 index 0000000000..0dd844bc46 --- /dev/null +++ b/lib/watch-workspace-item.js @@ -0,0 +1,40 @@ +import {CompositeDisposable} from 'atom'; + +import URIPattern from './atom/uri-pattern'; + +export function watchWorkspaceItem(workspace, pattern, component, stateKey) { + const uPattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); + + function itemMatches(item) { + return item.getURI && uPattern.matches(item.getURI()).ok(); + } + + if (!component.state) { + component.state = {}; + } + let itemCount = workspace.getPaneItems().filter(itemMatches).length; + component.state[stateKey] = itemCount > 0; + + return new CompositeDisposable( + workspace.onDidAddPaneItem(({item}) => { + const hadOpen = itemCount > 0; + if (itemMatches(item)) { + itemCount++; + + if (itemCount > 0 && !hadOpen) { + component.setState({[stateKey]: true}); + } + } + }), + workspace.onDidDestroyPaneItem(({item}) => { + const hadOpen = itemCount > 0; + if (itemMatches(item)) { + itemCount--; + + if (itemCount <= 0 && hadOpen) { + component.setState({[stateKey]: false}); + } + } + }), + ); +} diff --git a/test/watch-workspace-item.test.js b/test/watch-workspace-item.test.js new file mode 100644 index 0000000000..9ea6826a5b --- /dev/null +++ b/test/watch-workspace-item.test.js @@ -0,0 +1,125 @@ +import {watchWorkspaceItem} from '../lib/watch-workspace-item'; + +describe('watchWorkspaceItem', function() { + let sub, atomEnv, workspace, component; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + workspace = atomEnv.workspace; + + component = { + state: {}, + setState: sinon.stub().resolves(), + }; + + workspace.addOpener(uri => { + if (uri.startsWith('atom-github://')) { + return { + getURI() { return uri; }, + }; + } else { + return undefined; + } + }); + }); + + afterEach(function() { + sub && sub.dispose(); + atomEnv.destroy(); + }); + + describe('initial state', function() { + it('creates component state if none is present', function() { + component.state = undefined; + + sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'aKey'); + assert.deepEqual(component.state, {aKey: false}); + }); + + it('is false when the pane is not open', async function() { + await workspace.open('atom-github://nonmatching'); + + sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey'); + assert.isFalse(component.state.someKey); + }); + + it('is true when the pane is already open', async function() { + await workspace.open('atom-github://item/one'); + await workspace.open('atom-github://item/two'); + + sub = watchWorkspaceItem(workspace, 'atom-github://item/one', component, 'theKey'); + + assert.isTrue(component.state.theKey); + }); + + it('is true when multiple panes matching the URI pattern are open', async function() { + await workspace.open('atom-github://item/one'); + await workspace.open('atom-github://item/two'); + await workspace.open('atom-github://nonmatch'); + + sub = watchWorkspaceItem(workspace, 'atom-github://item/{pattern}', component, 'theKey'); + + assert.isTrue(component.state.theKey); + }); + }); + + describe('workspace events', function() { + it('becomes true when the pane is opened', async function() { + sub = watchWorkspaceItem(workspace, 'atom-github://item/{pattern}', component, 'theKey'); + + assert.isFalse(component.state.theKey); + + await workspace.open('atom-github://item/match'); + + assert.isTrue(component.setState.calledWith({theKey: true})); + }); + + it('remains true if another matching pane is opened', async function() { + await workspace.open('atom-github://item/match0'); + sub = watchWorkspaceItem(workspace, 'atom-github://item/{pattern}', component, 'theKey'); + + assert.isTrue(component.state.theKey); + + await workspace.open('atom-github://item/match1'); + + assert.isFalse(component.setState.called); + }); + + it('remains true if a matching pane is closed but another remains open', async function() { + await workspace.open('atom-github://item/match0'); + await workspace.open('atom-github://item/match1'); + + sub = watchWorkspaceItem(workspace, 'atom-github://item/{pattern}', component, 'theKey'); + assert.isTrue(component.state.theKey); + + assert.isTrue(workspace.hide('atom-github://item/match1')); + + assert.isFalse(component.setState.called); + }); + + it('becomes false if the last matching pane is closed', async function() { + await workspace.open('atom-github://item/match0'); + await workspace.open('atom-github://item/match1'); + + sub = watchWorkspaceItem(workspace, 'atom-github://item/{pattern}', component, 'theKey'); + assert.isTrue(component.state.theKey); + + assert.isTrue(workspace.hide('atom-github://item/match1')); + assert.isTrue(workspace.hide('atom-github://item/match0')); + + assert.isTrue(component.setState.calledWith({theKey: false})); + }); + }); + + it('stops updating when disposed', async function() { + sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'theKey'); + assert.isFalse(component.state.theKey); + + sub.dispose(); + await workspace.open('atom-github://item'); + assert.isFalse(component.setState.called); + + await workspace.hide('atom-github://item'); + assert.isFalse(component.setState.called); + }); +}); From f35b12ef43264c874f3068c18def2b96002c64eb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 09:42:14 -0400 Subject: [PATCH 0769/4053] Cover that last conditional :ok_hand: --- test/watch-workspace-item.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/watch-workspace-item.test.js b/test/watch-workspace-item.test.js index 9ea6826a5b..25c21753c3 100644 --- a/test/watch-workspace-item.test.js +++ b/test/watch-workspace-item.test.js @@ -1,4 +1,5 @@ import {watchWorkspaceItem} from '../lib/watch-workspace-item'; +import URIPattern from '../lib/atom/uri-pattern'; describe('watchWorkspaceItem', function() { let sub, atomEnv, workspace, component; @@ -61,6 +62,14 @@ describe('watchWorkspaceItem', function() { assert.isTrue(component.state.theKey); }); + + it('accepts a preconstructed URIPattern', async function() { + await workspace.open('atom-github://item/one'); + const u = new URIPattern('atom-github://item/{pattern}'); + + sub = watchWorkspaceItem(workspace, u, component, 'theKey'); + assert.isTrue(component.state.theKey); + }); }); describe('workspace events', function() { From 50c6ff13dc579b42fdd63b0490bdfee932524495 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 10:24:19 -0400 Subject: [PATCH 0770/4053] Unescape *all* doubled dashes in a URI pattern --- lib/atom/uri-pattern.js | 2 +- test/atom/uri-pattern.test.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/atom/uri-pattern.js b/lib/atom/uri-pattern.js index 0a213f50e9..558a54f95d 100644 --- a/lib/atom/uri-pattern.js +++ b/lib/atom/uri-pattern.js @@ -246,7 +246,7 @@ function dashEscape(raw) { * Reverse the escaping performed by `dashEscape` by un-doubling `-` characters. */ function dashUnescape(escaped) { - return escaped.replace('--', '-'); + return escaped.replace(/--/g, '-'); } /** diff --git a/test/atom/uri-pattern.test.js b/test/atom/uri-pattern.test.js index 4cb77a52ad..a36f332401 100644 --- a/test/atom/uri-pattern.test.js +++ b/test/atom/uri-pattern.test.js @@ -38,6 +38,14 @@ describe('URIPattern', function() { assert.isTrue(pattern.matches('proto://host/foo#exact').ok()); assert.isFalse(pattern.matches('proto://host/foo#nope').ok()); }); + + it('escapes and unescapes dashes', function() { + assert.isTrue( + new URIPattern('atom-github://with-many-dashes') + .matches('atom-github://with-many-dashes') + .ok(), + ); + }); }); describe('parameter placeholders', function() { From e5440cb1ebf6517253bd54cf869a4555fbd81333 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 10:25:29 -0400 Subject: [PATCH 0771/4053] Use watchWorkspaceItem to track open CommitPreviewItems --- lib/controllers/commit-controller.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index 099ebfe810..e5301ce839 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -3,18 +3,18 @@ import {TextBuffer} from 'atom'; import React from 'react'; import PropTypes from 'prop-types'; -import {CompositeDisposable} from 'event-kit'; +import {CompositeDisposable, Disposable} from 'event-kit'; import fs from 'fs-extra'; import CommitView from '../views/commit-view'; import RefHolder from '../models/ref-holder'; import CommitPreviewItem from '../items/commit-preview-item'; import {AuthorPropType, UserStorePropType} from '../prop-types'; +import {watchWorkspaceItem} from '../watch-workspace-item'; import {autobind} from '../helpers'; import {addEvent} from '../reporter-proxy'; import URIPattern from '../atom/uri-pattern'; - export const COMMIT_GRAMMAR_SCOPE = 'text.git-commit'; export default class CommitController extends React.Component { @@ -57,9 +57,8 @@ export default class CommitController extends React.Component { this.commitMessageBuffer.onDidChange(this.handleMessageChange), ); - this.state = { - commitPreviewOpen: this.isCommitPreviewOpen(), - }; + this.previewWatcherSub = new Disposable(); + this.watchCommitPreviewItems(); } isCommitPreviewOpen() { @@ -147,6 +146,22 @@ export default class CommitController extends React.Component { this.subscriptions.dispose(); } + /** + * Track the presence of CommitPreviewItems corresponding to the current repository with this.state.commitPreviewOpen. + */ + watchCommitPreviewItems() { + this.subscriptions.remove(this.previewWatcherSub); + this.previewWatcherSub.dispose(); + + this.previewWatcherSub = watchWorkspaceItem( + this.props.workspace, + CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath()), + this, + 'commitPreviewOpen', + ); + this.subscriptions.add(this.previewWatcherSub); + } + commit(message, coAuthors = [], amend = false) { let msg, verbatim; if (this.isCommitMessageEditorExpanded()) { @@ -283,7 +298,6 @@ export default class CommitController extends React.Component { } toggleCommitPreview() { - this.setState({commitPreviewOpen: !this.state.commitPreviewOpen}); return this.props.workspace.toggle( CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath()), ); From e16b0f8bec92ed96142a9a9c2dfd49bf45e4da4d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 10:27:28 -0400 Subject: [PATCH 0772/4053] Register a fake opener for commit preview item URIs --- test/controllers/commit-controller.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index ffd4c1c9be..9abb689022 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -6,6 +6,7 @@ import {shallow, mount} from 'enzyme'; import Commit from '../../lib/models/commit'; import {nullBranch} from '../../lib/models/branch'; import UserStore from '../../lib/models/user-store'; +import URIPattern from '../../lib/atom/uri-pattern'; import CommitController, {COMMIT_GRAMMAR_SCOPE} from '../../lib/controllers/commit-controller'; import CommitPreviewItem from '../../lib/items/commit-preview-item'; @@ -29,6 +30,16 @@ describe('CommitController', function() { const noop = () => {}; const store = new UserStore({config}); + // Ensure the Workspace doesn't mangle atom-github://... URIs + const pattern = new URIPattern(CommitPreviewItem.uriPattern); + workspace.addOpener(uri => { + if (pattern.matches(uri).ok()) { + return {getURI() { return uri; }}; + } else { + return undefined; + } + }); + app = ( Date: Thu, 1 Nov 2018 10:46:04 -0400 Subject: [PATCH 0773/4053] Refactor watchWorkspaceItem to use a class --- lib/watch-workspace-item.js | 84 ++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js index 0dd844bc46..0f231d7ba5 100644 --- a/lib/watch-workspace-item.js +++ b/lib/watch-workspace-item.js @@ -2,39 +2,65 @@ import {CompositeDisposable} from 'atom'; import URIPattern from './atom/uri-pattern'; -export function watchWorkspaceItem(workspace, pattern, component, stateKey) { - const uPattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); +class ItemWatcher { + constructor(workspace, pattern, component, stateKey) { + this.workspace = workspace; + this.pattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); + this.component = component; + this.stateKey = stateKey; + + this.itemCount = workspace.getPaneItems().filter(this.itemMatches).length; + this.subs = new CompositeDisposable(); + } - function itemMatches(item) { - return item.getURI && uPattern.matches(item.getURI()).ok(); + setInitialState() { + if (!this.component.state) { + this.component.state = {}; + } + this.component.state[this.stateKey] = this.itemCount > 0; + return this; } - if (!component.state) { - component.state = {}; + subscribeToWorkspace() { + this.subs.dispose(); + this.subs = new CompositeDisposable( + this.workspace.onDidAddPaneItem(this.itemAdded), + this.workspace.onDidDestroyPaneItem(this.itemDestroyed), + ); + return this; } - let itemCount = workspace.getPaneItems().filter(itemMatches).length; - component.state[stateKey] = itemCount > 0; - - return new CompositeDisposable( - workspace.onDidAddPaneItem(({item}) => { - const hadOpen = itemCount > 0; - if (itemMatches(item)) { - itemCount++; - - if (itemCount > 0 && !hadOpen) { - component.setState({[stateKey]: true}); - } + + itemMatches = item => item.getURI && this.pattern.matches(item.getURI()).ok() + + itemAdded = ({item}) => { + const hadOpen = this.itemCount > 0; + if (this.itemMatches(item)) { + this.itemCount++; + + if (this.itemCount > 0 && !hadOpen) { + this.component.setState({[this.stateKey]: true}); } - }), - workspace.onDidDestroyPaneItem(({item}) => { - const hadOpen = itemCount > 0; - if (itemMatches(item)) { - itemCount--; - - if (itemCount <= 0 && hadOpen) { - component.setState({[stateKey]: false}); - } + } + } + + itemDestroyed = ({item}) => { + const hadOpen = this.itemCount > 0; + if (this.itemMatches(item)) { + this.itemCount--; + + if (this.itemCount <= 0 && hadOpen) { + this.component.setState({[this.stateKey]: false}); } - }), - ); + } + } + + dispose() { + this.subs.dispose(); + } +} + +export function watchWorkspaceItem(workspace, pattern, component, stateKey) { + return new ItemWatcher(workspace, pattern, component, stateKey) + .setInitialState() + .subscribeToWorkspace(); } From adecb128224fc4117d58a6fde60d7c0e9264f218 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 11:07:57 -0400 Subject: [PATCH 0774/4053] Update an item watcher's pattern on a mounted component with setPattern --- lib/watch-workspace-item.js | 24 +++++++++++++++++++++++- test/watch-workspace-item.test.js | 27 ++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js index 0f231d7ba5..e55fdae9ba 100644 --- a/lib/watch-workspace-item.js +++ b/lib/watch-workspace-item.js @@ -9,7 +9,7 @@ class ItemWatcher { this.component = component; this.stateKey = stateKey; - this.itemCount = workspace.getPaneItems().filter(this.itemMatches).length; + this.itemCount = this.getItemCount(); this.subs = new CompositeDisposable(); } @@ -30,8 +30,30 @@ class ItemWatcher { return this; } + setPattern(pattern) { + const wasTrue = this.itemCount > 0; + + this.pattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); + + // Update the item count to match the new pattern + this.itemCount = this.getItemCount(); + + // Update the component's state if it's changed as a result + if (wasTrue && this.itemCount <= 0) { + return new Promise(resolve => this.component.setState({[this.stateKey]: false}, resolve)); + } else if (!wasTrue && this.itemCount > 0) { + return new Promise(resolve => this.component.setState({[this.stateKey]: true}, resolve)); + } else { + return Promise.resolve(); + } + } + itemMatches = item => item.getURI && this.pattern.matches(item.getURI()).ok() + getItemCount() { + return this.workspace.getPaneItems().filter(this.itemMatches).length; + } + itemAdded = ({item}) => { const hadOpen = this.itemCount > 0; if (this.itemMatches(item)) { diff --git a/test/watch-workspace-item.test.js b/test/watch-workspace-item.test.js index 25c21753c3..f592685546 100644 --- a/test/watch-workspace-item.test.js +++ b/test/watch-workspace-item.test.js @@ -10,7 +10,7 @@ describe('watchWorkspaceItem', function() { component = { state: {}, - setState: sinon.stub().resolves(), + setState: sinon.stub().callsFake((updater, cb) => cb && cb()), }; workspace.addOpener(uri => { @@ -131,4 +131,29 @@ describe('watchWorkspaceItem', function() { await workspace.hide('atom-github://item'); assert.isFalse(component.setState.called); }); + + describe('setPattern', function() { + it('immediately updates the state based on the new pattern', async function() { + sub = watchWorkspaceItem(workspace, 'atom-github://item0/{pattern}', component, 'theKey'); + assert.isFalse(component.state.theKey); + + await workspace.open('atom-github://item1/match'); + assert.isFalse(component.setState.called); + + await sub.setPattern('atom-github://item1/{pattern}'); + assert.isFalse(component.state.theKey); + assert.isTrue(component.setState.calledWith({theKey: true})); + }); + + it('uses the new pattern to keep state up to date', async function() { + sub = watchWorkspaceItem(workspace, 'atom-github://item0/{pattern}', component, 'theKey'); + await sub.setPattern('atom-github://item1/{pattern}'); + + await workspace.open('atom-github://item0/match'); + assert.isFalse(component.setState.called); + + await workspace.open('atom-github://item1/match'); + assert.isTrue(component.setState.calledWith({theKey: true})); + }); + }); }); From 60a9a8212277bfd2062e9c8de678400aded64c3b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 11:10:49 -0400 Subject: [PATCH 0775/4053] Use setPattern to update the existing preview item watcher --- lib/controllers/commit-controller.js | 42 +++++++--------------- test/controllers/commit-controller.test.js | 32 +++++++++++++++++ 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index e5301ce839..f4515eec8e 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -57,19 +57,13 @@ export default class CommitController extends React.Component { this.commitMessageBuffer.onDidChange(this.handleMessageChange), ); - this.previewWatcherSub = new Disposable(); - this.watchCommitPreviewItems(); - } - - isCommitPreviewOpen() { - const items = this.props.workspace.getPaneItems(); - const uriPattern = new URIPattern(CommitPreviewItem.uriPattern); - for (const item of items) { - if (item.getURI && uriPattern.matches(item.getURI())) { - return true; - } - } - return false; + this.previewWatcher = watchWorkspaceItem( + this.props.workspace, + CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath()), + this, + 'commitPreviewOpen', + ); + this.subscriptions.add(this.previewWatcher); } componentDidMount() { @@ -140,28 +134,18 @@ export default class CommitController extends React.Component { } else { this.commitMessageBuffer.setTextViaDiff(this.getCommitMessage()); } + + if (prevProps.repository !== this.props.repository) { + this.previewWatcher.setPattern( + CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath()), + ); + } } componentWillUnmount() { this.subscriptions.dispose(); } - /** - * Track the presence of CommitPreviewItems corresponding to the current repository with this.state.commitPreviewOpen. - */ - watchCommitPreviewItems() { - this.subscriptions.remove(this.previewWatcherSub); - this.previewWatcherSub.dispose(); - - this.previewWatcherSub = watchWorkspaceItem( - this.props.workspace, - CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath()), - this, - 'commitPreviewOpen', - ); - this.subscriptions.add(this.previewWatcherSub); - } - commit(message, coAuthors = [], amend = false) { let msg, verbatim; if (this.isCommitMessageEditorExpanded()) { diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 9abb689022..8e450e80dc 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -449,5 +449,37 @@ describe('CommitController', function() { assert.isTrue(workspace.toggle.calledThrice); assert.isTrue(wrapper.find('CommitView').prop('commitPreviewOpen')); }); + + it('toggles the commit preview pane for the active repository', async function() { + const workdir0 = await cloneRepository('three-files'); + const repository0 = await buildRepository(workdir0); + + const workdir1 = await cloneRepository('three-files'); + const repository1 = await buildRepository(workdir1); + + const wrapper = shallow(React.cloneElement(app, {repository: repository0})); + + assert.isFalse(wrapper.find('CommitView').prop('commitPreviewOpen')); + + await wrapper.find('CommitView').prop('toggleCommitPreview')(); + assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); + assert.isFalse(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir1))); + assert.isTrue(wrapper.find('CommitView').prop('commitPreviewOpen')); + + wrapper.setProps({repository: repository1}); + assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); + assert.isFalse(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir1))); + assert.isFalse(wrapper.find('CommitView').prop('commitPreviewOpen')); + + await wrapper.find('CommitView').prop('toggleCommitPreview')(); + assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); + assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir1))); + assert.isTrue(wrapper.find('CommitView').prop('commitPreviewOpen')); + + await wrapper.find('CommitView').prop('toggleCommitPreview')(); + assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); + assert.isFalse(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir1))); + assert.isFalse(wrapper.find('CommitView').prop('commitPreviewOpen')); + }); }); }); From afb2a1ee77f198d34640fd4a094e7322de4b456f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 11:21:23 -0400 Subject: [PATCH 0776/4053] :art: group related props --- lib/views/file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 92b9b08e4b..b3d9e348bd 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -36,6 +36,7 @@ export default class FilePatchView extends React.Component { repository: PropTypes.object.isRequired, hasUndoHistory: PropTypes.bool.isRequired, useEditorAutoHeight: PropTypes.bool, + isActive: PropTypes.bool, workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, @@ -54,7 +55,6 @@ export default class FilePatchView extends React.Component { toggleSymlinkChange: PropTypes.func.isRequired, undoLastDiscard: PropTypes.func.isRequired, discardRows: PropTypes.func.isRequired, - isActive: PropTypes.bool, handleMouseDown: PropTypes.func, } From eb42fb24799fbed2b17c2350c32eab54058f7f27 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 11:31:59 -0400 Subject: [PATCH 0777/4053] Require isActive prop in FilePatchView --- lib/views/file-patch-view.js | 2 +- test/views/file-patch-view.test.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index b3d9e348bd..e40a8cc021 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -36,7 +36,7 @@ export default class FilePatchView extends React.Component { repository: PropTypes.object.isRequired, hasUndoHistory: PropTypes.bool.isRequired, useEditorAutoHeight: PropTypes.bool, - isActive: PropTypes.bool, + isActive: PropTypes.bool.isRequired, workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 8964aac090..da8b386b8c 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -53,6 +53,7 @@ describe('FilePatchView', function() { selectionMode: 'line', selectedRows: new Set(), repository, + isActive: true, workspace, config: atomEnv.config, From 6bc589037442851a2fa9708f9e21d5274a91d277 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 11:38:14 -0400 Subject: [PATCH 0778/4053] Add a CSS class to FilePatchView's root when inactive --- lib/views/file-patch-view.js | 1 + test/views/file-patch-view.test.js | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index e40a8cc021..4a52d2ed85 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -164,6 +164,7 @@ export default class FilePatchView extends React.Component { `github-FilePatchView--${this.props.stagingStatus}`, {'github-FilePatchView--blank': !this.props.filePatch.isPresent()}, {'github-FilePatchView--hunkMode': this.props.selectionMode === 'hunk'}, + {'github-FilePatchView--inactive': !this.props.isActive}, ); return ( diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index da8b386b8c..ed9ebfdf95 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -116,6 +116,13 @@ describe('FilePatchView', function() { assert.isTrue(wrapper.find('.github-FilePatchView--hunkMode').exists()); }); + it('sets the root class when inactive', function() { + const wrapper = shallow(buildApp({isActive: true})); + assert.isFalse(wrapper.find('.github-FilePatchView--inactive').exists()); + wrapper.setProps({isActive: false}); + assert.isTrue(wrapper.find('.github-FilePatchView--inactive').exists()); + }); + it('preserves the selection index when a new file patch arrives in line selection mode', function() { const selectedRowsChanged = sinon.spy(); const wrapper = mount(buildApp({ From 6a70de9c87dd4bceeee40c74df5966c089b680a5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 11:42:33 -0400 Subject: [PATCH 0779/4053] Wait I got that backwards --- lib/views/file-patch-view.js | 2 +- test/views/file-patch-view.test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 4a52d2ed85..a8e0f4f008 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -164,7 +164,7 @@ export default class FilePatchView extends React.Component { `github-FilePatchView--${this.props.stagingStatus}`, {'github-FilePatchView--blank': !this.props.filePatch.isPresent()}, {'github-FilePatchView--hunkMode': this.props.selectionMode === 'hunk'}, - {'github-FilePatchView--inactive': !this.props.isActive}, + {'github-FilePatchView--active': this.props.isActive}, ); return ( diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index ed9ebfdf95..7c7073bf1f 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -116,11 +116,11 @@ describe('FilePatchView', function() { assert.isTrue(wrapper.find('.github-FilePatchView--hunkMode').exists()); }); - it('sets the root class when inactive', function() { + it('sets the root class when active', function() { const wrapper = shallow(buildApp({isActive: true})); - assert.isFalse(wrapper.find('.github-FilePatchView--inactive').exists()); + assert.isTrue(wrapper.find('.github-FilePatchView--active').exists()); wrapper.setProps({isActive: false}); - assert.isTrue(wrapper.find('.github-FilePatchView--inactive').exists()); + assert.isFalse(wrapper.find('.github-FilePatchView--active').exists()); }); it('preserves the selection index when a new file patch arrives in line selection mode', function() { From 17dbf1e4aef78c7a89474188f752142be56e5676 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 11:44:45 -0400 Subject: [PATCH 0780/4053] Don't style cursor lines in inactive FilePatchView editors --- styles/file-patch-view.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index c04a3445b0..a2694cc9b2 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -163,7 +163,7 @@ .hunk-line-mixin(@bg;) { background-color: fade(@bg, 18%); - &.line.cursor-line { + .github-FilePatchView--active &.line.cursor-line { background-color: fade(@bg, 28%); } } From b1bf5c89f956d3a5a4c30b32168c107be674eb16 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 11:48:03 -0400 Subject: [PATCH 0781/4053] Okay fine let's do a class for both active and inactive --- lib/views/file-patch-view.js | 1 + test/views/file-patch-view.test.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index a8e0f4f008..4cbf23538a 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -165,6 +165,7 @@ export default class FilePatchView extends React.Component { {'github-FilePatchView--blank': !this.props.filePatch.isPresent()}, {'github-FilePatchView--hunkMode': this.props.selectionMode === 'hunk'}, {'github-FilePatchView--active': this.props.isActive}, + {'github-FilePatchView--inactive': !this.props.isActive}, ); return ( diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 7c7073bf1f..05a919011a 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -116,11 +116,13 @@ describe('FilePatchView', function() { assert.isTrue(wrapper.find('.github-FilePatchView--hunkMode').exists()); }); - it('sets the root class when active', function() { + it('sets the root class when active or inactive', function() { const wrapper = shallow(buildApp({isActive: true})); assert.isTrue(wrapper.find('.github-FilePatchView--active').exists()); + assert.isFalse(wrapper.find('.github-FilePatchView--inactive').exists()); wrapper.setProps({isActive: false}); assert.isFalse(wrapper.find('.github-FilePatchView--active').exists()); + assert.isTrue(wrapper.find('.github-FilePatchView--inactive').exists()); }); it('preserves the selection index when a new file patch arrives in line selection mode', function() { From a2fc2acbac3a642d661f0b9411787873be617363 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 11:52:00 -0400 Subject: [PATCH 0782/4053] Hide selection regions in inactive FilePatch editors --- styles/file-patch-view.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index a2694cc9b2..8fe131c6c0 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -225,4 +225,10 @@ } } } + + // Inactive + + &--inactive .highlights .highlight.selection { + display: none; + } } From 48aa0e6c6896eee081ac735355aedf2230a7539e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 12:49:46 -0400 Subject: [PATCH 0783/4053] :shirt: unused imports --- lib/controllers/commit-controller.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index f4515eec8e..059a7282bb 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -3,7 +3,7 @@ import {TextBuffer} from 'atom'; import React from 'react'; import PropTypes from 'prop-types'; -import {CompositeDisposable, Disposable} from 'event-kit'; +import {CompositeDisposable} from 'event-kit'; import fs from 'fs-extra'; import CommitView from '../views/commit-view'; @@ -13,7 +13,6 @@ import {AuthorPropType, UserStorePropType} from '../prop-types'; import {watchWorkspaceItem} from '../watch-workspace-item'; import {autobind} from '../helpers'; import {addEvent} from '../reporter-proxy'; -import URIPattern from '../atom/uri-pattern'; export const COMMIT_GRAMMAR_SCOPE = 'text.git-commit'; From 72e1c611f63fb21b8973b46ca0329fbf0aeda9a4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 14:30:02 -0400 Subject: [PATCH 0784/4053] Shhhhhhh this is totally related --- keymaps/git.cson | 1 + 1 file changed, 1 insertion(+) diff --git a/keymaps/git.cson b/keymaps/git.cson index 368b2d5759..2075d94559 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -26,6 +26,7 @@ 'shift-tab': 'core:focus-previous' 'o': 'github:open-file' 'left': 'core:move-left' + 'cmd-left': 'core:move-left' '.github-CommitView button': 'tab': 'core:focus-next' From bd43ddd1d30e22d4d60c6d3a0cbd1e45b85cd164 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 1 Nov 2018 14:49:51 -0400 Subject: [PATCH 0785/4053] Add .native-key-bindings to within CommitView --- lib/views/commit-view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index 400c96c4d0..6901706915 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -164,7 +164,7 @@ export default class CommitView extends React.Component {
{this.commitIsEnabled(false) && } From a8bb1e98e30072351f85d54a8573c3dd318da35b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 2 Nov 2018 12:03:35 -0400 Subject: [PATCH 0796/4053] Tests to add an {active: true} flag on watchWorkspaceItem When set to true, the state key is set to `true` only when a matching item is the active item in some Pane. When set to false, the state key is set to true when a matching item is open anywhere in the Workspace. --- test/watch-workspace-item.test.js | 46 ++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/test/watch-workspace-item.test.js b/test/watch-workspace-item.test.js index f592685546..533d74fb99 100644 --- a/test/watch-workspace-item.test.js +++ b/test/watch-workspace-item.test.js @@ -1,7 +1,7 @@ import {watchWorkspaceItem} from '../lib/watch-workspace-item'; import URIPattern from '../lib/atom/uri-pattern'; -describe('watchWorkspaceItem', function() { +describe.only('watchWorkspaceItem', function() { let sub, atomEnv, workspace, component; beforeEach(function() { @@ -70,6 +70,42 @@ describe('watchWorkspaceItem', function() { sub = watchWorkspaceItem(workspace, u, component, 'theKey'); assert.isTrue(component.state.theKey); }); + + describe('{active: true}', function() { + it('is false when the pane is not open', async function() { + await workspace.open('atom-github://nonmatching'); + + sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey', {active: true}); + assert.isFalse(component.state.someKey); + }); + + it('is false when the pane is open, but not active', async function() { + await workspace.open('atom-github://item'); + await workspace.open('atom-github://nonmatching'); + + sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey', {active: true}); + assert.isFalse(component.state.someKey); + }); + + it('is true when the pane is open and active in the workspace', async function() { + await workspace.open('atom-github://nonmatching'); + await workspace.open('atom-github://item'); + + sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey', {active: true}); + assert.isTrue(component.state.someKey); + }); + + it('is true when the pane is open and active in any pane', async function() { + await workspace.open('atom-github://item', {location: 'right'}); + await workspace.open('atom-github://nonmatching'); + + assert.strictEqual(workspace.getRightDock().getActivePaneItem().getURI(), 'atom-github://item'); + assert.strictEqual(workspace.getActivePaneItem(), 'atom-github://nonmatching'); + + sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey', {active: true}); + assert.isTrue(component.state.someKey); + }); + }); }); describe('workspace events', function() { @@ -118,6 +154,10 @@ describe('watchWorkspaceItem', function() { assert.isTrue(component.setState.calledWith({theKey: false})); }); + + describe('{active: true}', function() { + // + }); }); it('stops updating when disposed', async function() { @@ -155,5 +195,9 @@ describe('watchWorkspaceItem', function() { await workspace.open('atom-github://item1/match'); assert.isTrue(component.setState.calledWith({theKey: true})); }); + + describe('{active: true}', function() { + // + }); }); }); From e6214f93a6b3706dd655aec434b13d13fe426b3b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 2 Nov 2018 12:04:05 -0400 Subject: [PATCH 0797/4053] Adjust CommitController tests for tri-state behavior --- test/controllers/commit-controller.test.js | 43 ++++++++++++++-------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 67e8654a66..6142c3d2b5 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -430,24 +430,38 @@ describe('CommitController', function() { it('opens and closes commit preview pane', async function() { const workdir = await cloneRepository('three-files'); const repository = await buildRepository(workdir); + const previewURI = CommitPreviewItem.buildURI(workdir); const wrapper = shallow(React.cloneElement(app, {repository})); - sinon.spy(workspace, 'toggle'); - - assert.isFalse(wrapper.find('CommitView').prop('commitPreviewOpen')); + assert.isFalse(wrapper.find('CommitView').prop('commitPreviewActive')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); - assert.isTrue(workspace.toggle.calledWith(CommitPreviewItem.buildURI(workdir))); - assert.isTrue(wrapper.find('CommitView').prop('commitPreviewOpen')); + + // Commit preview open as active pane item + assert.strictEqual(workspace.getActivePaneItem().getURI(), previewURI); + assert.strictEqual(workspace.getActivePaneItem(), workspace.paneForItem(previewURI).getPendingItem()); + assert.isTrue(wrapper.find('CommitView').prop('commitPreviewActive')); + + await workspace.open(__filename); + + // Commit preview open, but not active + assert.include(workspace.getAllPaneItems().map(i => i.getURI()), previewURI); + assert.notStrictEqual(workspace.getActivePaneItem().getURI(), previewURI); + assert.isFalse(wrapper.find('CommitView').prop('commitPreviewActive')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); - assert.isTrue(workspace.toggle.calledTwice); - assert.isFalse(wrapper.find('CommitView').prop('commitPreviewOpen')); + + // Open as active pane item again + assert.strictEqual(workspace.getActivePaneItem().getURI(), previewURI); + assert.strictEqual(workspace.getActivePaneItem(), workspace.paneForItem(previewURI).getPendingItem()); + assert.isTrue(wrapper.find('CommitView').prop('commitPreviewActive')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); - assert.isTrue(workspace.toggle.calledThrice); - assert.isTrue(wrapper.find('CommitView').prop('commitPreviewOpen')); + + // Commit preview closed + assert.notInclude(workspace.getAllPaneItems().map(i => i.getURI()), previewURI); + assert.isTrue(wrapper.find('CommitView').prop('commitPreviewActive')); }); it('records a metrics event when pane is toggled', async function() { @@ -464,7 +478,6 @@ describe('CommitController', function() { assert.isTrue(reporterProxy.addEvent.calledOnceWithExactly('toggle-commit-preview', {package: 'github'})); }); - it('toggles the commit preview pane for the active repository', async function() { const workdir0 = await cloneRepository('three-files'); const repository0 = await buildRepository(workdir0); @@ -474,27 +487,27 @@ describe('CommitController', function() { const wrapper = shallow(React.cloneElement(app, {repository: repository0})); - assert.isFalse(wrapper.find('CommitView').prop('commitPreviewOpen')); + assert.isFalse(wrapper.find('CommitView').prop('commitPreviewActive')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); assert.isFalse(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir1))); - assert.isTrue(wrapper.find('CommitView').prop('commitPreviewOpen')); + assert.isTrue(wrapper.find('CommitView').prop('commitPreviewActive')); wrapper.setProps({repository: repository1}); assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); assert.isFalse(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir1))); - assert.isFalse(wrapper.find('CommitView').prop('commitPreviewOpen')); + assert.isFalse(wrapper.find('CommitView').prop('commitPreviewActive')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir1))); - assert.isTrue(wrapper.find('CommitView').prop('commitPreviewOpen')); + assert.isTrue(wrapper.find('CommitView').prop('commitPreviewActive')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); assert.isFalse(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir1))); - assert.isFalse(wrapper.find('CommitView').prop('commitPreviewOpen')); + assert.isFalse(wrapper.find('CommitView').prop('commitPreviewActive')); }); }); }); From c91ba1dee175d6176a8a77d7f6573a7a477df8be Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 2 Nov 2018 12:04:22 -0400 Subject: [PATCH 0798/4053] Rename the prop because we care about active now instead of just open --- lib/controllers/commit-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index 4ddf9c3728..601c084fa8 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -60,7 +60,7 @@ export default class CommitController extends React.Component { this.props.workspace, CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath()), this, - 'commitPreviewOpen', + 'commitPreviewActive', ); this.subscriptions.add(this.previewWatcher); } From 67e70d081f7a1c79a287cee415305a476e6510a6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 2 Nov 2018 12:04:37 -0400 Subject: [PATCH 0799/4053] Incomplete work to get {active: true} working --- lib/watch-workspace-item.js | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js index e55fdae9ba..f047648334 100644 --- a/lib/watch-workspace-item.js +++ b/lib/watch-workspace-item.js @@ -3,21 +3,31 @@ import {CompositeDisposable} from 'atom'; import URIPattern from './atom/uri-pattern'; class ItemWatcher { - constructor(workspace, pattern, component, stateKey) { + constructor(workspace, pattern, component, stateKey, opts) { this.workspace = workspace; this.pattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); this.component = component; this.stateKey = stateKey; + this.opts = opts; - this.itemCount = this.getItemCount(); + this.itemCount = this.readItemCount(); + this.activeCount = this.readActiveCount(); this.subs = new CompositeDisposable(); } + getCurrentState() { + if (this.opts.active) { + return this.activeCount > 0; + } else { + return this.itemCount > 0; + } + } + setInitialState() { if (!this.component.state) { this.component.state = {}; } - this.component.state[this.stateKey] = this.itemCount > 0; + this.component.state[this.stateKey] = this.getCurrentState(); return this; } @@ -50,10 +60,14 @@ class ItemWatcher { itemMatches = item => item.getURI && this.pattern.matches(item.getURI()).ok() - getItemCount() { + readItemCount() { return this.workspace.getPaneItems().filter(this.itemMatches).length; } + readActiveCount() { + return this.workspace.getPanes().filter(pane => this.itemMatches(pane.getActiveItem())).length; + } + itemAdded = ({item}) => { const hadOpen = this.itemCount > 0; if (this.itemMatches(item)) { @@ -81,8 +95,13 @@ class ItemWatcher { } } -export function watchWorkspaceItem(workspace, pattern, component, stateKey) { - return new ItemWatcher(workspace, pattern, component, stateKey) +export function watchWorkspaceItem(workspace, pattern, component, stateKey, options = {}) { + const opts = { + active: false, + ...options, + }; + + return new ItemWatcher(workspace, pattern, component, stateKey, opts) .setInitialState() .subscribeToWorkspace(); } From 29f75d78fa3996599b4f49f8ef48797212d19725 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 2 Nov 2018 14:24:45 -0700 Subject: [PATCH 0800/4053] Check if item exists before calling its `getURI` method --- lib/watch-workspace-item.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js index f047648334..99f46c723d 100644 --- a/lib/watch-workspace-item.js +++ b/lib/watch-workspace-item.js @@ -58,7 +58,9 @@ class ItemWatcher { } } - itemMatches = item => item.getURI && this.pattern.matches(item.getURI()).ok() + itemMatches = item => { + return item && item.getURI && this.pattern.matches(item.getURI()).ok(); + } readItemCount() { return this.workspace.getPaneItems().filter(this.itemMatches).length; From 8904f391413f39ca82006bf38f1d968177152b95 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 2 Nov 2018 15:30:24 -0700 Subject: [PATCH 0801/4053] Actually, let's inline that function --- lib/watch-workspace-item.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js index 99f46c723d..17c315542c 100644 --- a/lib/watch-workspace-item.js +++ b/lib/watch-workspace-item.js @@ -58,9 +58,7 @@ class ItemWatcher { } } - itemMatches = item => { - return item && item.getURI && this.pattern.matches(item.getURI()).ok(); - } + itemMatches = item => item && item.getURI && this.pattern.matches(item.getURI()).ok() readItemCount() { return this.workspace.getPaneItems().filter(this.itemMatches).length; From b9ef77305e1abdf29f1952c5e47df6c66ab5aae0 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 2 Nov 2018 15:31:48 -0700 Subject: [PATCH 0802/4053] Revert "Incomplete work to get {active: true} working" This reverts commit 67e70d081f7a1c79a287cee415305a476e6510a6. --- lib/watch-workspace-item.js | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js index 17c315542c..05da51cf13 100644 --- a/lib/watch-workspace-item.js +++ b/lib/watch-workspace-item.js @@ -3,31 +3,21 @@ import {CompositeDisposable} from 'atom'; import URIPattern from './atom/uri-pattern'; class ItemWatcher { - constructor(workspace, pattern, component, stateKey, opts) { + constructor(workspace, pattern, component, stateKey) { this.workspace = workspace; this.pattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); this.component = component; this.stateKey = stateKey; - this.opts = opts; - this.itemCount = this.readItemCount(); - this.activeCount = this.readActiveCount(); + this.itemCount = this.getItemCount(); this.subs = new CompositeDisposable(); } - getCurrentState() { - if (this.opts.active) { - return this.activeCount > 0; - } else { - return this.itemCount > 0; - } - } - setInitialState() { if (!this.component.state) { this.component.state = {}; } - this.component.state[this.stateKey] = this.getCurrentState(); + this.component.state[this.stateKey] = this.itemCount > 0; return this; } @@ -60,14 +50,10 @@ class ItemWatcher { itemMatches = item => item && item.getURI && this.pattern.matches(item.getURI()).ok() - readItemCount() { + getItemCount() { return this.workspace.getPaneItems().filter(this.itemMatches).length; } - readActiveCount() { - return this.workspace.getPanes().filter(pane => this.itemMatches(pane.getActiveItem())).length; - } - itemAdded = ({item}) => { const hadOpen = this.itemCount > 0; if (this.itemMatches(item)) { @@ -95,13 +81,8 @@ class ItemWatcher { } } -export function watchWorkspaceItem(workspace, pattern, component, stateKey, options = {}) { - const opts = { - active: false, - ...options, - }; - - return new ItemWatcher(workspace, pattern, component, stateKey, opts) +export function watchWorkspaceItem(workspace, pattern, component, stateKey) { + return new ItemWatcher(workspace, pattern, component, stateKey) .setInitialState() .subscribeToWorkspace(); } From 669015bac1df5f168eccd7d07e20ca4192d312a3 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 2 Nov 2018 17:19:18 -0700 Subject: [PATCH 0803/4053] Assert on the URI, not item --- test/watch-workspace-item.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/watch-workspace-item.test.js b/test/watch-workspace-item.test.js index 533d74fb99..96a857158f 100644 --- a/test/watch-workspace-item.test.js +++ b/test/watch-workspace-item.test.js @@ -100,7 +100,7 @@ describe.only('watchWorkspaceItem', function() { await workspace.open('atom-github://nonmatching'); assert.strictEqual(workspace.getRightDock().getActivePaneItem().getURI(), 'atom-github://item'); - assert.strictEqual(workspace.getActivePaneItem(), 'atom-github://nonmatching'); + assert.strictEqual(workspace.getActivePaneItem().getURI(), 'atom-github://nonmatching'); sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey', {active: true}); assert.isTrue(component.state.someKey); From 7258c297cc99d93366f40770683a1133614a7f3c Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 2 Nov 2018 17:23:00 -0700 Subject: [PATCH 0804/4053] Stopgap for funky test issue (`atom-github://item` opens in right dock) --- test/watch-workspace-item.test.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/watch-workspace-item.test.js b/test/watch-workspace-item.test.js index 96a857158f..1a392132b7 100644 --- a/test/watch-workspace-item.test.js +++ b/test/watch-workspace-item.test.js @@ -80,10 +80,11 @@ describe.only('watchWorkspaceItem', function() { }); it('is false when the pane is open, but not active', async function() { - await workspace.open('atom-github://item'); + // TODO: fix this test suite so that 'atom-github://item' works + await workspace.open('atom-github://some-item'); await workspace.open('atom-github://nonmatching'); - sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey', {active: true}); + sub = watchWorkspaceItem(workspace, 'atom-github://some-item', component, 'someKey', {active: true}); assert.isFalse(component.state.someKey); }); @@ -161,14 +162,15 @@ describe.only('watchWorkspaceItem', function() { }); it('stops updating when disposed', async function() { - sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'theKey'); + // TODO: fix this test suite so that 'atom-github://item' works + sub = watchWorkspaceItem(workspace, 'atom-github://some-item', component, 'theKey'); assert.isFalse(component.state.theKey); sub.dispose(); - await workspace.open('atom-github://item'); + await workspace.open('atom-github://some-item'); assert.isFalse(component.setState.called); - await workspace.hide('atom-github://item'); + await workspace.hide('atom-github://some-item'); assert.isFalse(component.setState.called); }); From 7b252994482961db3ea5954749c022a7b61604dc Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 2 Nov 2018 17:27:41 -0700 Subject: [PATCH 0805/4053] Implement ActiveItemWatcher class --- lib/watch-workspace-item.js | 81 +++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js index 05da51cf13..4d225ee49e 100644 --- a/lib/watch-workspace-item.js +++ b/lib/watch-workspace-item.js @@ -81,8 +81,81 @@ class ItemWatcher { } } -export function watchWorkspaceItem(workspace, pattern, component, stateKey) { - return new ItemWatcher(workspace, pattern, component, stateKey) - .setInitialState() - .subscribeToWorkspace(); +class ActiveItemWatcher { + constructor(workspace, pattern, component, stateKey, opts) { + this.workspace = workspace; + this.pattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); + this.component = component; + this.stateKey = stateKey; + this.opts = opts; + + this.activeItem = this.isActiveItem(); + this.subs = new CompositeDisposable(); + } + + isActiveItem() { + for (const pane of this.workspace.getPanes()) { + if (this.itemMatches(pane.getActiveItem())) { + return true; + } + } + return false; + } + + setInitialState() { + if (!this.component.state) { + this.component.state = {}; + } + this.component.state[this.stateKey] = this.activeItem; + return this; + } + + subscribeToWorkspace() { + this.subs.dispose(); + this.subs = new CompositeDisposable( + this.workspace.onDidChangeActivePaneItem(this.updateActiveState), + ); + return this; + } + + updateActiveState() { + const wasActive = this.activeItem; + + // Update the component's state if it's changed as a result + if (wasActive && !this.activeItem) { + return new Promise(resolve => this.component.setState({[this.stateKey]: false}, resolve)); + } else if (!wasActive && this.activeItem) { + return new Promise(resolve => this.component.setState({[this.stateKey]: true}, resolve)); + } else { + return Promise.resolve(); + } + } + + setPattern(pattern) { + this.pattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); + + this.updateActiveState(); + } + + itemMatches = item => item && item.getURI && this.pattern.matches(item.getURI()).ok() + + dispose() { + this.subs.dispose(); + } +} + +export function watchWorkspaceItem(workspace, pattern, component, stateKey, options = {}) { + if (options.active) { + // I implemented this as a separate class because the logic differs enough + // and I suspect we can replace `ItemWatcher` with this. I don't see a clear use case for the `ItemWatcher` class + return new ActiveItemWatcher(workspace, pattern, component, stateKey, options) + .setInitialState() + .subscribeToWorkspace(); + } else { + // TODO: would we ever actually use this? If not, clean it up, along with tests + return new ItemWatcher(workspace, pattern, component, stateKey, options) + .setInitialState() + .subscribeToWorkspace(); + } + } From 692ea4cf590348f897cf541686f54ecc28d43c17 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 2 Nov 2018 19:08:17 -0700 Subject: [PATCH 0806/4053] Pass `{active: true}` to `watchWorkspaceItem` --- lib/controllers/commit-controller.js | 3 ++- lib/views/commit-view.js | 4 ++-- lib/watch-workspace-item.js | 6 ++++-- test/views/commit-view.test.js | 6 +++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index 601c084fa8..e097b2b41b 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -61,6 +61,7 @@ export default class CommitController extends React.Component { CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath()), this, 'commitPreviewActive', + {active: true}, ); this.subscriptions.add(this.previewWatcher); } @@ -122,7 +123,7 @@ export default class CommitController extends React.Component { selectedCoAuthors={this.props.selectedCoAuthors} updateSelectedCoAuthors={this.props.updateSelectedCoAuthors} toggleCommitPreview={this.toggleCommitPreview} - commitPreviewOpen={this.state.commitPreviewOpen} + commitPreviewActive={this.state.commitPreviewActive} /> ); } diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index 358934d6bc..af82d6da96 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -42,7 +42,7 @@ export default class CommitView extends React.Component { mergeConflictsExist: PropTypes.bool.isRequired, stagedChangesExist: PropTypes.bool.isRequired, isCommitting: PropTypes.bool.isRequired, - commitPreviewOpen: PropTypes.bool.isRequired, + commitPreviewActive: PropTypes.bool.isRequired, deactivateCommitBox: PropTypes.bool.isRequired, maximumCharacterLimit: PropTypes.number.isRequired, messageBuffer: PropTypes.object.isRequired, // FIXME more specific proptype @@ -167,7 +167,7 @@ export default class CommitView extends React.Component { className="github-CommitView-commitPreview github-CommitView-button btn native-key-bindings" disabled={!this.props.stagedChangesExist} onClick={this.props.toggleCommitPreview}> - {this.props.commitPreviewOpen ? 'Close Commit Preview' : 'Preview Commit'} + {this.props.commitPreviewActive ? 'Close Commit Preview' : 'Preview Commit'}
diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js index 4d225ee49e..319568e6d4 100644 --- a/lib/watch-workspace-item.js +++ b/lib/watch-workspace-item.js @@ -114,13 +114,15 @@ class ActiveItemWatcher { this.subs.dispose(); this.subs = new CompositeDisposable( this.workspace.onDidChangeActivePaneItem(this.updateActiveState), + this.workspace.onDidDestroyPaneItem(this.updateActiveState), ); return this; } - updateActiveState() { + updateActiveState = () => { const wasActive = this.activeItem; + this.activeItem = this.isActiveItem(); // Update the component's state if it's changed as a result if (wasActive && !this.activeItem) { return new Promise(resolve => this.component.setState({[this.stateKey]: false}, resolve)); @@ -134,7 +136,7 @@ class ActiveItemWatcher { setPattern(pattern) { this.pattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); - this.updateActiveState(); + return this.updateActiveState(); } itemMatches = item => item && item.getURI && this.pattern.matches(item.getURI()).ok() diff --git a/test/views/commit-view.test.js b/test/views/commit-view.test.js index fd182e4e50..14aef60080 100644 --- a/test/views/commit-view.test.js +++ b/test/views/commit-view.test.js @@ -43,7 +43,7 @@ describe('CommitView', function() { stagedChangesExist={false} mergeConflictsExist={false} isCommitting={false} - commitPreviewOpen={false} + commitPreviewActive={false} deactivateCommitBox={false} maximumCharacterLimit={72} messageBuffer={messageBuffer} @@ -658,10 +658,10 @@ describe('CommitView', function() { assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'Preview Commit'); - wrapper.setProps({commitPreviewOpen: true}); + wrapper.setProps({commitPreviewActive: true}); assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'Close Commit Preview'); - wrapper.setProps({commitPreviewOpen: false}); + wrapper.setProps({commitPreviewActive: false}); assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'Preview Commit'); }); }); From 2ab46e71ada787685df0219370fa0f13699163bc Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 2 Nov 2018 19:20:09 -0700 Subject: [PATCH 0807/4053] Watch for active pane item change in workspace center only If we watch for changes on the workspace then we don't get an event in the case that an already-open file is clicked in tree-view, because tree-view remains the active item --- lib/watch-workspace-item.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js index 319568e6d4..dc2fcd1077 100644 --- a/lib/watch-workspace-item.js +++ b/lib/watch-workspace-item.js @@ -113,8 +113,7 @@ class ActiveItemWatcher { subscribeToWorkspace() { this.subs.dispose(); this.subs = new CompositeDisposable( - this.workspace.onDidChangeActivePaneItem(this.updateActiveState), - this.workspace.onDidDestroyPaneItem(this.updateActiveState), + this.workspace.getCenter().onDidChangeActivePaneItem(this.updateActiveState), ); return this; } From baafe4a640bf714338b3ab8ffc5ff5851ca72ebf Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 2 Nov 2018 19:25:05 -0700 Subject: [PATCH 0808/4053] Make Commit Preview a pending item (honors config setting) --- lib/controllers/commit-controller.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index e097b2b41b..4ddab2d384 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -283,9 +283,12 @@ export default class CommitController extends React.Component { toggleCommitPreview() { addEvent('toggle-commit-preview', {package: 'github'}); - return this.props.workspace.toggle( - CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath()), - ); + const uri = CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath()); + if (this.props.workspace.hide(uri)) { + return Promise.resolve(); + } else { + return this.props.workspace.open(uri, {searchAllPanes: true, pending: true}); + } } } From 1f0e3b001e748637e2015c1013000662450c1830 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 2 Nov 2018 19:29:06 -0700 Subject: [PATCH 0809/4053] Use unique item name for test that places item in right dock --- test/watch-workspace-item.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/watch-workspace-item.test.js b/test/watch-workspace-item.test.js index 1a392132b7..9448f74beb 100644 --- a/test/watch-workspace-item.test.js +++ b/test/watch-workspace-item.test.js @@ -81,10 +81,10 @@ describe.only('watchWorkspaceItem', function() { it('is false when the pane is open, but not active', async function() { // TODO: fix this test suite so that 'atom-github://item' works - await workspace.open('atom-github://some-item'); + await workspace.open('atom-github://item'); await workspace.open('atom-github://nonmatching'); - sub = watchWorkspaceItem(workspace, 'atom-github://some-item', component, 'someKey', {active: true}); + sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey', {active: true}); assert.isFalse(component.state.someKey); }); @@ -97,13 +97,13 @@ describe.only('watchWorkspaceItem', function() { }); it('is true when the pane is open and active in any pane', async function() { - await workspace.open('atom-github://item', {location: 'right'}); + await workspace.open('atom-github://some-item', {location: 'right'}); await workspace.open('atom-github://nonmatching'); - assert.strictEqual(workspace.getRightDock().getActivePaneItem().getURI(), 'atom-github://item'); + assert.strictEqual(workspace.getRightDock().getActivePaneItem().getURI(), 'atom-github://some-item'); assert.strictEqual(workspace.getActivePaneItem().getURI(), 'atom-github://nonmatching'); - sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey', {active: true}); + sub = watchWorkspaceItem(workspace, 'atom-github://some-item', component, 'someKey', {active: true}); assert.isTrue(component.state.someKey); }); }); @@ -163,14 +163,14 @@ describe.only('watchWorkspaceItem', function() { it('stops updating when disposed', async function() { // TODO: fix this test suite so that 'atom-github://item' works - sub = watchWorkspaceItem(workspace, 'atom-github://some-item', component, 'theKey'); + sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'theKey'); assert.isFalse(component.state.theKey); sub.dispose(); - await workspace.open('atom-github://some-item'); + await workspace.open('atom-github://item'); assert.isFalse(component.setState.called); - await workspace.hide('atom-github://some-item'); + await workspace.hide('atom-github://item'); assert.isFalse(component.setState.called); }); From ecb7e32bbc56a8ad90eec3a5bf5c65c279f6c893 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 5 Nov 2018 08:18:37 +0000 Subject: [PATCH 0810/4053] fix(package): update tree-kill to version 1.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3504ad6e5f..66c3e6e764 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "relay-runtime": "1.6.0", "temp": "0.8.3", "tinycolor2": "1.4.1", - "tree-kill": "1.2.0", + "tree-kill": "1.2.1", "underscore-plus": "1.6.8", "what-the-diff": "0.4.0", "what-the-status": "1.0.3", From 946872dda2bc4e710254347256ef5328b41dc2df Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 5 Nov 2018 08:18:41 +0000 Subject: [PATCH 0811/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index c51a367149..052773129d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5060,7 +5060,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -7972,9 +7972,9 @@ } }, "tree-kill": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz", - "integrity": "sha1-WEZ4Yje0I5AU8F2xVrZDIS1MbzY=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", + "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==" }, "trim-newlines": { "version": "1.0.0", From 0c643d543520c68832f44ee56be70cc8903b43e6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 5 Nov 2018 10:20:50 +0000 Subject: [PATCH 0812/4053] chore(package): update node-fetch to version 2.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3504ad6e5f..762cfa030c 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "mocha-appveyor-reporter": "0.4.1", "mocha-multi-reporters": "^1.1.7", "mocha-stress": "1.0.0", - "node-fetch": "2.2.0", + "node-fetch": "2.2.1", "nyc": "13.0.0", "relay-compiler": "1.6.2", "semver": "5.6.0", From c051a126e7268168038400ade196928a67481144 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 5 Nov 2018 10:20:53 +0000 Subject: [PATCH 0813/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index c51a367149..c625e47af7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5060,7 +5060,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -5473,9 +5473,9 @@ } }, "node-fetch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz", - "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.1.tgz", + "integrity": "sha512-ObXBpNCD3A/vYQiQtEWl7DuqjAXjfptYFuGHLdPl5U19/6kJuZV+8uMHLrkj3wJrJoyfg4nhgyFixZdaZoAiEQ==", "dev": true }, "node-int64": { From 45b379cbfa464dd94df55c8c4475e9466985ef7a Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 5 Nov 2018 12:25:07 +0100 Subject: [PATCH 0814/4053] fix commit controller test --- test/controllers/commit-controller.test.js | 19 ++++++++++--------- test/watch-workspace-item.test.js | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 6142c3d2b5..8e4a868573 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -13,7 +13,7 @@ import CommitPreviewItem from '../../lib/items/commit-preview-item'; import {cloneRepository, buildRepository, buildRepositoryWithPipeline} from '../helpers'; import * as reporterProxy from '../../lib/reporter-proxy'; -describe('CommitController', function() { +describe.only('CommitController', function() { let atomEnvironment, workspace, commandRegistry, notificationManager, lastCommit, config, confirm, tooltips; let app; @@ -426,8 +426,8 @@ describe('CommitController', function() { }); }); - describe('toggleCommitPreview', function() { - it('opens and closes commit preview pane', async function() { + describe('tri-state toggle commit preview', function() { + it('opens, hides, and closes commit preview pane', async function() { const workdir = await cloneRepository('three-files'); const repository = await buildRepository(workdir); const previewURI = CommitPreviewItem.buildURI(workdir); @@ -436,17 +436,18 @@ describe('CommitController', function() { assert.isFalse(wrapper.find('CommitView').prop('commitPreviewActive')); + await workspace.open(path.join(workdir, 'a.txt')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); // Commit preview open as active pane item assert.strictEqual(workspace.getActivePaneItem().getURI(), previewURI); - assert.strictEqual(workspace.getActivePaneItem(), workspace.paneForItem(previewURI).getPendingItem()); + assert.strictEqual(workspace.getActivePaneItem(), workspace.paneForURI(previewURI).getPendingItem()); assert.isTrue(wrapper.find('CommitView').prop('commitPreviewActive')); - await workspace.open(__filename); + await workspace.open(path.join(workdir, 'a.txt')); // Commit preview open, but not active - assert.include(workspace.getAllPaneItems().map(i => i.getURI()), previewURI); + assert.include(workspace.getPaneItems().map(i => i.getURI()), previewURI); assert.notStrictEqual(workspace.getActivePaneItem().getURI(), previewURI); assert.isFalse(wrapper.find('CommitView').prop('commitPreviewActive')); @@ -454,14 +455,14 @@ describe('CommitController', function() { // Open as active pane item again assert.strictEqual(workspace.getActivePaneItem().getURI(), previewURI); - assert.strictEqual(workspace.getActivePaneItem(), workspace.paneForItem(previewURI).getPendingItem()); + assert.strictEqual(workspace.getActivePaneItem(), workspace.paneForURI(previewURI).getPendingItem()); assert.isTrue(wrapper.find('CommitView').prop('commitPreviewActive')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); // Commit preview closed - assert.notInclude(workspace.getAllPaneItems().map(i => i.getURI()), previewURI); - assert.isTrue(wrapper.find('CommitView').prop('commitPreviewActive')); + assert.notInclude(workspace.getPaneItems().map(i => i.getURI()), previewURI); + assert.isFalse(wrapper.find('CommitView').prop('commitPreviewActive')); }); it('records a metrics event when pane is toggled', async function() { diff --git a/test/watch-workspace-item.test.js b/test/watch-workspace-item.test.js index 9448f74beb..672ae15dee 100644 --- a/test/watch-workspace-item.test.js +++ b/test/watch-workspace-item.test.js @@ -1,7 +1,7 @@ import {watchWorkspaceItem} from '../lib/watch-workspace-item'; import URIPattern from '../lib/atom/uri-pattern'; -describe.only('watchWorkspaceItem', function() { +describe('watchWorkspaceItem', function() { let sub, atomEnv, workspace, component; beforeEach(function() { From f17da4b9a53bc493fb35dd54f81d907929367aaf Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 5 Nov 2018 12:34:49 +0100 Subject: [PATCH 0815/4053] =?UTF-8?q?got=20the=20tests=20to=20be=20green?= =?UTF-8?q?=20but=20do=20they=20REALLY=20work=20tho=20=F0=9F=A4=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/controllers/commit-controller.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 8e4a868573..5cf9dd6b29 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -501,12 +501,12 @@ describe.only('CommitController', function() { assert.isFalse(wrapper.find('CommitView').prop('commitPreviewActive')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); - assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); + assert.isFalse(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir1))); assert.isTrue(wrapper.find('CommitView').prop('commitPreviewActive')); await wrapper.find('CommitView').prop('toggleCommitPreview')(); - assert.isTrue(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); + assert.isFalse(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir0))); assert.isFalse(workspace.getPaneItems().some(item => item.getURI() === CommitPreviewItem.buildURI(workdir1))); assert.isFalse(wrapper.find('CommitView').prop('commitPreviewActive')); }); From 3d8fb2c1dd662307130fb02dd0ccb2060315ea43 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 5 Nov 2018 12:35:06 +0100 Subject: [PATCH 0816/4053] take out the `.only` --- test/controllers/commit-controller.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 5cf9dd6b29..bbb187bfa6 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -13,7 +13,7 @@ import CommitPreviewItem from '../../lib/items/commit-preview-item'; import {cloneRepository, buildRepository, buildRepositoryWithPipeline} from '../helpers'; import * as reporterProxy from '../../lib/reporter-proxy'; -describe.only('CommitController', function() { +describe('CommitController', function() { let atomEnvironment, workspace, commandRegistry, notificationManager, lastCommit, config, confirm, tooltips; let app; From dde3a608a487283feb83caa6487890190792ffa2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 5 Nov 2018 10:44:19 -0500 Subject: [PATCH 0817/4053] Test for a unified MultiFilePatch buffer --- test/models/patch/builder.test.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 987194ef82..2f295fdd4c 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -1,7 +1,7 @@ import {buildFilePatch, buildMultiFilePatch} from '../../../lib/models/patch'; import {assertInPatch, assertInFilePatch} from '../../helpers'; -describe('buildFilePatch', function() { +describe.only('buildFilePatch', function() { it('returns a null patch for an empty diff list', function() { const p = buildFilePatch([]); assert.isFalse(p.getOldFile().isPresent()); @@ -556,6 +556,25 @@ describe('buildFilePatch', function() { ]); assert.lengthOf(mp.getFilePatches(), 3); + + assert.strictEqual( + mp.getBuffer().getText(), + 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\nline-6\n' + + 'line-5\nline-6\nline-7\nline-8\n' + + 'line-0\nline-1\nline-2\n', + ); + + const assertAllSame = getter => { + assert.lengthOf( + Array.from(new Set(mp.getFilePatches.map(p => p[getter]()))), + 1, + `FilePatches have different results from ${getter}`, + ); + }; + for (const getter of ['getUnchangedLayer', 'getAdditionLayer', 'getDeletionLayer', 'getNoNewlineLayer']) { + assertAllSame(getter); + } + assert.strictEqual(mp.getFilePatches()[0].getOldPath(), 'first'); assertInFilePatch(mp.getFilePatches()[0]).hunks( { From 97f6a1a8ae2430b3f59f6abdd77553719a5d3513 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 5 Nov 2018 13:01:37 -0500 Subject: [PATCH 0818/4053] Parse multi-file patches into a single buffer --- lib/models/patch/builder.js | 44 +++++++++++++++++++--------- lib/models/patch/multi-file-patch.js | 7 ++++- test/models/patch/builder.test.js | 30 +++++++++---------- 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index c2bef5fe72..6f700e1c6c 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -20,8 +20,9 @@ export function buildFilePatch(diffs) { } export function buildMultiFilePatch(diffs) { + const layeredBuffer = initializeBuffer(); const byPath = new Map(); - const filePatches = []; + const actions = []; let index = 0; for (const diff of diffs) { @@ -34,7 +35,7 @@ export function buildMultiFilePatch(diffs) { if (otherHalf) { // The second half. Complete the paired diff, or fail if they have unexpected statuses or modes. const [otherDiff, otherIndex] = otherHalf; - filePatches[otherIndex] = dualDiffFilePatch(diff, otherDiff); + actions[otherIndex] = () => dualDiffFilePatch(diff, otherDiff, layeredBuffer); byPath.delete(thePath); } else { // The first half we've seen. @@ -42,27 +43,33 @@ export function buildMultiFilePatch(diffs) { index++; } } else { - filePatches[index] = singleDiffFilePatch(diff); + actions[index] = () => singleDiffFilePatch(diff, layeredBuffer); index++; } } // Populate unpaired diffs that looked like they could be part of a pair, but weren't. for (const [unpairedDiff, originalIndex] of byPath.values()) { - filePatches[originalIndex] = singleDiffFilePatch(unpairedDiff); + actions[originalIndex] = () => singleDiffFilePatch(unpairedDiff, layeredBuffer); } - return new MultiFilePatch(filePatches); + const filePatches = actions.map(action => action()); + + return new MultiFilePatch(layeredBuffer.buffer, filePatches); } function emptyDiffFilePatch() { return FilePatch.createNull(); } -function singleDiffFilePatch(diff) { +function singleDiffFilePatch(diff, layeredBuffer = null) { const wasSymlink = diff.oldMode === '120000'; const isSymlink = diff.newMode === '120000'; - const [hunks, buffer, layers] = buildHunks(diff); + + if (!layeredBuffer) { + layeredBuffer = initializeBuffer(); + } + const [hunks] = buildHunks(diff, layeredBuffer); let oldSymlink = null; let newSymlink = null; @@ -81,12 +88,16 @@ function singleDiffFilePatch(diff) { const newFile = diff.newPath !== null || diff.newMode !== null ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink}) : nullFile; - const patch = new Patch({status: diff.status, hunks, buffer, layers}); + const patch = new Patch({status: diff.status, hunks, ...layeredBuffer}); return new FilePatch(oldFile, newFile, patch); } -function dualDiffFilePatch(diff1, diff2) { +function dualDiffFilePatch(diff1, diff2, layeredBuffer = null) { + if (!layeredBuffer) { + layeredBuffer = initializeBuffer(); + } + let modeChangeDiff, contentChangeDiff; if (diff1.oldMode === '120000' || diff1.newMode === '120000') { modeChangeDiff = diff1; @@ -96,7 +107,7 @@ function dualDiffFilePatch(diff1, diff2) { contentChangeDiff = diff1; } - const [hunks, buffer, layers] = buildHunks(contentChangeDiff); + const [hunks] = buildHunks(contentChangeDiff, layeredBuffer); const filePath = contentChangeDiff.oldPath || contentChangeDiff.newPath; const symlink = modeChangeDiff.hunks[0].lines[0].slice(1); @@ -122,7 +133,7 @@ function dualDiffFilePatch(diff1, diff2) { const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink}); const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink}); - const patch = new Patch({status, hunks, buffer, layers}); + const patch = new Patch({status, hunks, ...layeredBuffer}); return new FilePatch(oldFile, newFile, patch); } @@ -134,12 +145,17 @@ const CHANGEKIND = { '\\': NoNewline, }; -function buildHunks(diff) { +function initializeBuffer() { const buffer = new TextBuffer(); const layers = ['hunk', 'unchanged', 'addition', 'deletion', 'noNewline'].reduce((obj, key) => { obj[key] = buffer.addMarkerLayer(); return obj; }, {}); + + return {buffer, layers}; +} + +function buildHunks(diff, {buffer, layers}) { const layersByKind = new Map([ [Unchanged, layers.unchanged], [Addition, layers.addition], @@ -148,7 +164,7 @@ function buildHunks(diff) { ]); const hunks = []; - let bufferRow = 0; + let bufferRow = buffer.getLastRow(); for (const hunkData of diff.hunks) { const bufferStartRow = bufferRow; @@ -210,5 +226,5 @@ function buildHunks(diff) { })); } - return [hunks, buffer, layers]; + return [hunks]; } diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index b47b90f545..5c3356ece8 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,8 +1,13 @@ export default class MultiFilePatch { - constructor(filePatches) { + constructor(buffer, filePatches) { + this.buffer = buffer; this.filePatches = filePatches; } + getBuffer() { + return this.buffer; + } + getFilePatches() { return this.filePatches; } diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 2f295fdd4c..3fa8b748c0 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -1,7 +1,7 @@ import {buildFilePatch, buildMultiFilePatch} from '../../../lib/models/patch'; import {assertInPatch, assertInFilePatch} from '../../helpers'; -describe.only('buildFilePatch', function() { +describe('buildFilePatch', function() { it('returns a null patch for an empty diff list', function() { const p = buildFilePatch([]); assert.isFalse(p.getOldFile().isPresent()); @@ -566,7 +566,7 @@ describe.only('buildFilePatch', function() { const assertAllSame = getter => { assert.lengthOf( - Array.from(new Set(mp.getFilePatches.map(p => p[getter]()))), + Array.from(new Set(mp.getFilePatches().map(p => p[getter]()))), 1, `FilePatches have different results from ${getter}`, ); @@ -595,19 +595,19 @@ describe.only('buildFilePatch', function() { assert.strictEqual(mp.getFilePatches()[1].getOldPath(), 'second'); assertInFilePatch(mp.getFilePatches()[1]).hunks( { - startRow: 0, endRow: 3, header: '@@ -5,3 +5,3 @@', regions: [ - {kind: 'unchanged', string: ' line-5\n', range: [[0, 0], [0, 6]]}, - {kind: 'addition', string: '+line-6\n', range: [[1, 0], [1, 6]]}, - {kind: 'deletion', string: '-line-7\n', range: [[2, 0], [2, 6]]}, - {kind: 'unchanged', string: ' line-8\n', range: [[3, 0], [3, 6]]}, + startRow: 7, endRow: 10, header: '@@ -5,3 +5,3 @@', regions: [ + {kind: 'unchanged', string: ' line-5\n', range: [[7, 0], [7, 6]]}, + {kind: 'addition', string: '+line-6\n', range: [[8, 0], [8, 6]]}, + {kind: 'deletion', string: '-line-7\n', range: [[9, 0], [9, 6]]}, + {kind: 'unchanged', string: ' line-8\n', range: [[10, 0], [10, 6]]}, ], }, ); assert.strictEqual(mp.getFilePatches()[2].getOldPath(), 'third'); assertInFilePatch(mp.getFilePatches()[2]).hunks( { - startRow: 0, endRow: 2, header: '@@ -1,0 +1,3 @@', regions: [ - {kind: 'addition', string: '+line-0\n+line-1\n+line-2\n', range: [[0, 0], [2, 6]]}, + startRow: 11, endRow: 13, header: '@@ -1,0 +1,3 @@', regions: [ + {kind: 'addition', string: '+line-0\n+line-1\n+line-2\n', range: [[11, 0], [13, 6]]}, ], }, ); @@ -691,8 +691,8 @@ describe.only('buildFilePatch', function() { assert.isTrue(fp1.hasTypechange()); assert.strictEqual(fp1.getNewSymlink(), 'was-non-symlink-destination'); assertInFilePatch(fp1).hunks({ - startRow: 0, endRow: 1, header: '@@ -1,2 +1,0 @@', regions: [ - {kind: 'deletion', string: '-line-0\n-line-1\n', range: [[0, 0], [1, 6]]}, + startRow: 3, endRow: 4, header: '@@ -1,2 +1,0 @@', regions: [ + {kind: 'deletion', string: '-line-0\n-line-1\n', range: [[3, 0], [4, 6]]}, ], }); @@ -700,15 +700,15 @@ describe.only('buildFilePatch', function() { assert.isTrue(fp2.hasTypechange()); assert.strictEqual(fp2.getOldSymlink(), 'was-symlink-destination'); assertInFilePatch(fp2).hunks({ - startRow: 0, endRow: 1, header: '@@ -1,0 +1,2 @@', regions: [ - {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 6]]}, + startRow: 5, endRow: 6, header: '@@ -1,0 +1,2 @@', regions: [ + {kind: 'addition', string: '+line-0\n+line-1\n', range: [[5, 0], [6, 6]]}, ], }); assert.strictEqual(fp3.getNewPath(), 'third'); assertInFilePatch(fp3).hunks({ - startRow: 0, endRow: 2, header: '@@ -1,3 +1,0 @@', regions: [ - {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n', range: [[0, 0], [2, 6]]}, + startRow: 7, endRow: 9, header: '@@ -1,3 +1,0 @@', regions: [ + {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n', range: [[7, 0], [9, 6]]}, ], }); }); From 513d32306b93c830ffb50da7b35d9fd5ddf26275 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 5 Nov 2018 14:20:35 -0500 Subject: [PATCH 0819/4053] Accept specific FilePatch instances in controller methods --- lib/controllers/file-patch-controller.js | 51 ++++++++++++------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index ebda46ca87..acf3d354b5 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -4,6 +4,7 @@ import path from 'path'; import {autobind, equalSets} from '../helpers'; import {addEvent} from '../reporter-proxy'; +import {MultiFilePatchPropType} from '../prop-types'; import ChangedFileItem from '../items/changed-file-item'; import FilePatchView from '../views/file-patch-view'; @@ -11,8 +12,7 @@ export default class FilePatchController extends React.Component { static propTypes = { repository: PropTypes.object.isRequired, stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), - relPath: PropTypes.string.isRequired, - filePatch: PropTypes.object.isRequired, + multiFilePatch: MultiFilePatchPropType.isRequired, hasUndoHistory: PropTypes.bool, workspace: PropTypes.object.isRequired, @@ -39,7 +39,7 @@ export default class FilePatchController extends React.Component { ); this.state = { - lastFilePatch: this.props.filePatch, + lastMultiFilePatch: this.props.multiFilePatch, selectionMode: 'hunk', selectedRows: new Set(), }; @@ -53,7 +53,7 @@ export default class FilePatchController extends React.Component { } componentDidUpdate(prevProps) { - if (prevProps.filePatch !== this.props.filePatch) { + if (prevProps.multiFilePatch !== this.props.multiFilePatch) { this.resolvePatchChangePromise(); this.patchChangePromise = new Promise(resolve => { this.resolvePatchChangePromise = resolve; @@ -85,31 +85,31 @@ export default class FilePatchController extends React.Component { ); } - undoLastDiscard({eventSource} = {}) { + undoLastDiscard(filePatch, {eventSource} = {}) { addEvent('undo-last-discard', { package: 'github', component: 'FilePatchController', eventSource, }); - return this.props.undoLastDiscard(this.props.relPath, this.props.repository); + return this.props.undoLastDiscard(filePatch.getPath(), this.props.repository); } - diveIntoMirrorPatch() { + diveIntoMirrorPatch(filePatch) { const mirrorStatus = this.withStagingStatus({staged: 'unstaged', unstaged: 'staged'}); const workingDirectory = this.props.repository.getWorkingDirectoryPath(); - const uri = ChangedFileItem.buildURI(this.props.relPath, workingDirectory, mirrorStatus); + const uri = ChangedFileItem.buildURI(filePatch.getPath(), workingDirectory, mirrorStatus); this.props.destroy(); return this.props.workspace.open(uri); } - surfaceFile() { - return this.props.surfaceFileAtPath(this.props.relPath, this.props.stagingStatus); + surfaceFile(filePatch) { + return this.props.surfaceFileAtPath(filePatch.getPath(), this.props.stagingStatus); } - async openFile(positions) { - const absolutePath = path.join(this.props.repository.getWorkingDirectoryPath(), this.props.relPath); + async openFile(filePatch, positions) { + const absolutePath = path.join(this.props.repository.getWorkingDirectoryPath(), filePatch.getPath()); const editor = await this.props.workspace.open(absolutePath, {pending: true}); if (positions.length > 0) { editor.setCursorBufferPosition(positions[0], {autoscroll: false}); @@ -121,14 +121,14 @@ export default class FilePatchController extends React.Component { return editor; } - toggleFile() { + toggleFile(filePatch) { return this.stagingOperation(() => { const methodName = this.withStagingStatus({staged: 'unstageFiles', unstaged: 'stageFiles'}); - return this.props.repository[methodName]([this.props.relPath]); + return this.props.repository[methodName]([filePatch.getPath()]); }); } - async toggleRows(rowSet, nextSelectionMode) { + async toggleRows(filePatch, rowSet, nextSelectionMode) { let chosenRows = rowSet; if (chosenRows) { await this.selectedRowsChanged(chosenRows, nextSelectionMode); @@ -142,26 +142,27 @@ export default class FilePatchController extends React.Component { return this.stagingOperation(() => { const patch = this.withStagingStatus({ - staged: () => this.props.filePatch.getUnstagePatchForLines(chosenRows), - unstaged: () => this.props.filePatch.getStagePatchForLines(chosenRows), + staged: () => filePatch.getUnstagePatchForLines(chosenRows), + unstaged: () => filePatch.getStagePatchForLines(chosenRows), }); return this.props.repository.applyPatchToIndex(patch); }); } - toggleModeChange() { + toggleModeChange(filePatch) { return this.stagingOperation(() => { const targetMode = this.withStagingStatus({ - unstaged: this.props.filePatch.getNewMode(), - staged: this.props.filePatch.getOldMode(), + unstaged: filePatch.getNewMode(), + staged: filePatch.getOldMode(), }); - return this.props.repository.stageFileModeChange(this.props.relPath, targetMode); + return this.props.repository.stageFileModeChange(filePatch.getPath(), targetMode); }); } - toggleSymlinkChange() { + toggleSymlinkChange(filePatch) { return this.stagingOperation(() => { - const {filePatch, relPath, repository} = this.props; + const relPath = filePatch.getPath(); + const repository = this.props.repository; return this.withStagingStatus({ unstaged: () => { if (filePatch.hasTypechange() && filePatch.getStatus() === 'added') { @@ -181,7 +182,7 @@ export default class FilePatchController extends React.Component { }); } - async discardRows(rowSet, nextSelectionMode, {eventSource} = {}) { + async discardRows(filePatch, rowSet, nextSelectionMode, {eventSource} = {}) { let chosenRows = rowSet; if (chosenRows) { await this.selectedRowsChanged(chosenRows, nextSelectionMode); @@ -196,7 +197,7 @@ export default class FilePatchController extends React.Component { eventSource, }); - return this.props.discardLines(this.props.filePatch, chosenRows, this.props.repository); + return this.props.discardLines(filePatch, chosenRows, this.props.repository); } selectedRowsChanged(rows, nextSelectionMode) { From 601bdcb186a401fd1373af0c8766e371029b5357 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 5 Nov 2018 15:00:39 -0500 Subject: [PATCH 0820/4053] Render multiple FilePatches in one FilePatchView, take 1 --- lib/views/file-patch-view.js | 224 ++++++++++++++++++++++------------- 1 file changed, 140 insertions(+), 84 deletions(-) diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 6cbba58939..c67a96e394 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -5,7 +5,7 @@ import {Range} from 'atom'; import {CompositeDisposable} from 'event-kit'; import {autobind} from '../helpers'; -import {RefHolderPropType} from '../prop-types'; +import {RefHolderPropType, MultiFilePatchPropType} from '../prop-types'; import AtomTextEditor from '../atom/atom-text-editor'; import Marker from '../atom/marker'; import MarkerLayer from '../atom/marker-layer'; @@ -28,10 +28,9 @@ const BLANK_LABEL = () => NBSP_CHARACTER; export default class FilePatchView extends React.Component { static propTypes = { - relPath: PropTypes.string.isRequired, stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, isPartiallyStaged: PropTypes.bool, - filePatch: PropTypes.object.isRequired, + multiFilePatch: MultiFilePatchPropType.isRequired, selectionMode: PropTypes.oneOf(['hunk', 'line']).isRequired, selectedRows: PropTypes.object.isRequired, repository: PropTypes.object.isRequired, @@ -56,7 +55,6 @@ export default class FilePatchView extends React.Component { toggleSymlinkChange: PropTypes.func.isRequired, undoLastDiscard: PropTypes.func.isRequired, discardRows: PropTypes.func.isRequired, - handleMouseDown: PropTypes.func, refInitialFocus: RefHolderPropType, } @@ -98,11 +96,18 @@ export default class FilePatchView extends React.Component { componentDidMount() { window.addEventListener('mouseup', this.didMouseUp); this.refEditor.map(editor => { - const [firstHunk] = this.props.filePatch.getHunks(); - if (firstHunk) { - this.nextSelectionMode = 'hunk'; - editor.setSelectedBufferRange(firstHunk.getRange()); + const [firstPatch] = this.props.multiFilePatch.getFilePatches(); + if (!firstPatch) { + return null; } + + const [firstHunk] = firstPatch.getHunks(); + if (!firstHunk) { + return null; + } + + this.nextSelectionMode = 'hunk'; + editor.setSelectedBufferRange(firstHunk.getRange()); return null; }); @@ -113,16 +118,16 @@ export default class FilePatchView extends React.Component { getSnapshotBeforeUpdate(prevProps) { let newSelectionRange = null; - if (this.props.filePatch !== prevProps.filePatch) { + if (this.props.multiFilePatch !== prevProps.multiFilePatch) { // Heuristically adjust the editor selection based on the old file patch, the old row selection state, and // the incoming patch. - newSelectionRange = this.props.filePatch.getNextSelectionRange( - prevProps.filePatch, + newSelectionRange = this.props.multiFilePatch.getNextSelectionRange( + prevProps.multiFilePatch, prevProps.selectedRows, ); this.suppressChanges = true; - this.props.filePatch.adoptBufferFrom(prevProps.filePatch); + this.props.multiFilePatch.adoptBufferFrom(prevProps.multiFilePatch); this.suppressChanges = false; } return newSelectionRange; @@ -142,7 +147,7 @@ export default class FilePatchView extends React.Component { } else { const nextHunks = new Set( Range.fromObject(newSelectionRange).getRows() - .map(row => this.props.filePatch.getHunkAt(row)) + .map(row => this.props.multiFilePatch.getHunkAt(row)) .filter(Boolean), ); const nextRanges = nextHunks.size > 0 @@ -165,63 +170,43 @@ export default class FilePatchView extends React.Component { this.subs.dispose(); } - handleMouseDown() { - this.props.handleMouseDown(this.props.relPath); - } - render() { const rootClass = cx( 'github-FilePatchView', `github-FilePatchView--${this.props.stagingStatus}`, - {'github-FilePatchView--blank': !this.props.filePatch.isPresent()}, + {'github-FilePatchView--blank': !this.props.multiFilePatch.anyPresent()}, {'github-FilePatchView--hunkMode': this.props.selectionMode === 'hunk'}, {'github-FilePatchView--active': this.props.isActive}, {'github-FilePatchView--inactive': !this.props.isActive}, ); return ( -
- +
{this.renderCommands()} - 0} - hasUndoHistory={this.props.hasUndoHistory} - - tooltips={this.props.tooltips} - - undoLastDiscard={this.undoLastDiscardFromButton} - diveIntoMirrorPatch={this.props.diveIntoMirrorPatch} - openFile={this.didOpenFile} - toggleFile={this.props.toggleFile} - /> -
- {this.props.filePatch.isPresent() ? this.renderNonEmptyPatch() : this.renderEmptyPatch()} + {this.props.multiFilePatch.anyPresent() ? this.renderNonEmptyPatch() : this.renderEmptyPatch()}
-
); } renderCommands() { let stageModeCommand = null; - if (this.props.filePatch.didChangeExecutableMode()) { + let stageSymlinkCommand = null; + + if (this.props.multiFilePatch.didAnyChangeExecutableMode()) { const command = this.props.stagingStatus === 'unstaged' ? 'github:stage-file-mode-change' : 'github:unstage-file-mode-change'; - stageModeCommand = ; + stageModeCommand = ; } - let stageSymlinkCommand = null; - if (this.props.filePatch.hasSymlink()) { + if (this.props.multiFilePatch.anyHaveSymlink()) { const command = this.props.stagingStatus === 'unstaged' ? 'github:stage-symlink-change' : 'github:unstage-symlink-change'; - stageSymlinkCommand = ; + stageSymlinkCommand = ; } return ( @@ -249,7 +234,7 @@ export default class FilePatchView extends React.Component { )} - - - - {this.renderExecutableModeChangeMeta()} - {this.renderSymlinkChangeMeta()} - - - - - {this.renderHunkHeaders()} + {this.props.multiFilePatch.getFilePatches().map(this.renderFilePatchDecorations)} {this.renderLineDecorations( Array.from(this.props.selectedRows, row => Range.fromObject([[row, 0], [row, Infinity]])), @@ -309,17 +285,17 @@ export default class FilePatchView extends React.Component { )} {this.renderDecorationsOnLayer( - this.props.filePatch.getAdditionLayer(), + this.props.multiFilePatch.getAdditionLayer(), 'github-FilePatchView-line--added', {icon: true, line: true}, )} {this.renderDecorationsOnLayer( - this.props.filePatch.getDeletionLayer(), + this.props.multiFilePatch.getDeletionLayer(), 'github-FilePatchView-line--deleted', {icon: true, line: true}, )} {this.renderDecorationsOnLayer( - this.props.filePatch.getNoNewlineLayer(), + this.props.multiFilePatch.getNoNewlineLayer(), 'github-FilePatchView-line--nonewline', {icon: true, line: true}, )} @@ -328,13 +304,45 @@ export default class FilePatchView extends React.Component { ); } - renderExecutableModeChangeMeta() { - if (!this.props.filePatch.didChangeExecutableMode()) { + renderFilePatchDecorations = filePatch => { + return ( + + + + + {this.renderExecutableModeChangeMeta(filePatch)} + {this.renderSymlinkChangeMeta(filePatch)} + + 0} + hasUndoHistory={this.props.hasUndoHistory} + + tooltips={this.props.tooltips} + + undoLastDiscard={this.undoLastDiscardFromButton} + diveIntoMirrorPatch={this.props.diveIntoMirrorPatch} + openFile={this.didOpenFile} + toggleFile={this.props.toggleFile} + /> + + + + + {this.renderHunkHeaders(filePatch)} + + ); + } + + renderExecutableModeChangeMeta(filePatch) { + if (!filePatch.didChangeExecutableMode()) { return null; } - const oldMode = this.props.filePatch.getOldMode(); - const newMode = this.props.filePatch.getNewMode(); + const oldMode = filePatch.getOldMode(); + const newMode = filePatch.getNewMode(); const attrs = this.props.stagingStatus === 'unstaged' ? { @@ -351,7 +359,7 @@ export default class FilePatchView extends React.Component { title="Mode change" actionIcon={attrs.actionIcon} actionText={attrs.actionText} - action={this.props.toggleModeChange}> + action={() => this.props.toggleModeChange(filePatch)}> File changed mode @@ -365,15 +373,15 @@ export default class FilePatchView extends React.Component { ); } - renderSymlinkChangeMeta() { - if (!this.props.filePatch.hasSymlink()) { + renderSymlinkChangeMeta(filePatch) { + if (!filePatch.hasSymlink()) { return null; } let detail =
; let title = ''; - const oldSymlink = this.props.filePatch.getOldSymlink(); - const newSymlink = this.props.filePatch.getNewSymlink(); + const oldSymlink = filePatch.getOldSymlink(); + const newSymlink = filePatch.getNewSymlink(); if (oldSymlink && newSymlink) { detail = ( @@ -434,7 +442,7 @@ export default class FilePatchView extends React.Component { title={title} actionIcon={attrs.actionIcon} actionText={attrs.actionText} - action={this.props.toggleSymlinkChange}> + action={() => this.props.toggleSymlinkChange(filePatch)}> {detail} @@ -442,16 +450,16 @@ export default class FilePatchView extends React.Component { ); } - renderHunkHeaders() { + renderHunkHeaders(filePatch) { const toggleVerb = this.props.stagingStatus === 'unstaged' ? 'Stage' : 'Unstage'; const selectedHunks = new Set( - Array.from(this.props.selectedRows, row => this.props.filePatch.getHunkAt(row)), + Array.from(this.props.selectedRows, row => filePatch.getHunkAt(row)), ); return ( - {this.props.filePatch.getHunks().map((hunk, index) => { + {filePatch.getHunks().map((hunk, index) => { const containsSelection = this.props.selectionMode === 'line' && selectedHunks.has(hunk); const isSelected = this.props.isActive && (this.props.selectionMode === 'hunk') && selectedHunks.has(hunk); @@ -595,9 +603,14 @@ export default class FilePatchView extends React.Component { ); } - toggleHunkSelection(hunk, containsSelection) { + toggleHunkSelection(filePatch, hunk, containsSelection) { if (containsSelection) { - return this.props.toggleRows(this.props.selectedRows, this.props.selectionMode, {eventSource: 'button'}); + return this.props.toggleRows( + filePatch, + this.props.selectedRows, + this.props.selectionMode, + {eventSource: 'button'}, + ); } else { const changeRows = new Set( hunk.getChanges() @@ -606,13 +619,18 @@ export default class FilePatchView extends React.Component { return rows; }, []), ); - return this.props.toggleRows(changeRows, 'hunk', {eventSource: 'button'}); + return this.props.toggleRows(filePatch, changeRows, 'hunk', {eventSource: 'button'}); } } - discardHunkSelection(hunk, containsSelection) { + discardHunkSelection(filePatch, hunk, containsSelection) { if (containsSelection) { - return this.props.discardRows(this.props.selectedRows, this.props.selectionMode, {eventSource: 'button'}); + return this.props.discardRows( + filePatch, + this.props.selectedRows, + this.props.selectionMode, + {eventSource: 'button'}, + ); } else { const changeRows = new Set( hunk.getChanges() @@ -621,7 +639,7 @@ export default class FilePatchView extends React.Component { return rows; }, []), ); - return this.props.discardRows(changeRows, 'hunk', {eventSource: 'button'}); + return this.props.discardRows(filePatch, changeRows, 'hunk', {eventSource: 'button'}); } } @@ -766,7 +784,12 @@ export default class FilePatchView extends React.Component { } didConfirm() { - return this.props.toggleRows(this.props.selectedRows, this.props.selectionMode); + return Promise.all( + Array.from( + this.getSelectedFilePatches(), + filePatch => this.props.toggleRows(filePatch, this.props.selectedRows, this.props.selectionMode), + ), + ); } didToggleSelectionMode() { @@ -795,6 +818,22 @@ export default class FilePatchView extends React.Component { }); } + didToggleModeChange = () => { + return Promise.all( + Array.from(this.getSelectedFilePatches()) + .filter(fp => fp.didChangeExecutableMode()) + .map(this.props.toggleModeChange), + ); + } + + didToggleSymlinkChange = () => { + return Promise.all( + Array.from(this.getSelectedFilePatches()) + .filter(fp => fp.hasSymlink()) + .map(this.props.toggleSymlinkChange), + ); + } + selectNextHunk() { this.refEditor.map(editor => { const nextHunks = new Set( @@ -827,7 +866,7 @@ export default class FilePatchView extends React.Component { for (const cursor of editor.getCursors()) { const cursorRow = cursor.getBufferPosition().row; - const hunk = this.props.filePatch.getHunkAt(cursorRow); + const hunk = this.props.multiFilePatch.getHunkAt(cursorRow); /* istanbul ignore next */ if (!hunk) { continue; @@ -913,7 +952,7 @@ export default class FilePatchView extends React.Component { } oldLineNumberLabel({bufferRow, softWrapped}) { - const hunk = this.props.filePatch.getHunkAt(bufferRow); + const hunk = this.props.multiFilePatch.getHunkAt(bufferRow); if (hunk === undefined) { return this.pad(''); } @@ -927,7 +966,7 @@ export default class FilePatchView extends React.Component { } newLineNumberLabel({bufferRow, softWrapped}) { - const hunk = this.props.filePatch.getHunkAt(bufferRow); + const hunk = this.props.multiFilePatch.getHunkAt(bufferRow); if (hunk === undefined) { return this.pad(''); } @@ -952,7 +991,7 @@ export default class FilePatchView extends React.Component { const seen = new Set(); return editor.getSelectedBufferRanges().reduce((acc, range) => { for (const row of range.getRows()) { - const hunk = this.props.filePatch.getHunkAt(row); + const hunk = this.props.multiFilePatch.getHunkAt(row); if (!hunk || seen.has(hunk)) { continue; } @@ -965,18 +1004,35 @@ export default class FilePatchView extends React.Component { }).getOr([]); } + /* + * Return a Set of FilePatches that include at least one editor selection. The selection need not contain an actual + * change row. + */ + getSelectedFilePatches() { + return this.refEditor.map(editor => { + const patches = new Set(); + for (const range of editor.getSelectedBufferRanges()) { + for (const row of range.getRows()) { + const patch = this.props.multiFilePatch.getFilePatchAt(row); + patches.add(patch); + } + } + return patches; + }).getOr(new Set()); + } + getHunkBefore(hunk) { const prevRow = hunk.getRange().start.row - 1; - return this.props.filePatch.getHunkAt(prevRow); + return this.props.multiFilePatch.getHunkAt(prevRow); } getHunkAfter(hunk) { const nextRow = hunk.getRange().end.row + 1; - return this.props.filePatch.getHunkAt(nextRow); + return this.props.multiFilePatch.getHunkAt(nextRow); } isChangeRow(bufferRow) { - const changeLayers = [this.props.filePatch.getAdditionLayer(), this.props.filePatch.getDeletionLayer()]; + const changeLayers = [this.props.multiFilePatch.getAdditionLayer(), this.props.multiFilePatch.getDeletionLayer()]; return changeLayers.some(layer => layer.findMarkers({intersectsRow: bufferRow}).length > 0); } @@ -990,7 +1046,7 @@ export default class FilePatchView extends React.Component { } pad(num) { - const maxDigits = this.props.filePatch.getMaxLineNumberWidth(); + const maxDigits = this.props.multiFilePatch.getMaxLineNumberWidth(); if (num === null) { return NBSP_CHARACTER.repeat(maxDigits); } else { From 04591fc8dbfc3afa66edf68b6dbb1551725c3f25 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 5 Nov 2018 15:28:10 -0500 Subject: [PATCH 0821/4053] Mark the contents of each FilePatch within a shared TextBuffer --- lib/models/patch/builder.js | 22 ++++++++++++++-------- lib/models/patch/file-patch.js | 4 ++++ lib/models/patch/patch.js | 7 ++++++- test/models/patch/builder.test.js | 3 +++ 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 6f700e1c6c..3226963b7d 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -69,7 +69,7 @@ function singleDiffFilePatch(diff, layeredBuffer = null) { if (!layeredBuffer) { layeredBuffer = initializeBuffer(); } - const [hunks] = buildHunks(diff, layeredBuffer); + const [hunks, patchMarker] = buildHunks(diff, layeredBuffer); let oldSymlink = null; let newSymlink = null; @@ -88,7 +88,7 @@ function singleDiffFilePatch(diff, layeredBuffer = null) { const newFile = diff.newPath !== null || diff.newMode !== null ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink}) : nullFile; - const patch = new Patch({status: diff.status, hunks, ...layeredBuffer}); + const patch = new Patch({status: diff.status, hunks, marker: patchMarker, ...layeredBuffer}); return new FilePatch(oldFile, newFile, patch); } @@ -107,7 +107,7 @@ function dualDiffFilePatch(diff1, diff2, layeredBuffer = null) { contentChangeDiff = diff1; } - const [hunks] = buildHunks(contentChangeDiff, layeredBuffer); + const [hunks, patchMarker] = buildHunks(contentChangeDiff, layeredBuffer); const filePath = contentChangeDiff.oldPath || contentChangeDiff.newPath; const symlink = modeChangeDiff.hunks[0].lines[0].slice(1); @@ -133,7 +133,7 @@ function dualDiffFilePatch(diff1, diff2, layeredBuffer = null) { const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink}); const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink}); - const patch = new Patch({status, hunks, ...layeredBuffer}); + const patch = new Patch({status, hunks, marker: patchMarker, ...layeredBuffer}); return new FilePatch(oldFile, newFile, patch); } @@ -147,7 +147,7 @@ const CHANGEKIND = { function initializeBuffer() { const buffer = new TextBuffer(); - const layers = ['hunk', 'unchanged', 'addition', 'deletion', 'noNewline'].reduce((obj, key) => { + const layers = ['patch', 'hunk', 'unchanged', 'addition', 'deletion', 'noNewline'].reduce((obj, key) => { obj[key] = buffer.addMarkerLayer(); return obj; }, {}); @@ -164,7 +164,9 @@ function buildHunks(diff, {buffer, layers}) { ]); const hunks = []; - let bufferRow = buffer.getLastRow(); + const patchStartRow = buffer.getLastRow(); + let bufferRow = patchStartRow; + let nextLineLength = 0; for (const hunkData of diff.hunks) { const bufferStartRow = bufferRow; @@ -174,7 +176,6 @@ function buildHunks(diff, {buffer, layers}) { let LastChangeKind = null; let currentRangeStart = bufferRow; let lastLineLength = 0; - let nextLineLength = 0; const finishCurrentRange = () => { if (currentRangeStart === bufferRow) { @@ -226,5 +227,10 @@ function buildHunks(diff, {buffer, layers}) { })); } - return [hunks]; + const patchMarker = layers.patch.markRange( + [[patchStartRow, 0], [bufferRow - 1, nextLineLength]], + {invalidate: 'never', exclusive: false}, + ); + + return [hunks, patchMarker]; } diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 76c98bccbc..d657dedb72 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -36,6 +36,10 @@ export default class FilePatch { return this.patch; } + getMarker() { + return this.getPatch().getMarker(); + } + getOldPath() { return this.getOldFile().getPath(); } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 2a924f85f2..11b02e7533 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -8,10 +8,11 @@ export default class Patch { return new NullPatch(); } - constructor({status, hunks, buffer, layers}) { + constructor({status, hunks, buffer, layers, marker}) { this.status = status; this.hunks = hunks; this.buffer = buffer; + this.marker = marker; this.hunkLayer = layers.hunk; this.unchangedLayer = layers.unchanged; @@ -28,6 +29,10 @@ export default class Patch { return this.status; } + getMarker() { + return this.marker; + } + getHunks() { return this.hunks; } diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 3fa8b748c0..c4e2385e72 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -576,6 +576,7 @@ describe('buildFilePatch', function() { } assert.strictEqual(mp.getFilePatches()[0].getOldPath(), 'first'); + assert.deepEqual(mp.getFilePatches()[0].getMarker().getRange().serialize(), [[0, 0], [6, 6]]); assertInFilePatch(mp.getFilePatches()[0]).hunks( { startRow: 0, endRow: 3, header: '@@ -1,2 +1,4 @@', regions: [ @@ -593,6 +594,7 @@ describe('buildFilePatch', function() { }, ); assert.strictEqual(mp.getFilePatches()[1].getOldPath(), 'second'); + assert.deepEqual(mp.getFilePatches()[1].getMarker().getRange().serialize(), [[7, 0], [10, 6]]); assertInFilePatch(mp.getFilePatches()[1]).hunks( { startRow: 7, endRow: 10, header: '@@ -5,3 +5,3 @@', regions: [ @@ -604,6 +606,7 @@ describe('buildFilePatch', function() { }, ); assert.strictEqual(mp.getFilePatches()[2].getOldPath(), 'third'); + assert.deepEqual(mp.getFilePatches()[2].getMarker().getRange().serialize(), [[11, 0], [13, 6]]); assertInFilePatch(mp.getFilePatches()[2]).hunks( { startRow: 11, endRow: 13, header: '@@ -1,0 +1,3 @@', regions: [ From 91a49db58b0386c36103adfd8bfc41f7495c1926 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 5 Nov 2018 15:36:52 -0500 Subject: [PATCH 0822/4053] FilePatch::getStartRange() returns the Range for file controls --- lib/models/patch/file-patch.js | 4 ++++ lib/models/patch/patch.js | 7 ++++++- test/models/patch/file-patch.test.js | 28 +++++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index d657dedb72..16f9165469 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -40,6 +40,10 @@ export default class FilePatch { return this.getPatch().getMarker(); } + getStartRange() { + return this.getPatch().getStartRange(); + } + getOldPath() { return this.getOldFile().getPath(); } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 11b02e7533..585b1bf7e6 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -1,4 +1,4 @@ -import {TextBuffer} from 'atom'; +import {TextBuffer, Range} from 'atom'; import Hunk from './hunk'; import {Unchanged, Addition, Deletion, NoNewline} from './region'; @@ -33,6 +33,11 @@ export default class Patch { return this.marker; } + getStartRange() { + const startPoint = this.getMarker().getRange().start; + return Range.fromObject([startPoint, startPoint]); + } + getHunks() { return this.hunks; } diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 4df0625843..09414abb02 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -22,7 +22,8 @@ describe('FilePatch', function() { ], }), ]; - const patch = new Patch({status: 'modified', hunks, buffer, layers}); + const marker = markRange(layers.patch); + const patch = new Patch({status: 'modified', hunks, buffer, layers, marker}); const oldFile = new File({path: 'a.txt', mode: '120000', symlink: 'dest.txt'}); const newFile = new File({path: 'b.txt', mode: '100755'}); @@ -44,6 +45,7 @@ describe('FilePatch', function() { assert.strictEqual(filePatch.getByteSize(), 15); assert.strictEqual(filePatch.getBuffer().getText(), '0000\n0001\n0002\n'); + assert.strictEqual(filePatch.getMarker(), marker); assert.strictEqual(filePatch.getMaxLineNumberWidth(), 1); assert.strictEqual(filePatch.getHunkAt(1), hunks[0]); @@ -158,6 +160,29 @@ describe('FilePatch', function() { ]); }); + it('returns the starting range of the patch', function() { + const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n'}); + const layers = buildLayers(buffer); + const hunks = [ + new Hunk({ + oldStartRow: 2, oldRowCount: 1, newStartRow: 2, newRowCount: 3, + marker: markRange(layers.hunk, 1, 3), + regions: [ + new Unchanged(markRange(layers.unchanged, 1)), + new Addition(markRange(layers.addition, 2, 3)), + ], + }), + ]; + const marker = markRange(layers.patch, 1, 3); + const patch = new Patch({status: 'modified', hunks, buffer, layers, marker}); + const oldFile = new File({path: 'a.txt', mode: '100644'}); + const newFile = new File({path: 'a.txt', mode: '100644'}); + + const filePatch = new FilePatch(oldFile, newFile, patch); + + assert.deepEqual(filePatch.getStartRange().serialize(), [[1, 0], [1, 0]]); + }); + it('adopts a buffer and layers from a prior FilePatch', function() { const oldFile = new File({path: 'a.txt', mode: '100755'}); const newFile = new File({path: 'b.txt', mode: '100755'}); @@ -903,6 +928,7 @@ describe('FilePatch', function() { function buildLayers(buffer) { return { + patch: buffer.addMarkerLayer(), hunk: buffer.addMarkerLayer(), unchanged: buffer.addMarkerLayer(), addition: buffer.addMarkerLayer(), From 885b81a6e07d1b87b3f550a71bbd0da6f1c3f142 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 5 Nov 2018 15:50:36 -0500 Subject: [PATCH 0823/4053] Locate a FilePatch within a shared TextBuffer by marker lookup --- lib/models/patch/builder.js | 2 +- lib/models/patch/multi-file-patch.js | 12 ++- test/models/patch/multi-file-patch.test.js | 108 ++++++++++++--------- 3 files changed, 76 insertions(+), 46 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 3226963b7d..f11ffb4a51 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -55,7 +55,7 @@ export function buildMultiFilePatch(diffs) { const filePatches = actions.map(action => action()); - return new MultiFilePatch(layeredBuffer.buffer, filePatches); + return new MultiFilePatch(layeredBuffer.buffer, layeredBuffer.layers.patch, filePatches); } function emptyDiffFilePatch() { diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 5c3356ece8..620448b311 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,7 +1,12 @@ export default class MultiFilePatch { - constructor(buffer, filePatches) { + constructor(buffer, patchLayer, filePatches) { this.buffer = buffer; + this.patchLayer = patchLayer; this.filePatches = filePatches; + + this.filePatchesByMarker = new Map( + this.filePatches.map(filePatch => [filePatch.getMarker(), filePatch]), + ); } getBuffer() { @@ -11,4 +16,9 @@ export default class MultiFilePatch { getFilePatches() { return this.filePatches; } + + getFilePatchAt(bufferRow) { + const [marker] = this.patchLayer.findMarkers({intersectsRow: bufferRow}); + return this.filePatchesByMarker.get(marker); + } } diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 3c0a38f525..f866020cc3 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -8,58 +8,78 @@ import Hunk from '../../../lib/models/patch/hunk'; import {Unchanged, Addition, Deletion} from '../../../lib/models/patch/region'; describe('MultiFilePatch', function() { + let buffer, layers; + + beforeEach(function() { + buffer = new TextBuffer(); + layers = { + patch: buffer.addMarkerLayer(), + hunk: buffer.addMarkerLayer(), + unchanged: buffer.addMarkerLayer(), + addition: buffer.addMarkerLayer(), + deletion: buffer.addMarkerLayer(), + noNewline: buffer.addMarkerLayer(), + }; + }); + it('has an accessor for its file patches', function() { const filePatches = [buildFilePatchFixture(0), buildFilePatchFixture(1)]; - const mp = new MultiFilePatch(filePatches); + const mp = new MultiFilePatch(buffer, layers.patch, filePatches); assert.strictEqual(mp.getFilePatches(), filePatches); }); -}); -function buildFilePatchFixture(index) { - const buffer = new TextBuffer(); - for (let i = 0; i < 8; i++) { - buffer.append(`file-${index} line-${i}\n`); - } + it('locates an individual FilePatch by marker lookup', function() { + const filePatches = []; + for (let i = 0; i < 10; i++) { + filePatches.push(buildFilePatchFixture(i)); + } + const mp = new MultiFilePatch(buffer, layers.patch, filePatches); - const layers = { - hunk: buffer.addMarkerLayer(), - unchanged: buffer.addMarkerLayer(), - addition: buffer.addMarkerLayer(), - deletion: buffer.addMarkerLayer(), - noNewline: buffer.addMarkerLayer(), - }; + assert.strictEqual(mp.getFilePatchAt(0), filePatches[0]); + assert.strictEqual(mp.getFilePatchAt(7), filePatches[0]); + assert.strictEqual(mp.getFilePatchAt(8), filePatches[1]); + assert.strictEqual(mp.getFilePatchAt(79), filePatches[9]); + }); + + function buildFilePatchFixture(index) { + const rowOffset = buffer.getLastRow(); + for (let i = 0; i < 8; i++) { + buffer.append(`file-${index} line-${i}\n`); + } - const mark = (layer, start, end = start) => layer.markRange([[start, 0], [end, Infinity]]); + const mark = (layer, start, end = start) => layer.markRange([[rowOffset + start, 0], [rowOffset + end, Infinity]]); - const hunks = [ - new Hunk({ - oldStartRow: 0, newStartRow: 0, oldRowCount: 3, newRowCount: 3, - sectionHeading: `file-${index} hunk-0`, - marker: mark(layers.hunk, 0, 3), - regions: [ - new Unchanged(mark(layers.unchanged, 0)), - new Addition(mark(layers.addition, 1)), - new Deletion(mark(layers.deletion, 2)), - new Unchanged(mark(layers.unchanged, 3)), - ], - }), - new Hunk({ - oldStartRow: 10, newStartRow: 10, oldRowCount: 3, newRowCount: 3, - sectionHeading: `file-${index} hunk-1`, - marker: mark(layers.hunk, 4, 7), - regions: [ - new Unchanged(mark(layers.unchanged, 4)), - new Addition(mark(layers.addition, 5)), - new Deletion(mark(layers.deletion, 6)), - new Unchanged(mark(layers.unchanged, 7)), - ], - }), - ]; + const hunks = [ + new Hunk({ + oldStartRow: 0, newStartRow: 0, oldRowCount: 3, newRowCount: 3, + sectionHeading: `file-${index} hunk-0`, + marker: mark(layers.hunk, 0, 3), + regions: [ + new Unchanged(mark(layers.unchanged, 0)), + new Addition(mark(layers.addition, 1)), + new Deletion(mark(layers.deletion, 2)), + new Unchanged(mark(layers.unchanged, 3)), + ], + }), + new Hunk({ + oldStartRow: 10, newStartRow: 10, oldRowCount: 3, newRowCount: 3, + sectionHeading: `file-${index} hunk-1`, + marker: mark(layers.hunk, 4, 7), + regions: [ + new Unchanged(mark(layers.unchanged, 4)), + new Addition(mark(layers.addition, 5)), + new Deletion(mark(layers.deletion, 6)), + new Unchanged(mark(layers.unchanged, 7)), + ], + }), + ]; - const patch = new Patch({status: 'modified', hunks, buffer, layers}); + const marker = mark(layers.patch, 0, 7); + const patch = new Patch({status: 'modified', hunks, buffer, layers, marker}); - const oldFile = new File({path: `file-${index}.txt`, mode: '100644'}); - const newFile = new File({path: `file-${index}.txt`, mode: '100644'}); + const oldFile = new File({path: `file-${index}.txt`, mode: '100644'}); + const newFile = new File({path: `file-${index}.txt`, mode: '100644'}); - return new FilePatch(oldFile, newFile, patch); -} + return new FilePatch(oldFile, newFile, patch); + } +}); From d671a4fc23c47a74ef62c6e8e135543c424aef79 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 5 Nov 2018 16:07:36 -0500 Subject: [PATCH 0824/4053] Lift hunk layer tracking and indexing up to MultiFilePatch --- lib/models/patch/multi-file-patch.js | 20 +++++++++--- lib/models/patch/patch.js | 11 +------ lib/views/file-patch-view.js | 2 +- test/models/patch/file-patch.test.js | 2 -- test/models/patch/multi-file-patch.test.js | 22 +++++++++++-- test/models/patch/patch.test.js | 38 ---------------------- 6 files changed, 38 insertions(+), 57 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 620448b311..00fb595011 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,12 +1,19 @@ export default class MultiFilePatch { - constructor(buffer, patchLayer, filePatches) { + constructor(buffer, patchLayer, hunkLayer, filePatches) { this.buffer = buffer; this.patchLayer = patchLayer; + this.hunkLayer = hunkLayer; this.filePatches = filePatches; - this.filePatchesByMarker = new Map( - this.filePatches.map(filePatch => [filePatch.getMarker(), filePatch]), - ); + this.filePatchesByMarker = new Map(); + this.hunksByMarker = new Map(); + + for (const filePatch of this.filePatches) { + this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); + for (const hunk of filePatch.getHunks()) { + this.hunksByMarker.set(hunk.getMarker(), hunk); + } + } } getBuffer() { @@ -21,4 +28,9 @@ export default class MultiFilePatch { const [marker] = this.patchLayer.findMarkers({intersectsRow: bufferRow}); return this.filePatchesByMarker.get(marker); } + + getHunkAt(bufferRow) { + const [marker] = this.hunkLayer.findMarkers({intersectsRow: bufferRow}); + return this.hunksByMarker.get(marker); + } } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 585b1bf7e6..fdfa3de092 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -21,7 +21,6 @@ export default class Patch { this.noNewlineLayer = layers.noNewline; this.buffer.retain(); - this.hunksByMarker = new Map(this.getHunks().map(hunk => [hunk.getMarker(), hunk])); this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); } @@ -79,11 +78,6 @@ export default class Patch { return lastHunk ? lastHunk.getMaxLineNumberWidth() : 0; } - getHunkAt(bufferRow) { - const [marker] = this.hunkLayer.findMarkers({intersectsRow: bufferRow}); - return this.hunksByMarker.get(marker); - } - clone(opts = {}) { return new this.constructor({ status: opts.status !== undefined ? opts.status : this.getStatus(), @@ -336,6 +330,7 @@ export default class Patch { return [[newSelectionRow, 0], [newSelectionRow, Infinity]]; } + // TODO lift up to MultiFilePatch adoptBufferFrom(lastPatch) { lastPatch.getHunkLayer().clear(); lastPatch.getUnchangedLayer().clear(); @@ -508,10 +503,6 @@ class NullPatch { return 0; } - getHunkAt(bufferRow) { - return undefined; - } - toString() { return ''; } diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index c67a96e394..6c992721ea 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -453,7 +453,7 @@ export default class FilePatchView extends React.Component { renderHunkHeaders(filePatch) { const toggleVerb = this.props.stagingStatus === 'unstaged' ? 'Stage' : 'Unstage'; const selectedHunks = new Set( - Array.from(this.props.selectedRows, row => filePatch.getHunkAt(row)), + Array.from(this.props.selectedRows, row => this.multiFilePatch.getHunkAt(row)), ); return ( diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 09414abb02..0726890900 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -48,8 +48,6 @@ describe('FilePatch', function() { assert.strictEqual(filePatch.getMarker(), marker); assert.strictEqual(filePatch.getMaxLineNumberWidth(), 1); - assert.strictEqual(filePatch.getHunkAt(1), hunks[0]); - const nBuffer = new TextBuffer({text: '0001\n0002\n'}); const nLayers = buildLayers(nBuffer); const nHunks = [ diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index f866020cc3..8719758fcf 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -24,7 +24,7 @@ describe('MultiFilePatch', function() { it('has an accessor for its file patches', function() { const filePatches = [buildFilePatchFixture(0), buildFilePatchFixture(1)]; - const mp = new MultiFilePatch(buffer, layers.patch, filePatches); + const mp = new MultiFilePatch(buffer, layers.patch, layers.hunk, filePatches); assert.strictEqual(mp.getFilePatches(), filePatches); }); @@ -33,7 +33,7 @@ describe('MultiFilePatch', function() { for (let i = 0; i < 10; i++) { filePatches.push(buildFilePatchFixture(i)); } - const mp = new MultiFilePatch(buffer, layers.patch, filePatches); + const mp = new MultiFilePatch(buffer, layers.patch, layers.hunk, filePatches); assert.strictEqual(mp.getFilePatchAt(0), filePatches[0]); assert.strictEqual(mp.getFilePatchAt(7), filePatches[0]); @@ -41,6 +41,24 @@ describe('MultiFilePatch', function() { assert.strictEqual(mp.getFilePatchAt(79), filePatches[9]); }); + it('locates a Hunk by marker lookup', function() { + const filePatches = [ + buildFilePatchFixture(0), + buildFilePatchFixture(1), + buildFilePatchFixture(2), + ]; + const mp = new MultiFilePatch(buffer, layers.patch, layers.hunk, filePatches); + + assert.strictEqual(mp.getHunkAt(0), filePatches[0].getHunks()[0]); + assert.strictEqual(mp.getHunkAt(3), filePatches[0].getHunks()[0]); + assert.strictEqual(mp.getHunkAt(4), filePatches[0].getHunks()[1]); + assert.strictEqual(mp.getHunkAt(7), filePatches[0].getHunks()[1]); + assert.strictEqual(mp.getHunkAt(8), filePatches[1].getHunks()[0]); + assert.strictEqual(mp.getHunkAt(15), filePatches[1].getHunks()[1]); + assert.strictEqual(mp.getHunkAt(16), filePatches[2].getHunks()[0]); + assert.strictEqual(mp.getHunkAt(23), filePatches[2].getHunks()[1]); + }); + function buildFilePatchFixture(index) { const rowOffset = buffer.getLastRow(); for (let i = 0; i < 8; i++) { diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 0ee6dccbef..2de64df4df 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -90,43 +90,6 @@ describe('Patch', function() { assert.strictEqual(p1.getMaxLineNumberWidth(), 0); }); - it('accesses the Hunk at a buffer row', function() { - const buffer = buildBuffer(8); - const layers = buildLayers(buffer); - const hunk0 = new Hunk({ - oldStartRow: 1, oldRowCount: 4, newStartRow: 1, newRowCount: 4, - marker: markRange(layers.hunk, 0, 3), - regions: [ - new Unchanged(markRange(layers.unchanged, 0)), - new Addition(markRange(layers.addition, 1)), - new Deletion(markRange(layers.deletion, 2)), - new Unchanged(markRange(layers.unchanged, 3)), - ], - }); - const hunk1 = new Hunk({ - oldStartRow: 10, oldRowCount: 4, newStartRow: 10, newRowCount: 4, - marker: markRange(layers.hunk, 4, 7), - regions: [ - new Unchanged(markRange(layers.unchanged, 4)), - new Deletion(markRange(layers.deletion, 5)), - new Addition(markRange(layers.addition, 6)), - new Unchanged(markRange(layers.unchanged, 7)), - ], - }); - const hunks = [hunk0, hunk1]; - const patch = new Patch({status: 'modified', hunks, buffer, layers}); - - assert.strictEqual(patch.getHunkAt(0), hunk0); - assert.strictEqual(patch.getHunkAt(1), hunk0); - assert.strictEqual(patch.getHunkAt(2), hunk0); - assert.strictEqual(patch.getHunkAt(3), hunk0); - assert.strictEqual(patch.getHunkAt(4), hunk1); - assert.strictEqual(patch.getHunkAt(5), hunk1); - assert.strictEqual(patch.getHunkAt(6), hunk1); - assert.strictEqual(patch.getHunkAt(7), hunk1); - assert.isUndefined(patch.getHunkAt(10)); - }); - it('clones itself with optionally overridden properties', function() { const buffer = new TextBuffer({text: 'bufferText'}); const layers = buildLayers(buffer); @@ -844,7 +807,6 @@ describe('Patch', function() { assert.strictEqual(nullPatch.toString(), ''); assert.strictEqual(nullPatch.getChangedLineCount(), 0); assert.strictEqual(nullPatch.getMaxLineNumberWidth(), 0); - assert.isUndefined(nullPatch.getHunkAt(0)); assert.deepEqual(nullPatch.getFirstChangeRange(), [[0, 0], [0, 0]]); assert.deepEqual(nullPatch.getNextSelectionRange(), [[0, 0], [0, 0]]); }); From a04f1e813a5ab967e3c42a9cc5030d6ec0acace4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 5 Nov 2018 16:09:28 -0500 Subject: [PATCH 0825/4053] Construct MultiFilePatches correctly in the builder --- lib/models/patch/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index f11ffb4a51..7ec3e0a1bd 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -55,7 +55,7 @@ export function buildMultiFilePatch(diffs) { const filePatches = actions.map(action => action()); - return new MultiFilePatch(layeredBuffer.buffer, layeredBuffer.layers.patch, filePatches); + return new MultiFilePatch(layeredBuffer.buffer, layeredBuffer.layers.patch, layeredBuffer.layers.hunk, filePatches); } function emptyDiffFilePatch() { From e9d33c9f5cd22fb979aefb7c2bdc30f727cd94da Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 5 Nov 2018 16:39:11 -0500 Subject: [PATCH 0826/4053] WIP: Move adoptBufferFrom() to MultiFilePatch --- lib/models/patch/multi-file-patch.js | 45 ++++++++++++ lib/models/patch/patch.js | 20 ------ test/models/patch/multi-file-patch.test.js | 39 +++++++++++ test/models/patch/patch.test.js | 81 ---------------------- 4 files changed, 84 insertions(+), 101 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 00fb595011..b4c275d089 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -33,4 +33,49 @@ export default class MultiFilePatch { const [marker] = this.hunkLayer.findMarkers({intersectsRow: bufferRow}); return this.hunksByMarker.get(marker); } + + adoptBufferFrom(lastMultiFilePatch) { + lastMultiFilePatch.getHunkLayer().clear(); + lastMultiFilePatch.getUnchangedLayer().clear(); + lastMultiFilePatch.getAdditionLayer().clear(); + lastMultiFilePatch.getDeletionLayer().clear(); + lastMultiFilePatch.getNoNewlineLayer().clear(); + + const nextBuffer = lastMultiFilePatch.getBuffer(); + nextBuffer.setText(this.getBuffer().getText()); + + for (const hunk of this.getHunks()) { + hunk.reMarkOn(lastMultiFilePatch.getHunkLayer()); + for (const region of hunk.getRegions()) { + const target = region.when({ + unchanged: () => lastMultiFilePatch.getUnchangedLayer(), + addition: () => lastMultiFilePatch.getAdditionLayer(), + deletion: () => lastMultiFilePatch.getDeletionLayer(), + nonewline: () => lastMultiFilePatch.getNoNewlineLayer(), + }); + region.reMarkOn(target); + } + } + + this.filePatchesByMarker.clear(); + this.hunksByMarker.clear(); + + for (const filePatch of this.filePatches) { + this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); + for (const hunk of filePatch.getHunks()) { + this.hunksByMarker.set(hunk.getMarker(), hunk); + } + } + + this.hunkLayer = lastMultiFilePatch.getHunkLayer(); + + this.unchangedLayer = lastMultiFilePatch.getUnchangedLayer(); + + // FIXME + this.additionLayer = lastMultiFilePatch.getAdditionLayer(); + this.deletionLayer = lastMultiFilePatch.getDeletionLayer(); + this.noNewlineLayer = lastMultiFilePatch.getNoNewlineLayer(); + + this.buffer = nextBuffer; + } } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index fdfa3de092..7e137843dc 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -479,26 +479,6 @@ class NullPatch { return [[0, 0], [0, 0]]; } - adoptBufferFrom(lastPatch) { - lastPatch.getHunkLayer().clear(); - lastPatch.getUnchangedLayer().clear(); - lastPatch.getAdditionLayer().clear(); - lastPatch.getDeletionLayer().clear(); - lastPatch.getNoNewlineLayer().clear(); - - const nextBuffer = lastPatch.getBuffer(); - nextBuffer.setText(''); - - this.hunkLayer = lastPatch.getHunkLayer(); - this.unchangedLayer = lastPatch.getUnchangedLayer(); - this.additionLayer = lastPatch.getAdditionLayer(); - this.deletionLayer = lastPatch.getDeletionLayer(); - this.noNewlineLayer = lastPatch.getNoNewlineLayer(); - - this.buffer.release(); - this.buffer = nextBuffer; - } - getMaxLineNumberWidth() { return 0; } diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 8719758fcf..608eac6d16 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -59,6 +59,45 @@ describe('MultiFilePatch', function() { assert.strictEqual(mp.getHunkAt(23), filePatches[2].getHunks()[1]); }); + it('adopts a buffer from a previous patch', function() { + const lastBuffer = buffer; + const lastLayers = layers; + const lastFilePatches = [ + buildFilePatchFixture(0), + buildFilePatchFixture(1), + buildFilePatchFixture(2), + ]; + const lastPatch = new MultiFilePatch(lastBuffer, lastLayers.patch, lastLayers.hunk, lastFilePatches); + + buffer = new TextBuffer(); + layers = { + patch: buffer.addMarkerLayer(), + hunk: buffer.addMarkerLayer(), + unchanged: buffer.addMarkerLayer(), + addition: buffer.addMarkerLayer(), + deletion: buffer.addMarkerLayer(), + noNewline: buffer.addMarkerLayer(), + }; + const nextFilePatches = [ + buildFilePatchFixture(0), + buildFilePatchFixture(1), + buildFilePatchFixture(2), + buildFilePatchFixture(3), + ]; + const nextPatch = new MultiFilePatch(buffer, layers.patch, layers.hunk, nextFilePatches); + + nextPatch.adoptBufferFrom(lastPatch); + + assert.strictEqual(nextPatch.getBuffer(), lastBuffer); + assert.strictEqual(nextPatch.getHunkLayer(), lastLayers.hunk); + assert.strictEqual(nextPatch.getUnchangedLayer(), lastLayers.unchanged); + assert.strictEqual(nextPatch.getAdditionLayer(), lastLayers.addition); + assert.strictEqual(nextPatch.getDeletionLayer(), lastLayers.deletion); + assert.strictEqual(nextPatch.getNoNewlineLayer(), lastLayers.noNewline); + + assert.lengthOf(nextPatch.getHunkLayer().getMarkers(), 8); + }); + function buildFilePatchFixture(index) { const rowOffset = buffer.getLastRow(); for (let i = 0; i < 8; i++) { diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 2de64df4df..e20ebcc512 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -810,87 +810,6 @@ describe('Patch', function() { assert.deepEqual(nullPatch.getFirstChangeRange(), [[0, 0], [0, 0]]); assert.deepEqual(nullPatch.getNextSelectionRange(), [[0, 0], [0, 0]]); }); - - it('adopts a buffer from a previous patch', function() { - const patch0 = buildPatchFixture(); - const buffer0 = patch0.getBuffer(); - const hunkLayer0 = patch0.getHunkLayer(); - const unchangedLayer0 = patch0.getUnchangedLayer(); - const additionLayer0 = patch0.getAdditionLayer(); - const deletionLayer0 = patch0.getDeletionLayer(); - const noNewlineLayer0 = patch0.getNoNewlineLayer(); - - const buffer1 = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n No newline at end of file'}); - const layers1 = buildLayers(buffer1); - const hunks1 = [ - new Hunk({ - oldStartRow: 1, oldRowCount: 2, newStartRow: 1, newRowCount: 3, - sectionHeading: '0', - marker: markRange(layers1.hunk, 0, 2), - regions: [ - new Unchanged(markRange(layers1.unchanged, 0)), - new Addition(markRange(layers1.addition, 1)), - new Unchanged(markRange(layers1.unchanged, 2)), - ], - }), - new Hunk({ - oldStartRow: 5, oldRowCount: 2, newStartRow: 1, newRowCount: 3, - sectionHeading: '0', - marker: markRange(layers1.hunk, 3, 5), - regions: [ - new Unchanged(markRange(layers1.unchanged, 3)), - new Deletion(markRange(layers1.deletion, 4)), - new NoNewline(markRange(layers1.noNewline, 5)), - ], - }), - ]; - - const patch1 = new Patch({status: 'modified', hunks: hunks1, buffer: buffer1, layers: layers1}); - - assert.notStrictEqual(patch1.getBuffer(), patch0.getBuffer()); - assert.notStrictEqual(patch1.getHunkLayer(), hunkLayer0); - assert.notStrictEqual(patch1.getUnchangedLayer(), unchangedLayer0); - assert.notStrictEqual(patch1.getAdditionLayer(), additionLayer0); - assert.notStrictEqual(patch1.getDeletionLayer(), deletionLayer0); - assert.notStrictEqual(patch1.getNoNewlineLayer(), noNewlineLayer0); - - patch1.adoptBufferFrom(patch0); - - assert.strictEqual(patch1.getBuffer(), buffer0); - - const markerRanges = [ - ['hunk', patch1.getHunkLayer(), hunkLayer0], - ['unchanged', patch1.getUnchangedLayer(), unchangedLayer0], - ['addition', patch1.getAdditionLayer(), additionLayer0], - ['deletion', patch1.getDeletionLayer(), deletionLayer0], - ['noNewline', patch1.getNoNewlineLayer(), noNewlineLayer0], - ].reduce((obj, [key, layer1, layer0]) => { - assert.strictEqual(layer1, layer0, `Layer ${key} not inherited`); - obj[key] = layer1.getMarkers().map(marker => marker.getRange().serialize()); - return obj; - }, {}); - - assert.deepEqual(markerRanges, { - hunk: [ - [[0, 0], [2, 4]], - [[3, 0], [5, 26]], - ], - unchanged: [ - [[0, 0], [0, 4]], - [[2, 0], [2, 4]], - [[3, 0], [3, 4]], - ], - addition: [ - [[1, 0], [1, 4]], - ], - deletion: [ - [[4, 0], [4, 4]], - ], - noNewline: [ - [[5, 0], [5, 26]], - ], - }); - }); }); function buildBuffer(lines, noNewline = false) { From e034b77afa37966361b3e9f485c6582d429d3dfd Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 5 Nov 2018 17:22:23 -0800 Subject: [PATCH 0827/4053] Finish up implementing `adoptBufferFrom()` on MultiFilePatch --- lib/models/patch/multi-file-patch.js | 54 +++++++++++++++++++++------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index b4c275d089..25f1deae90 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,8 +1,14 @@ export default class MultiFilePatch { - constructor(buffer, patchLayer, hunkLayer, filePatches) { + constructor(buffer, layers, filePatches) { this.buffer = buffer; - this.patchLayer = patchLayer; - this.hunkLayer = hunkLayer; + + this.patchLayer = layers.patch; + this.hunkLayer = layers.hunk; + this.unchangedLayer = layers.unchanged; + this.additionLayer = layers.addition; + this.deletionLayer = layers.deletion; + this.noNewlineLayer = layers.noNewline; + this.filePatches = filePatches; this.filePatchesByMarker = new Map(); @@ -20,6 +26,26 @@ export default class MultiFilePatch { return this.buffer; } + getHunkLayer() { + return this.hunkLayer; + } + + getUnchangedLayer() { + return this.unchangedLayer; + } + + getAdditionLayer() { + return this.additionLayer; + } + + getDeletionLayer() { + return this.deletionLayer; + } + + getNoNewlineLayer() { + return this.noNewlineLayer; + } + getFilePatches() { return this.filePatches; } @@ -44,16 +70,18 @@ export default class MultiFilePatch { const nextBuffer = lastMultiFilePatch.getBuffer(); nextBuffer.setText(this.getBuffer().getText()); - for (const hunk of this.getHunks()) { - hunk.reMarkOn(lastMultiFilePatch.getHunkLayer()); - for (const region of hunk.getRegions()) { - const target = region.when({ - unchanged: () => lastMultiFilePatch.getUnchangedLayer(), - addition: () => lastMultiFilePatch.getAdditionLayer(), - deletion: () => lastMultiFilePatch.getDeletionLayer(), - nonewline: () => lastMultiFilePatch.getNoNewlineLayer(), - }); - region.reMarkOn(target); + for (const patch of this.getFilePatches()) { + for (const hunk of patch.getHunks()) { + hunk.reMarkOn(lastMultiFilePatch.getHunkLayer()); + for (const region of hunk.getRegions()) { + const target = region.when({ + unchanged: () => lastMultiFilePatch.getUnchangedLayer(), + addition: () => lastMultiFilePatch.getAdditionLayer(), + deletion: () => lastMultiFilePatch.getDeletionLayer(), + nonewline: () => lastMultiFilePatch.getNoNewlineLayer(), + }); + region.reMarkOn(target); + } } } From 8f01004656f011650448e61cbcc9da2f2ddc602d Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 5 Nov 2018 17:23:12 -0800 Subject: [PATCH 0828/4053] Pass layers object to MultiFilePatch --- lib/models/patch/builder.js | 6 +++++- test/models/patch/multi-file-patch.test.js | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 7ec3e0a1bd..083ac2b657 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -55,7 +55,11 @@ export function buildMultiFilePatch(diffs) { const filePatches = actions.map(action => action()); - return new MultiFilePatch(layeredBuffer.buffer, layeredBuffer.layers.patch, layeredBuffer.layers.hunk, filePatches); + const layers = { + patch: layeredBuffer.layers.patch, + hunk: layeredBuffer.layers.hunk, + }; + return new MultiFilePatch(layeredBuffer.buffer, layers, filePatches); } function emptyDiffFilePatch() { diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 608eac6d16..7a64d74614 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -24,7 +24,7 @@ describe('MultiFilePatch', function() { it('has an accessor for its file patches', function() { const filePatches = [buildFilePatchFixture(0), buildFilePatchFixture(1)]; - const mp = new MultiFilePatch(buffer, layers.patch, layers.hunk, filePatches); + const mp = new MultiFilePatch(buffer, layers, filePatches); assert.strictEqual(mp.getFilePatches(), filePatches); }); @@ -33,7 +33,7 @@ describe('MultiFilePatch', function() { for (let i = 0; i < 10; i++) { filePatches.push(buildFilePatchFixture(i)); } - const mp = new MultiFilePatch(buffer, layers.patch, layers.hunk, filePatches); + const mp = new MultiFilePatch(buffer, layers, filePatches); assert.strictEqual(mp.getFilePatchAt(0), filePatches[0]); assert.strictEqual(mp.getFilePatchAt(7), filePatches[0]); @@ -47,7 +47,7 @@ describe('MultiFilePatch', function() { buildFilePatchFixture(1), buildFilePatchFixture(2), ]; - const mp = new MultiFilePatch(buffer, layers.patch, layers.hunk, filePatches); + const mp = new MultiFilePatch(buffer, layers, filePatches); assert.strictEqual(mp.getHunkAt(0), filePatches[0].getHunks()[0]); assert.strictEqual(mp.getHunkAt(3), filePatches[0].getHunks()[0]); @@ -67,7 +67,7 @@ describe('MultiFilePatch', function() { buildFilePatchFixture(1), buildFilePatchFixture(2), ]; - const lastPatch = new MultiFilePatch(lastBuffer, lastLayers.patch, lastLayers.hunk, lastFilePatches); + const lastPatch = new MultiFilePatch(lastBuffer, layers, lastFilePatches); buffer = new TextBuffer(); layers = { @@ -84,7 +84,7 @@ describe('MultiFilePatch', function() { buildFilePatchFixture(2), buildFilePatchFixture(3), ]; - const nextPatch = new MultiFilePatch(buffer, layers.patch, layers.hunk, nextFilePatches); + const nextPatch = new MultiFilePatch(buffer, layers, nextFilePatches); nextPatch.adoptBufferFrom(lastPatch); From 3247c0d62123b7d5402f2f98506afffeb3984e8b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 5 Nov 2018 19:38:07 -0800 Subject: [PATCH 0829/4053] WIP clean up Patch model and remove layer code Question -- do we still need the layer stuff in BufferBuilder? My guess is no, but there's a bunch of region and marker logic in `getStagePatchForLines` --- lib/models/patch/patch.js | 108 ++------------------------------------ 1 file changed, 4 insertions(+), 104 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 7e137843dc..38d8e3a0eb 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -8,18 +8,12 @@ export default class Patch { return new NullPatch(); } - constructor({status, hunks, buffer, layers, marker}) { + constructor({status, hunks, buffer, marker}) { this.status = status; this.hunks = hunks; this.buffer = buffer; this.marker = marker; - this.hunkLayer = layers.hunk; - this.unchangedLayer = layers.unchanged; - this.additionLayer = layers.addition; - this.deletionLayer = layers.deletion; - this.noNewlineLayer = layers.noNewline; - this.buffer.retain(); this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); } @@ -45,26 +39,6 @@ export default class Patch { return this.buffer; } - getHunkLayer() { - return this.hunkLayer; - } - - getUnchangedLayer() { - return this.unchangedLayer; - } - - getAdditionLayer() { - return this.additionLayer; - } - - getDeletionLayer() { - return this.deletionLayer; - } - - getNoNewlineLayer() { - return this.noNewlineLayer; - } - getByteSize() { return Buffer.byteLength(this.buffer.getText(), 'utf8'); } @@ -83,13 +57,6 @@ export default class Patch { status: opts.status !== undefined ? opts.status : this.getStatus(), hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), buffer: opts.buffer !== undefined ? opts.buffer : this.getBuffer(), - layers: opts.layers !== undefined ? opts.layers : { - hunk: this.getHunkLayer(), - unchanged: this.getUnchangedLayer(), - addition: this.getAdditionLayer(), - deletion: this.getDeletionLayer(), - noNewline: this.getNoNewlineLayer(), - }, }); } @@ -173,7 +140,7 @@ export default class Patch { const wholeFile = rowSet.size === this.changedLineCount; const status = this.getStatus() === 'deleted' && !wholeFile ? 'modified' : this.getStatus(); - return this.clone({hunks, status, buffer: builder.getBuffer(), layers: builder.getLayers()}); + return this.clone({hunks, status, buffer: builder.getBuffer()}); } getUnstagePatchForLines(rowSet) { @@ -261,7 +228,7 @@ export default class Patch { status = 'added'; } - return this.clone({hunks, status, buffer: builder.getBuffer(), layers: builder.getLayers()}); + return this.clone({hunks, status, buffer: builder.getBuffer()}); } getFirstChangeRange() { @@ -330,40 +297,6 @@ export default class Patch { return [[newSelectionRow, 0], [newSelectionRow, Infinity]]; } - // TODO lift up to MultiFilePatch - adoptBufferFrom(lastPatch) { - lastPatch.getHunkLayer().clear(); - lastPatch.getUnchangedLayer().clear(); - lastPatch.getAdditionLayer().clear(); - lastPatch.getDeletionLayer().clear(); - lastPatch.getNoNewlineLayer().clear(); - - const nextBuffer = lastPatch.getBuffer(); - nextBuffer.setText(this.getBuffer().getText()); - - for (const hunk of this.getHunks()) { - hunk.reMarkOn(lastPatch.getHunkLayer()); - for (const region of hunk.getRegions()) { - const target = region.when({ - unchanged: () => lastPatch.getUnchangedLayer(), - addition: () => lastPatch.getAdditionLayer(), - deletion: () => lastPatch.getDeletionLayer(), - nonewline: () => lastPatch.getNoNewlineLayer(), - }); - region.reMarkOn(target); - } - } - - this.hunkLayer = lastPatch.getHunkLayer(); - this.unchangedLayer = lastPatch.getUnchangedLayer(); - this.additionLayer = lastPatch.getAdditionLayer(); - this.deletionLayer = lastPatch.getDeletionLayer(); - this.noNewlineLayer = lastPatch.getNoNewlineLayer(); - - this.buffer = nextBuffer; - this.hunksByMarker = new Map(this.getHunks().map(hunk => [hunk.getMarker(), hunk])); - } - toString() { return this.getHunks().reduce((str, hunk) => str + hunk.toStringIn(this.getBuffer()), ''); } @@ -390,11 +323,6 @@ export default class Patch { class NullPatch { constructor() { this.buffer = new TextBuffer(); - this.hunkLayer = this.buffer.addMarkerLayer(); - this.unchangedLayer = this.buffer.addMarkerLayer(); - this.additionLayer = this.buffer.addMarkerLayer(); - this.deletionLayer = this.buffer.addMarkerLayer(); - this.noNewlineLayer = this.buffer.addMarkerLayer(); this.buffer.retain(); } @@ -411,26 +339,6 @@ class NullPatch { return this.buffer; } - getHunkLayer() { - return this.hunkLayer; - } - - getUnchangedLayer() { - return this.unchangedLayer; - } - - getAdditionLayer() { - return this.additionLayer; - } - - getDeletionLayer() { - return this.deletionLayer; - } - - getNoNewlineLayer() { - return this.noNewlineLayer; - } - getByteSize() { return 0; } @@ -443,8 +351,7 @@ class NullPatch { if ( opts.status === undefined && opts.hunks === undefined && - opts.buffer === undefined && - opts.layers === undefined + opts.buffer === undefined ) { return this; } else { @@ -452,13 +359,6 @@ class NullPatch { status: opts.status !== undefined ? opts.status : this.getStatus(), hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), buffer: opts.buffer !== undefined ? opts.buffer : this.getBuffer(), - layers: opts.layers !== undefined ? opts.layers : { - hunk: this.getHunkLayer(), - unchanged: this.getUnchangedLayer(), - addition: this.getAdditionLayer(), - deletion: this.getDeletionLayer(), - noNewline: this.getNoNewlineLayer(), - }, }); } } From 178f50491fc4a9ad71ed1e1057b8dd43b9079940 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 00:24:07 -0800 Subject: [PATCH 0830/4053] Don't pass layer to Patch constructor --- lib/models/patch/builder.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 083ac2b657..9dbd146c57 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -92,7 +92,7 @@ function singleDiffFilePatch(diff, layeredBuffer = null) { const newFile = diff.newPath !== null || diff.newMode !== null ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink}) : nullFile; - const patch = new Patch({status: diff.status, hunks, marker: patchMarker, ...layeredBuffer}); + const patch = new Patch({status: diff.status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); return new FilePatch(oldFile, newFile, patch); } @@ -137,7 +137,7 @@ function dualDiffFilePatch(diff1, diff2, layeredBuffer = null) { const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink}); const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink}); - const patch = new Patch({status, hunks, marker: patchMarker, ...layeredBuffer}); + const patch = new Patch({status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); return new FilePatch(oldFile, newFile, patch); } From a1badc1f64a3d957fc10f4d7ad9ee1239d003c9f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 6 Nov 2018 09:50:17 -0500 Subject: [PATCH 0831/4053] Lift all layer and buffer management to MultiFilePatch --- lib/models/patch/file-patch.js | 103 +++++++++++++++++---------- lib/models/patch/multi-file-patch.js | 80 ++++++++++++++++++++- lib/models/patch/patch.js | 56 ++++++++++----- 3 files changed, 180 insertions(+), 59 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 16f9165469..494d663982 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -104,6 +104,10 @@ export default class FilePatch { return this.getPatch().getNoNewlineLayer(); } + containsRow(row) { + return this.getPatch().containsRow(row); + } + // TODO delete if unused getAdditionRanges() { return this.getHunks().reduce((acc, hunk) => { @@ -136,10 +140,6 @@ export default class FilePatch { return [range]; } - adoptBufferFrom(prevFilePatch) { - this.getPatch().adoptBufferFrom(prevFilePatch.getPatch()); - } - didChangeExecutableMode() { if (!this.oldFile.isPresent() || !this.newFile.isPresent()) { return false; @@ -182,52 +182,77 @@ export default class FilePatch { ); } - getStagePatchForLines(selectedLineSet) { - if (this.patch.getChangedLineCount() === selectedLineSet.size) { - if (this.hasTypechange() && this.getStatus() === 'deleted') { - // handle special case when symlink is created where a file was deleted. In order to stage the file deletion, - // we must ensure that the created file patch has no new file - return this.clone({newFile: nullFile}); - } else { - return this; - } - } else { - const patch = this.patch.getStagePatchForLines(selectedLineSet); - if (this.getStatus() === 'deleted') { - // Populate newFile - return this.clone({newFile: this.getOldFile(), patch}); - } else { - return this.clone({patch}); + buildStagePatchForLines(originalBuffer, nextLayeredBuffer, selectedLineSet) { + let newFile = this.getOldFile(); + + if (this.hasTypechange() && this.getStatus() === 'deleted') { + // Handle the special case when symlink is created where an entire file was deleted. In order to stage the file + // deletion, we must ensure that the created file patch has no new file. + if ( + this.patch.getChangedLineCount() === selectedLineSet.size && + Array.from(selectedLineSet, row => this.patch.containsRow(row)).every(Boolean) + ) { + newFile = nullFile; } } - } - getStagePatchForHunk(selectedHunk) { - return this.getStagePatchForLines(new Set(selectedHunk.getBufferRows())); + const {patch, buffer, layers} = this.patch.getStagePatchForLines( + originalBuffer, + nextLayeredBuffer, + selectedLineSet, + ); + if (this.getStatus() === 'deleted') { + // Populate newFile + return { + filePatch: this.clone({newFile, patch}), + buffer, + layers, + }; + } else { + return { + filePatch: this.clone({patch}), + buffer, + layers, + }; + } } - getUnstagePatchForLines(selectedLineSet) { - const wholeFile = this.patch.getChangedLineCount() === selectedLineSet.size; + buildUnstagePatchForLines(originalBuffer, nextLayeredBuffer, selectedLineSet) { const nonNullFile = this.getNewFile().isPresent() ? this.getNewFile() : this.getOldFile(); let oldFile = this.getNewFile(); let newFile = nonNullFile; - if (wholeFile && this.getStatus() === 'added') { - // Ensure that newFile is null if the patch is an addition because we're deleting the entire file from the - // index. If a symlink was deleted and replaced by a non-symlink file, we don't want the symlink entry to muck - // up the patch. - oldFile = nonNullFile; - newFile = nullFile; - } else if (wholeFile && this.getStatus() === 'deleted') { - oldFile = nullFile; - newFile = nonNullFile; + if (this.getStatus() === 'added') { + if ( + selectedLineSet.size === this.patch.getChangedLineCount() && + Array.from(selectedLineSet, row => this.patch.containsRow(row)).every(Boolean) + ) { + // Ensure that newFile is null if the patch is an addition because we're deleting the entire file from the + // index. If a symlink was deleted and replaced by a non-symlink file, we don't want the symlink entry to muck + // up the patch. + oldFile = nonNullFile; + newFile = nullFile; + } + } else if (this.getStatus() === 'deleted') { + if ( + selectedLineSet.size === this.patch.getChangedLineCount() && + Array.from(selectedLineSet, row => this.patch.containsRow(row)).every(Boolean) + ) { + oldFile = nullFile; + newFile = nonNullFile; + } } - return this.clone({oldFile, newFile, patch: this.patch.getUnstagePatchForLines(selectedLineSet)}); - } - - getUnstagePatchForHunk(hunk) { - return this.getUnstagePatchForLines(new Set(hunk.getBufferRows())); + const {patch, buffer, layers} = this.patch.buildUnstagePatchForLines( + originalBuffer, + nextLayeredBuffer, + selectedLineSet, + ); + return { + filePatch: this.clone({oldFile, newFile, patch}), + buffer, + layers, + }; } getNextSelectionRange(lastFilePatch, lastSelectedRows) { diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 25f1deae90..eb2b022e17 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,3 +1,5 @@ +import {TextBuffer} from 'atom'; + export default class MultiFilePatch { constructor(buffer, layers, filePatches) { this.buffer = buffer; @@ -60,6 +62,40 @@ export default class MultiFilePatch { return this.hunksByMarker.get(marker); } + getStagePatchForLines(selectedLineSet) { + const nextLayeredBuffer = this.buildLayeredBuffer(); + const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { + return fp.buildStagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); + }); + + return new MultiFilePatch( + nextLayeredBuffer.buffer, + nextLayeredBuffer.layers, + nextFilePatches, + ); + } + + getStagePatchForHunk(hunk) { + return this.getStagePatchForLines(new Set(hunk.getBufferRows())); + } + + getUnstagePatchForLines(selectedLineSet) { + const nextLayeredBuffer = this.buildLayeredBuffer(); + const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { + return fp.buildUnstagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); + }); + + return new MultiFilePatch( + nextLayeredBuffer.buffer, + nextLayeredBuffer.layers, + nextFilePatches, + ); + } + + getUnstagePatchForHunk(hunk) { + return this.getUnstagePatchForLines(new Set(hunk.getBufferRows())); + } + adoptBufferFrom(lastMultiFilePatch) { lastMultiFilePatch.getHunkLayer().clear(); lastMultiFilePatch.getUnchangedLayer().clear(); @@ -95,15 +131,53 @@ export default class MultiFilePatch { } } + this.patchLayer = lastMultiFilePatch.getPatchLayer(); this.hunkLayer = lastMultiFilePatch.getHunkLayer(); - this.unchangedLayer = lastMultiFilePatch.getUnchangedLayer(); - - // FIXME this.additionLayer = lastMultiFilePatch.getAdditionLayer(); this.deletionLayer = lastMultiFilePatch.getDeletionLayer(); this.noNewlineLayer = lastMultiFilePatch.getNoNewlineLayer(); this.buffer = nextBuffer; } + + buildLayeredBuffer() { + const buffer = new TextBuffer(); + buffer.retain(); + + return { + buffer, + layers: { + patch: buffer.addMarkerLayer(), + hunk: buffer.addMarkerLayer(), + unchanged: buffer.addMarkerLayer(), + addition: buffer.addMarkerLayer(), + deletion: buffer.addMarkerLayer(), + noNewline: buffer.addMarkerLayer(), + }, + }; + } + + /* + * Efficiently locate the FilePatch instances that contain at least one row from a Set. + */ + getFilePatchesContaining(rowSet) { + const sortedRowSet = Array.from(rowSet); + sortedRowSet.sort((a, b) => b - a); + + const filePatches = new Set(); + let lastFilePatch = null; + for (const row in sortedRowSet) { + // Because the rows are sorted, consecutive rows will almost certainly belong to the same patch, so we can save + // many avoidable marker index lookups by comparing with the last. + if (lastFilePatch && lastFilePatch.containsRow(row)) { + continue; + } + + lastFilePatch = this.getFilePatchAt(row); + filePatches.add(lastFilePatch); + } + + return filePatches; + } } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 38d8e3a0eb..1c3f380b25 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -8,13 +8,11 @@ export default class Patch { return new NullPatch(); } - constructor({status, hunks, buffer, marker}) { + constructor({status, hunks, marker}) { this.status = status; this.hunks = hunks; - this.buffer = buffer; this.marker = marker; - this.buffer.retain(); this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); } @@ -47,6 +45,10 @@ export default class Patch { return this.changedLineCount; } + containsRow(row) { + return this.marker.getRange().intersectsRow(row); + } + getMaxLineNumberWidth() { const lastHunk = this.hunks[this.hunks.length - 1]; return lastHunk ? lastHunk.getMaxLineNumberWidth() : 0; @@ -60,8 +62,8 @@ export default class Patch { }); } - getStagePatchForLines(rowSet) { - const builder = new BufferBuilder(this.getBuffer()); + buildStagePatchForLines(originalBuffer, nextLayeredBuffer, rowSet) { + const builder = new BufferBuilder(originalBuffer, nextLayeredBuffer); const hunks = []; let newRowDelta = 0; @@ -138,13 +140,21 @@ export default class Patch { } } + const buffer = builder.getBuffer(); + const layers = builder.getLayers(); + const marker = layers.patch.markRange([[0, 0], [buffer.getLastRow(), Infinity]]); + const wholeFile = rowSet.size === this.changedLineCount; const status = this.getStatus() === 'deleted' && !wholeFile ? 'modified' : this.getStatus(); - return this.clone({hunks, status, buffer: builder.getBuffer()}); + return { + patch: this.clone({hunks, status, marker}), + buffer, + layers, + }; } - getUnstagePatchForLines(rowSet) { - const builder = new BufferBuilder(this.getBuffer()); + buildUnstagePatchForLines(originalBuffer, nextLayeredBuffer, rowSet) { + const builder = new BufferBuilder(originalBuffer, nextLayeredBuffer); const hunks = []; let newRowDelta = 0; @@ -228,7 +238,15 @@ export default class Patch { status = 'added'; } - return this.clone({hunks, status, buffer: builder.getBuffer()}); + const buffer = builder.getBuffer(); + const layers = builder.getLayers(); + const marker = layers.patch.markRange([[0, 0], [buffer.getLastRow(), Infinity]]); + + return { + patch: this.clone({hunks, status, marker}), + buffer, + layers, + }; } getFirstChangeRange() { @@ -397,15 +415,19 @@ class NullPatch { } class BufferBuilder { - constructor(original) { + constructor(original, nextLayeredBuffer) { this.originalBuffer = original; - this.buffer = new TextBuffer(); - this.buffer.retain(); - this.layers = new Map( - [Unchanged, Addition, Deletion, NoNewline, 'hunk'].map(key => { - return [key, this.buffer.addMarkerLayer()]; - }), - ); + + this.buffer = nextLayeredBuffer.buffer; + this.layers = new Map([ + [Unchanged, nextLayeredBuffer.layers.unchanged], + [Addition, nextLayeredBuffer.layers.addition], + [Deletion, nextLayeredBuffer.layers.deletion], + [NoNewline, nextLayeredBuffer.layers.noNewline], + ['hunk', nextLayeredBuffer.layers.hunk], + ['patch', nextLayeredBuffer.layers.patch], + ]); + this.offset = 0; this.hunkBufferText = ''; From d6d5ff717d8b65ffcd30809cf36037a5e50572a2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 6 Nov 2018 10:25:28 -0500 Subject: [PATCH 0832/4053] Applyable MultiFilePatch strings through the magic of concatenation --- lib/models/patch/multi-file-patch.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index eb2b022e17..d406b35e44 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -180,4 +180,11 @@ export default class MultiFilePatch { return filePatches; } + + /* + * Construct an apply-able patch String. + */ + toString() { + return this.filePatches.map(fp => fp.toString()).join(''); + } } From a9b9a76795cb3b28dd8c1d1a1db98c41ecdb859a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 6 Nov 2018 10:28:57 -0500 Subject: [PATCH 0833/4053] Use lifted MultiFilePatch methods in FilePatchView and FilePatchController --- lib/controllers/file-patch-controller.js | 10 +++++----- lib/views/file-patch-view.js | 25 ++++++++++++------------ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index acf3d354b5..d6092f9a33 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -128,7 +128,7 @@ export default class FilePatchController extends React.Component { }); } - async toggleRows(filePatch, rowSet, nextSelectionMode) { + async toggleRows(rowSet, nextSelectionMode) { let chosenRows = rowSet; if (chosenRows) { await this.selectedRowsChanged(chosenRows, nextSelectionMode); @@ -142,8 +142,8 @@ export default class FilePatchController extends React.Component { return this.stagingOperation(() => { const patch = this.withStagingStatus({ - staged: () => filePatch.getUnstagePatchForLines(chosenRows), - unstaged: () => filePatch.getStagePatchForLines(chosenRows), + staged: () => this.props.multiFilePatch.getUnstagePatchForLines(chosenRows), + unstaged: () => this.props.multiFilePatch.getStagePatchForLines(chosenRows), }); return this.props.repository.applyPatchToIndex(patch); }); @@ -182,7 +182,7 @@ export default class FilePatchController extends React.Component { }); } - async discardRows(filePatch, rowSet, nextSelectionMode, {eventSource} = {}) { + async discardRows(rowSet, nextSelectionMode, {eventSource} = {}) { let chosenRows = rowSet; if (chosenRows) { await this.selectedRowsChanged(chosenRows, nextSelectionMode); @@ -197,7 +197,7 @@ export default class FilePatchController extends React.Component { eventSource, }); - return this.props.discardLines(filePatch, chosenRows, this.props.repository); + return this.props.discardLines(this.props.multiFilePatch, chosenRows, this.props.repository); } selectedRowsChanged(rows, nextSelectionMode) { diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 6c992721ea..38e4f9887d 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -597,16 +597,17 @@ export default class FilePatchView extends React.Component { discardSelectionFromCommand = () => { return this.props.discardRows( + this.props.multiFilePatch, this.props.selectedRows, this.props.selectionMode, {eventSource: {command: 'github:discard-selected-lines'}}, ); } - toggleHunkSelection(filePatch, hunk, containsSelection) { + toggleHunkSelection(hunk, containsSelection) { if (containsSelection) { return this.props.toggleRows( - filePatch, + this.props.multiFilePatch, this.props.selectedRows, this.props.selectionMode, {eventSource: 'button'}, @@ -619,14 +620,19 @@ export default class FilePatchView extends React.Component { return rows; }, []), ); - return this.props.toggleRows(filePatch, changeRows, 'hunk', {eventSource: 'button'}); + return this.props.toggleRows( + this.props.multiFilePatch, + changeRows, + 'hunk', + {eventSource: 'button'}, + ); } } - discardHunkSelection(filePatch, hunk, containsSelection) { + discardHunkSelection(hunk, containsSelection) { if (containsSelection) { return this.props.discardRows( - filePatch, + this.props.multiFilePatch, this.props.selectedRows, this.props.selectionMode, {eventSource: 'button'}, @@ -639,7 +645,7 @@ export default class FilePatchView extends React.Component { return rows; }, []), ); - return this.props.discardRows(filePatch, changeRows, 'hunk', {eventSource: 'button'}); + return this.props.discardRows(this.props.multiFilePatch, changeRows, 'hunk', {eventSource: 'button'}); } } @@ -784,12 +790,7 @@ export default class FilePatchView extends React.Component { } didConfirm() { - return Promise.all( - Array.from( - this.getSelectedFilePatches(), - filePatch => this.props.toggleRows(filePatch, this.props.selectedRows, this.props.selectionMode), - ), - ); + return this.props.toggleRows(this.props.multiFilePatch, this.props.selectedRows, this.props.selectionMode); } didToggleSelectionMode() { From 5bdd388c67d3beae38b2e29bc1a7887aba3bb117 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 6 Nov 2018 10:29:16 -0500 Subject: [PATCH 0834/4053] Hack to get discardLines() happy with a MultiFilePatch --- lib/controllers/root-controller.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 5622941a05..db41139e7b 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -677,17 +677,19 @@ export default class RootController extends React.Component { ); } - async discardLines(filePatch, lines, repository = this.props.repository) { - const filePath = filePatch.getPath(); + async discardLines(multiFilePatch, lines, repository = this.props.repository) { + const filePaths = multiFilePatch.getFilePatches().map(fp => fp.getPath()); const destructiveAction = async () => { - const discardFilePatch = filePatch.getUnstagePatchForLines(lines); + const discardFilePatch = multiFilePatch.getUnstagePatchForLines(lines); await repository.applyPatchToWorkdir(discardFilePatch); }; return await repository.storeBeforeAndAfterBlobs( - [filePath], - () => this.ensureNoUnsavedFiles([filePath], 'Cannot discard lines.', repository.getWorkingDirectoryPath()), + [filePaths], + () => this.ensureNoUnsavedFiles(filePaths, 'Cannot discard lines.', repository.getWorkingDirectoryPath()), destructiveAction, - filePath, + // FIXME: Present::storeBeforeAndAfterBlobs() and DiscardHistory::storeBeforeAndAfterBlobs() need a way to store + // multiple partial paths + filePaths[0], ); } From d0a6e362864f7323b26eb3cd2e907e1a1024b930 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 6 Nov 2018 12:57:54 -0500 Subject: [PATCH 0835/4053] WIP Patch tests --- lib/models/patch/patch.js | 8 ----- test/models/patch/patch.test.js | 56 ++++++++++++++++----------------- 2 files changed, 28 insertions(+), 36 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 1c3f380b25..7416215839 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -33,14 +33,6 @@ export default class Patch { return this.hunks; } - getBuffer() { - return this.buffer; - } - - getByteSize() { - return Buffer.byteLength(this.buffer.getText(), 'utf8'); - } - getChangedLineCount() { return this.changedLineCount; } diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index e20ebcc512..b412df6c45 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -9,23 +9,11 @@ describe('Patch', function() { it('has some standard accessors', function() { const buffer = new TextBuffer({text: 'bufferText'}); const layers = buildLayers(buffer); - const p = new Patch({status: 'modified', hunks: [], buffer, layers}); + const marker = markRange(layers.patch, 0, Infinity); + const p = new Patch({status: 'modified', hunks: [], marker}); assert.strictEqual(p.getStatus(), 'modified'); assert.deepEqual(p.getHunks(), []); - assert.strictEqual(p.getBuffer().getText(), 'bufferText'); assert.isTrue(p.isPresent()); - - assert.strictEqual(p.getUnchangedLayer().getMarkerCount(), 0); - assert.strictEqual(p.getAdditionLayer().getMarkerCount(), 0); - assert.strictEqual(p.getDeletionLayer().getMarkerCount(), 0); - assert.strictEqual(p.getNoNewlineLayer().getMarkerCount(), 0); - }); - - it('computes the byte size of the total patch data', function() { - const buffer = new TextBuffer({text: '\u00bd + \u00bc = \u00be'}); - const layers = buildLayers(buffer); - const p = new Patch({status: 'modified', hunks: [], buffer, layers}); - assert.strictEqual(p.getByteSize(), 12); }); it('computes the total changed line count', function() { @@ -58,7 +46,9 @@ describe('Patch', function() { ], }), ]; - const p = new Patch({status: 'modified', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, Infinity); + + const p = new Patch({status: 'modified', hunks, marker}); assert.strictEqual(p.getChangedLineCount(), 10); }); @@ -93,34 +83,37 @@ describe('Patch', function() { it('clones itself with optionally overridden properties', function() { const buffer = new TextBuffer({text: 'bufferText'}); const layers = buildLayers(buffer); - const original = new Patch({status: 'modified', hunks: [], buffer, layers}); + const marker = markRange(layers.patch, 0, Infinity); + + const original = new Patch({status: 'modified', hunks: [], marker}); const dup0 = original.clone(); assert.notStrictEqual(dup0, original); assert.strictEqual(dup0.getStatus(), 'modified'); assert.deepEqual(dup0.getHunks(), []); - assert.strictEqual(dup0.getBuffer().getText(), 'bufferText'); + assert.strictEqual(dup0.getMarker(), marker); const dup1 = original.clone({status: 'added'}); assert.notStrictEqual(dup1, original); assert.strictEqual(dup1.getStatus(), 'added'); assert.deepEqual(dup1.getHunks(), []); - assert.strictEqual(dup1.getBuffer().getText(), 'bufferText'); + assert.strictEqual(dup0.getMarker(), marker); const hunks = [new Hunk({regions: []})]; const dup2 = original.clone({hunks}); assert.notStrictEqual(dup2, original); assert.strictEqual(dup2.getStatus(), 'modified'); assert.deepEqual(dup2.getHunks(), hunks); - assert.strictEqual(dup2.getBuffer().getText(), 'bufferText'); + assert.strictEqual(dup0.getMarker(), marker); const nBuffer = new TextBuffer({text: 'changed'}); const nLayers = buildLayers(nBuffer); - const dup3 = original.clone({buffer: nBuffer, layers: nLayers}); + const nMarker = markRange(nLayers.patch, 0, Infinity); + const dup3 = original.clone({marker: nMarker}); assert.notStrictEqual(dup3, original); assert.strictEqual(dup3.getStatus(), 'modified'); assert.deepEqual(dup3.getHunks(), []); - assert.strictEqual(dup3.getBuffer().getText(), 'changed'); + assert.strictEqual(dup3.getMarker(), nMarker); }); it('clones a nullPatch as a nullPatch', function() { @@ -135,22 +128,23 @@ describe('Patch', function() { assert.notStrictEqual(dup0, nullPatch); assert.strictEqual(dup0.getStatus(), 'added'); assert.deepEqual(dup0.getHunks(), []); - assert.strictEqual(dup0.getBuffer().getText(), ''); + assert.deepEqual(dup0.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); const hunks = [new Hunk({regions: []})]; const dup1 = nullPatch.clone({hunks}); assert.notStrictEqual(dup1, nullPatch); assert.isNull(dup1.getStatus()); assert.deepEqual(dup1.getHunks(), hunks); - assert.strictEqual(dup1.getBuffer().getText(), ''); + assert.deepEqual(dup0.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); const nBuffer = new TextBuffer({text: 'changed'}); const nLayers = buildLayers(nBuffer); - const dup2 = nullPatch.clone({buffer: nBuffer, layers: nLayers}); + const nMarker = markRange(nLayers.patch, 0, Infinity); + const dup2 = nullPatch.clone({marker: nMarker}); assert.notStrictEqual(dup2, nullPatch); assert.isNull(dup2.getStatus()); assert.deepEqual(dup2.getHunks(), []); - assert.strictEqual(dup2.getBuffer().getText(), 'changed'); + assert.strictEqual(dup2.getMarker(), nMarker); }); describe('stage patch generation', function() { @@ -801,8 +795,7 @@ describe('Patch', function() { const nullPatch = Patch.createNull(); assert.isNull(nullPatch.getStatus()); assert.deepEqual(nullPatch.getHunks(), []); - assert.strictEqual(nullPatch.getBuffer().getText(), ''); - assert.strictEqual(nullPatch.getByteSize(), 0); + assert.deepEqual(nullPatch.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); assert.isFalse(nullPatch.isPresent()); assert.strictEqual(nullPatch.toString(), ''); assert.strictEqual(nullPatch.getChangedLineCount(), 0); @@ -832,6 +825,7 @@ function buildBuffer(lines, noNewline = false) { function buildLayers(buffer) { return { + patch: buffer.addMarkerLayer(), hunk: buffer.addMarkerLayer(), unchanged: buffer.addMarkerLayer(), addition: buffer.addMarkerLayer(), @@ -895,6 +889,12 @@ function buildPatchFixture() { ], }), ]; + const marker = markRange(layers.patch, 0, Infinity); - return new Patch({status: 'modified', hunks, buffer, layers}); + return { + patch: new Patch({status: 'modified', hunks, marker}), + buffer, + layers, + marker, + }; } From bcf8e8c825313a7f630d16cdae13e647ae2d57f9 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 6 Nov 2018 22:12:56 +0100 Subject: [PATCH 0836/4053] use FilePatchController instead of MultiFilePatchController --- lib/containers/changed-file-container.js | 6 ++---- lib/containers/commit-preview-container.js | 6 +++--- lib/models/patch/file-patch.js | 12 ++++++------ 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/containers/changed-file-container.js b/lib/containers/changed-file-container.js index 6ee00e7df3..da4d819382 100644 --- a/lib/containers/changed-file-container.js +++ b/lib/containers/changed-file-container.js @@ -5,7 +5,7 @@ import yubikiri from 'yubikiri'; import {autobind} from '../helpers'; import ObserveModel from '../views/observe-model'; import LoadingView from '../views/loading-view'; -import MultiFilePatchController from '../controllers/multi-file-patch-controller'; +import FilePatchController from '../controllers/file-patch-controller'; export default class ChangedFileContainer extends React.Component { static propTypes = { @@ -53,11 +53,9 @@ export default class ChangedFileContainer extends React.Component { } return ( - ); diff --git a/lib/containers/commit-preview-container.js b/lib/containers/commit-preview-container.js index 877fa76cfb..abc1739070 100644 --- a/lib/containers/commit-preview-container.js +++ b/lib/containers/commit-preview-container.js @@ -4,7 +4,7 @@ import yubikiri from 'yubikiri'; import ObserveModel from '../views/observe-model'; import LoadingView from '../views/loading-view'; -import MultiFilePatchController from '../controllers/multi-file-patch-controller'; +import FilePatchController from '../controllers/file-patch-controller'; export default class CommitPreviewContainer extends React.Component { static propTypes = { @@ -31,8 +31,8 @@ export default class CommitPreviewContainer extends React.Component { } return ( - diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 494d663982..7d7199f0bc 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -12,12 +12,12 @@ export default class FilePatch { this.oldFile = oldFile; this.newFile = newFile; this.patch = patch; - const metricsData = {package: 'github'}; - if (this.getPatch()) { - metricsData.sizeInBytes = this.getByteSize(); - } - - addEvent('file-patch-constructed', metricsData); + // const metricsData = {package: 'github'}; + // if (this.getPatch()) { + // metricsData.sizeInBytes = this.getByteSize(); + // } + // + // addEvent('file-patch-constructed', metricsData); } isPresent() { From 74d859371af89153a5ed507470c47ac55c4884b2 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 6 Nov 2018 22:13:23 +0100 Subject: [PATCH 0837/4053] get rid of anything to do with active states --- lib/controllers/file-patch-controller.js | 1 - lib/views/file-patch-view.js | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index d6092f9a33..7625ed485d 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -26,7 +26,6 @@ export default class FilePatchController extends React.Component { undoLastDiscard: PropTypes.func, surfaceFileAtPath: PropTypes.func, handleClick: PropTypes.func, - isActive: PropTypes.bool, } constructor(props) { diff --git a/lib/views/file-patch-view.js b/lib/views/file-patch-view.js index 38e4f9887d..3f6bdc9885 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/file-patch-view.js @@ -36,7 +36,6 @@ export default class FilePatchView extends React.Component { repository: PropTypes.object.isRequired, hasUndoHistory: PropTypes.bool, useEditorAutoHeight: PropTypes.bool, - isActive: PropTypes.bool.isRequired, workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, @@ -70,7 +69,7 @@ export default class FilePatchView extends React.Component { 'didMouseDownOnHeader', 'didMouseDownOnLineNumber', 'didMouseMoveOnLineNumber', 'didMouseUp', 'didConfirm', 'didToggleSelectionMode', 'selectNextHunk', 'selectPreviousHunk', 'didOpenFile', 'didAddSelection', 'didChangeSelectionRange', 'didDestroySelection', - 'oldLineNumberLabel', 'newLineNumberLabel', 'handleMouseDown', + 'oldLineNumberLabel', 'newLineNumberLabel', ); this.mouseSelectionInProgress = false; @@ -176,8 +175,6 @@ export default class FilePatchView extends React.Component { `github-FilePatchView--${this.props.stagingStatus}`, {'github-FilePatchView--blank': !this.props.multiFilePatch.anyPresent()}, {'github-FilePatchView--hunkMode': this.props.selectionMode === 'hunk'}, - {'github-FilePatchView--active': this.props.isActive}, - {'github-FilePatchView--inactive': !this.props.isActive}, ); return ( @@ -461,7 +458,7 @@ export default class FilePatchView extends React.Component { {filePatch.getHunks().map((hunk, index) => { const containsSelection = this.props.selectionMode === 'line' && selectedHunks.has(hunk); - const isSelected = this.props.isActive && (this.props.selectionMode === 'hunk') && selectedHunks.has(hunk); + const isSelected = (this.props.selectionMode === 'hunk') && selectedHunks.has(hunk); let buttonSuffix = ''; if (containsSelection) { @@ -514,9 +511,6 @@ export default class FilePatchView extends React.Component { if (ranges.length === 0) { return null; } - if (!this.props.isActive) { - return null; - } const holder = refHolder || new RefHolder(); return ( From a87b3a48564d58825736ae12c09ecfef48851502 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 6 Nov 2018 13:22:14 -0800 Subject: [PATCH 0838/4053] handle case where template is unset --- lib/models/repository-states/present.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 7cd9e196c3..76bb0fd791 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -196,7 +196,10 @@ export default class Present extends State { if (filePathEndsWith(event.path, '.git', 'config')) { // this won't catch changes made to the template file itself... - const template = await this.fetchCommitMessageTemplate(); + let template = await this.fetchCommitMessageTemplate(); + if (template === null) { + template = ''; + } if (this.commitMessageTemplate !== template) { this.setCommitMessageTemplate(template); this.setCommitMessage(template); From ae3e09875fda0315ba4c07e210d46f7d848a6302 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 6 Nov 2018 22:25:51 +0100 Subject: [PATCH 0839/4053] rename: - `FilePatchController` to `MultiFilePatchController` - `FilePatchView` to `MultiFilePatchView` and getting rid of the old `MultiFilePatchController` --- lib/containers/changed-file-container.js | 4 +- lib/containers/commit-preview-container.js | 4 +- lib/controllers/file-patch-controller.js | 232 --------- .../multi-file-patch-controller.js | 244 +++++++-- lib/helpers.js | 4 +- ...patch-view.js => multi-file-patch-view.js} | 2 +- .../controllers/file-patch-controller.test.js | 441 ---------------- .../multi-file-patch-controller.test.js | 486 +++++++++++++++--- test/views/file-patch-view.test.js | 6 +- 9 files changed, 639 insertions(+), 784 deletions(-) delete mode 100644 lib/controllers/file-patch-controller.js rename lib/views/{file-patch-view.js => multi-file-patch-view.js} (99%) delete mode 100644 test/controllers/file-patch-controller.test.js diff --git a/lib/containers/changed-file-container.js b/lib/containers/changed-file-container.js index da4d819382..30c9a245e4 100644 --- a/lib/containers/changed-file-container.js +++ b/lib/containers/changed-file-container.js @@ -5,7 +5,7 @@ import yubikiri from 'yubikiri'; import {autobind} from '../helpers'; import ObserveModel from '../views/observe-model'; import LoadingView from '../views/loading-view'; -import FilePatchController from '../controllers/file-patch-controller'; +import MultiFilePatchController from '../controllers/multi-file-patch-controller'; export default class ChangedFileContainer extends React.Component { static propTypes = { @@ -53,7 +53,7 @@ export default class ChangedFileContainer extends React.Component { } return ( - { - this.resolvePatchChangePromise = resolve; - }); - } - - componentDidUpdate(prevProps) { - if (prevProps.multiFilePatch !== this.props.multiFilePatch) { - this.resolvePatchChangePromise(); - this.patchChangePromise = new Promise(resolve => { - this.resolvePatchChangePromise = resolve; - }); - } - } - - render() { - return ( - - ); - } - - undoLastDiscard(filePatch, {eventSource} = {}) { - addEvent('undo-last-discard', { - package: 'github', - component: 'FilePatchController', - eventSource, - }); - - return this.props.undoLastDiscard(filePatch.getPath(), this.props.repository); - } - - diveIntoMirrorPatch(filePatch) { - const mirrorStatus = this.withStagingStatus({staged: 'unstaged', unstaged: 'staged'}); - const workingDirectory = this.props.repository.getWorkingDirectoryPath(); - const uri = ChangedFileItem.buildURI(filePatch.getPath(), workingDirectory, mirrorStatus); - - this.props.destroy(); - return this.props.workspace.open(uri); - } - - surfaceFile(filePatch) { - return this.props.surfaceFileAtPath(filePatch.getPath(), this.props.stagingStatus); - } - - async openFile(filePatch, positions) { - const absolutePath = path.join(this.props.repository.getWorkingDirectoryPath(), filePatch.getPath()); - const editor = await this.props.workspace.open(absolutePath, {pending: true}); - if (positions.length > 0) { - editor.setCursorBufferPosition(positions[0], {autoscroll: false}); - for (const position of positions.slice(1)) { - editor.addCursorAtBufferPosition(position); - } - editor.scrollToBufferPosition(positions[positions.length - 1], {center: true}); - } - return editor; - } - - toggleFile(filePatch) { - return this.stagingOperation(() => { - const methodName = this.withStagingStatus({staged: 'unstageFiles', unstaged: 'stageFiles'}); - return this.props.repository[methodName]([filePatch.getPath()]); - }); - } - - async toggleRows(rowSet, nextSelectionMode) { - let chosenRows = rowSet; - if (chosenRows) { - await this.selectedRowsChanged(chosenRows, nextSelectionMode); - } else { - chosenRows = this.state.selectedRows; - } - - if (chosenRows.size === 0) { - return Promise.resolve(); - } - - return this.stagingOperation(() => { - const patch = this.withStagingStatus({ - staged: () => this.props.multiFilePatch.getUnstagePatchForLines(chosenRows), - unstaged: () => this.props.multiFilePatch.getStagePatchForLines(chosenRows), - }); - return this.props.repository.applyPatchToIndex(patch); - }); - } - - toggleModeChange(filePatch) { - return this.stagingOperation(() => { - const targetMode = this.withStagingStatus({ - unstaged: filePatch.getNewMode(), - staged: filePatch.getOldMode(), - }); - return this.props.repository.stageFileModeChange(filePatch.getPath(), targetMode); - }); - } - - toggleSymlinkChange(filePatch) { - return this.stagingOperation(() => { - const relPath = filePatch.getPath(); - const repository = this.props.repository; - return this.withStagingStatus({ - unstaged: () => { - if (filePatch.hasTypechange() && filePatch.getStatus() === 'added') { - return repository.stageFileSymlinkChange(relPath); - } - - return repository.stageFiles([relPath]); - }, - staged: () => { - if (filePatch.hasTypechange() && filePatch.getStatus() === 'deleted') { - return repository.stageFileSymlinkChange(relPath); - } - - return repository.unstageFiles([relPath]); - }, - }); - }); - } - - async discardRows(rowSet, nextSelectionMode, {eventSource} = {}) { - let chosenRows = rowSet; - if (chosenRows) { - await this.selectedRowsChanged(chosenRows, nextSelectionMode); - } else { - chosenRows = this.state.selectedRows; - } - - addEvent('discard-unstaged-changes', { - package: 'github', - component: 'FilePatchController', - lineCount: chosenRows.size, - eventSource, - }); - - return this.props.discardLines(this.props.multiFilePatch, chosenRows, this.props.repository); - } - - selectedRowsChanged(rows, nextSelectionMode) { - if (equalSets(this.state.selectedRows, rows) && this.state.selectionMode === nextSelectionMode) { - return Promise.resolve(); - } - - return new Promise(resolve => { - this.setState({selectedRows: rows, selectionMode: nextSelectionMode}, resolve); - }); - } - - withStagingStatus(callbacks) { - const callback = callbacks[this.props.stagingStatus]; - /* istanbul ignore if */ - if (!callback) { - throw new Error(`Unknown staging status: ${this.props.stagingStatus}`); - } - return callback instanceof Function ? callback() : callback; - } - - stagingOperation(fn) { - if (this.stagingOperationInProgress) { - return null; - } - this.stagingOperationInProgress = true; - this.patchChangePromise.then(() => { - this.stagingOperationInProgress = false; - }); - - return fn(); - } -} diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index a1ad50a251..ebcd8171ab 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -1,52 +1,232 @@ -import React, {Fragment} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; +import path from 'path'; -import {MultiFilePatchPropType, RefHolderPropType} from '../prop-types'; -import FilePatchController from '../controllers/file-patch-controller'; -import {autobind} from '../helpers'; +import {autobind, equalSets} from '../helpers'; +import {addEvent} from '../reporter-proxy'; +import {MultiFilePatchPropType} from '../prop-types'; +import ChangedFileItem from '../items/changed-file-item'; +import MultiFilePatchView from '../views/multi-file-patch-view'; export default class MultiFilePatchController extends React.Component { static propTypes = { + repository: PropTypes.object.isRequired, + stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), multiFilePatch: MultiFilePatchPropType.isRequired, - refInitialFocus: RefHolderPropType, + hasUndoHistory: PropTypes.bool, + + workspace: PropTypes.object.isRequired, + commands: PropTypes.object.isRequired, + keymaps: PropTypes.object.isRequired, + tooltips: PropTypes.object.isRequired, + config: PropTypes.object.isRequired, + + destroy: PropTypes.func.isRequired, + discardLines: PropTypes.func, + undoLastDiscard: PropTypes.func, + surfaceFileAtPath: PropTypes.func, + handleClick: PropTypes.func, } constructor(props) { super(props); - autobind(this, 'handleMouseDown'); - const firstFilePatch = this.props.multiFilePatch.getFilePatches()[0]; + autobind( + this, + 'selectedRowsChanged', + 'undoLastDiscard', 'diveIntoMirrorPatch', 'surfaceFile', 'openFile', + 'toggleFile', 'toggleRows', 'toggleModeChange', 'toggleSymlinkChange', 'discardRows', + ); - this.state = {activeFilePatch: firstFilePatch ? firstFilePatch.getPath() : null}; + this.state = { + lastMultiFilePatch: this.props.multiFilePatch, + selectionMode: 'hunk', + selectedRows: new Set(), + }; + + this.mouseSelectionInProgress = false; + this.stagingOperationInProgress = false; + + this.patchChangePromise = new Promise(resolve => { + this.resolvePatchChangePromise = resolve; + }); } - handleMouseDown(relPath) { - this.setState({activeFilePatch: relPath}); + componentDidUpdate(prevProps) { + if (prevProps.multiFilePatch !== this.props.multiFilePatch) { + this.resolvePatchChangePromise(); + this.patchChangePromise = new Promise(resolve => { + this.resolvePatchChangePromise = resolve; + }); + } } render() { return ( - - {this.props.multiFilePatch.getFilePatches().map(filePatch => { - const relPath = filePatch.getPath(); - const isActive = this.state.activeFilePatch === relPath; - let props = this.props; - if (!isActive) { - props = {...props}; - delete props.refInitialFocus; - } + - ); - })} - + selectedRows={this.state.selectedRows} + selectionMode={this.state.selectionMode} + selectedRowsChanged={this.selectedRowsChanged} + + diveIntoMirrorPatch={this.diveIntoMirrorPatch} + surfaceFile={this.surfaceFile} + openFile={this.openFile} + toggleFile={this.toggleFile} + toggleRows={this.toggleRows} + toggleModeChange={this.toggleModeChange} + toggleSymlinkChange={this.toggleSymlinkChange} + undoLastDiscard={this.undoLastDiscard} + discardRows={this.discardRows} + selectNextHunk={this.selectNextHunk} + selectPreviousHunk={this.selectPreviousHunk} + /> ); } + + undoLastDiscard(filePatch, {eventSource} = {}) { + addEvent('undo-last-discard', { + package: 'github', + component: 'FilePatchController', + eventSource, + }); + + return this.props.undoLastDiscard(filePatch.getPath(), this.props.repository); + } + + diveIntoMirrorPatch(filePatch) { + const mirrorStatus = this.withStagingStatus({staged: 'unstaged', unstaged: 'staged'}); + const workingDirectory = this.props.repository.getWorkingDirectoryPath(); + const uri = ChangedFileItem.buildURI(filePatch.getPath(), workingDirectory, mirrorStatus); + + this.props.destroy(); + return this.props.workspace.open(uri); + } + + surfaceFile(filePatch) { + return this.props.surfaceFileAtPath(filePatch.getPath(), this.props.stagingStatus); + } + + async openFile(filePatch, positions) { + const absolutePath = path.join(this.props.repository.getWorkingDirectoryPath(), filePatch.getPath()); + const editor = await this.props.workspace.open(absolutePath, {pending: true}); + if (positions.length > 0) { + editor.setCursorBufferPosition(positions[0], {autoscroll: false}); + for (const position of positions.slice(1)) { + editor.addCursorAtBufferPosition(position); + } + editor.scrollToBufferPosition(positions[positions.length - 1], {center: true}); + } + return editor; + } + + toggleFile(filePatch) { + return this.stagingOperation(() => { + const methodName = this.withStagingStatus({staged: 'unstageFiles', unstaged: 'stageFiles'}); + return this.props.repository[methodName]([filePatch.getPath()]); + }); + } + + async toggleRows(rowSet, nextSelectionMode) { + let chosenRows = rowSet; + if (chosenRows) { + await this.selectedRowsChanged(chosenRows, nextSelectionMode); + } else { + chosenRows = this.state.selectedRows; + } + + if (chosenRows.size === 0) { + return Promise.resolve(); + } + + return this.stagingOperation(() => { + const patch = this.withStagingStatus({ + staged: () => this.props.multiFilePatch.getUnstagePatchForLines(chosenRows), + unstaged: () => this.props.multiFilePatch.getStagePatchForLines(chosenRows), + }); + return this.props.repository.applyPatchToIndex(patch); + }); + } + + toggleModeChange(filePatch) { + return this.stagingOperation(() => { + const targetMode = this.withStagingStatus({ + unstaged: filePatch.getNewMode(), + staged: filePatch.getOldMode(), + }); + return this.props.repository.stageFileModeChange(filePatch.getPath(), targetMode); + }); + } + + toggleSymlinkChange(filePatch) { + return this.stagingOperation(() => { + const relPath = filePatch.getPath(); + const repository = this.props.repository; + return this.withStagingStatus({ + unstaged: () => { + if (filePatch.hasTypechange() && filePatch.getStatus() === 'added') { + return repository.stageFileSymlinkChange(relPath); + } + + return repository.stageFiles([relPath]); + }, + staged: () => { + if (filePatch.hasTypechange() && filePatch.getStatus() === 'deleted') { + return repository.stageFileSymlinkChange(relPath); + } + + return repository.unstageFiles([relPath]); + }, + }); + }); + } + + async discardRows(rowSet, nextSelectionMode, {eventSource} = {}) { + let chosenRows = rowSet; + if (chosenRows) { + await this.selectedRowsChanged(chosenRows, nextSelectionMode); + } else { + chosenRows = this.state.selectedRows; + } + + addEvent('discard-unstaged-changes', { + package: 'github', + component: 'FilePatchController', + lineCount: chosenRows.size, + eventSource, + }); + + return this.props.discardLines(this.props.multiFilePatch, chosenRows, this.props.repository); + } + + selectedRowsChanged(rows, nextSelectionMode) { + if (equalSets(this.state.selectedRows, rows) && this.state.selectionMode === nextSelectionMode) { + return Promise.resolve(); + } + + return new Promise(resolve => { + this.setState({selectedRows: rows, selectionMode: nextSelectionMode}, resolve); + }); + } + + withStagingStatus(callbacks) { + const callback = callbacks[this.props.stagingStatus]; + /* istanbul ignore if */ + if (!callback) { + throw new Error(`Unknown staging status: ${this.props.stagingStatus}`); + } + return callback instanceof Function ? callback() : callback; + } + + stagingOperation(fn) { + if (this.stagingOperationInProgress) { + return null; + } + this.stagingOperationInProgress = true; + this.patchChangePromise.then(() => { + this.stagingOperationInProgress = false; + }); + + return fn(); + } } diff --git a/lib/helpers.js b/lib/helpers.js index 37f1854de1..5acc2f6cd5 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -3,7 +3,7 @@ import fs from 'fs-extra'; import os from 'os'; import temp from 'temp'; -import FilePatchController from './controllers/file-patch-controller'; +import MultiFilePatchController from './controllers/multi-file-patch-controller'; import RefHolder from './models/ref-holder'; export const LINE_ENDING_REGEX = /\r?\n/; @@ -374,7 +374,7 @@ export function getCommitMessageEditors(repository, workspace) { export function getFilePatchPaneItems({onlyStaged, empty} = {}, workspace) { return workspace.getPaneItems().filter(item => { - const isFilePatchItem = item && item.getRealItem && item.getRealItem() instanceof FilePatchController; + const isFilePatchItem = item && item.getRealItem && item.getRealItem() instanceof MultiFilePatchController; if (onlyStaged) { return isFilePatchItem && item.stagingStatus === 'staged'; } else if (empty) { diff --git a/lib/views/file-patch-view.js b/lib/views/multi-file-patch-view.js similarity index 99% rename from lib/views/file-patch-view.js rename to lib/views/multi-file-patch-view.js index 3f6bdc9885..670acf49c8 100644 --- a/lib/views/file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -26,7 +26,7 @@ const NBSP_CHARACTER = '\u00a0'; const BLANK_LABEL = () => NBSP_CHARACTER; -export default class FilePatchView extends React.Component { +export default class MultiFilePatchView extends React.Component { static propTypes = { stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, isPartiallyStaged: PropTypes.bool, diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js deleted file mode 100644 index b5177297e0..0000000000 --- a/test/controllers/file-patch-controller.test.js +++ /dev/null @@ -1,441 +0,0 @@ -import path from 'path'; -import fs from 'fs-extra'; -import React from 'react'; -import {shallow} from 'enzyme'; - -import FilePatchController from '../../lib/controllers/file-patch-controller'; -import * as reporterProxy from '../../lib/reporter-proxy'; -import {cloneRepository, buildRepository} from '../helpers'; - -describe('FilePatchController', function() { - let atomEnv, repository, filePatch; - - beforeEach(async function() { - atomEnv = global.buildAtomEnvironment(); - - const workdirPath = await cloneRepository(); - repository = await buildRepository(workdirPath); - - // a.txt: unstaged changes - await fs.writeFile(path.join(workdirPath, 'a.txt'), '00\n01\n02\n03\n04\n05\n06'); - - filePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); - }); - - afterEach(function() { - atomEnv.destroy(); - }); - - function buildApp(overrideProps = {}) { - const props = { - repository, - stagingStatus: 'unstaged', - relPath: 'a.txt', - isPartiallyStaged: false, - filePatch, - hasUndoHistory: false, - workspace: atomEnv.workspace, - commands: atomEnv.commands, - keymaps: atomEnv.keymaps, - tooltips: atomEnv.tooltips, - config: atomEnv.config, - destroy: () => {}, - discardLines: () => {}, - undoLastDiscard: () => {}, - surfaceFileAtPath: () => {}, - ...overrideProps, - }; - - return ; - } - - it('passes extra props to the FilePatchView', function() { - const extra = Symbol('extra'); - const wrapper = shallow(buildApp({extra})); - - assert.strictEqual(wrapper.find('FilePatchView').prop('extra'), extra); - }); - - it('calls undoLastDiscard through with set arguments', function() { - const undoLastDiscard = sinon.spy(); - const wrapper = shallow(buildApp({relPath: 'b.txt', undoLastDiscard})); - wrapper.find('FilePatchView').prop('undoLastDiscard')(); - - assert.isTrue(undoLastDiscard.calledWith('b.txt', repository)); - }); - - it('calls surfaceFileAtPath with set arguments', function() { - const surfaceFileAtPath = sinon.spy(); - const wrapper = shallow(buildApp({relPath: 'c.txt', surfaceFileAtPath})); - wrapper.find('FilePatchView').prop('surfaceFile')(); - - assert.isTrue(surfaceFileAtPath.calledWith('c.txt', 'unstaged')); - }); - - describe('diveIntoMirrorPatch()', function() { - it('destroys the current pane and opens the staged changes', async function() { - const destroy = sinon.spy(); - sinon.stub(atomEnv.workspace, 'open').resolves(); - const wrapper = shallow(buildApp({relPath: 'c.txt', stagingStatus: 'unstaged', destroy})); - - await wrapper.find('FilePatchView').prop('diveIntoMirrorPatch')(); - - assert.isTrue(destroy.called); - assert.isTrue(atomEnv.workspace.open.calledWith( - 'atom-github://file-patch/c.txt' + - `?workdir=${encodeURIComponent(repository.getWorkingDirectoryPath())}&stagingStatus=staged`, - )); - }); - - it('destroys the current pane and opens the unstaged changes', async function() { - const destroy = sinon.spy(); - sinon.stub(atomEnv.workspace, 'open').resolves(); - const wrapper = shallow(buildApp({relPath: 'd.txt', stagingStatus: 'staged', destroy})); - - await wrapper.find('FilePatchView').prop('diveIntoMirrorPatch')(); - - assert.isTrue(destroy.called); - assert.isTrue(atomEnv.workspace.open.calledWith( - 'atom-github://file-patch/d.txt' + - `?workdir=${encodeURIComponent(repository.getWorkingDirectoryPath())}&stagingStatus=unstaged`, - )); - }); - }); - - describe('openFile()', function() { - it('opens an editor on the current file', async function() { - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - const editor = await wrapper.find('FilePatchView').prop('openFile')([]); - - assert.strictEqual(editor.getPath(), path.join(repository.getWorkingDirectoryPath(), 'a.txt')); - }); - - it('sets the cursor to a single position', async function() { - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - const editor = await wrapper.find('FilePatchView').prop('openFile')([[1, 1]]); - - assert.deepEqual(editor.getCursorBufferPositions().map(p => p.serialize()), [[1, 1]]); - }); - - it('adds cursors at a set of positions', async function() { - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - const editor = await wrapper.find('FilePatchView').prop('openFile')([[1, 1], [3, 1], [5, 0]]); - - assert.deepEqual(editor.getCursorBufferPositions().map(p => p.serialize()), [[1, 1], [3, 1], [5, 0]]); - }); - }); - - describe('toggleFile()', function() { - it('stages the current file if unstaged', async function() { - sinon.spy(repository, 'stageFiles'); - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - - await wrapper.find('FilePatchView').prop('toggleFile')(); - - assert.isTrue(repository.stageFiles.calledWith(['a.txt'])); - }); - - it('unstages the current file if staged', async function() { - sinon.spy(repository, 'unstageFiles'); - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'staged'})); - - await wrapper.find('FilePatchView').prop('toggleFile')(); - - assert.isTrue(repository.unstageFiles.calledWith(['a.txt'])); - }); - - it('is a no-op if a staging operation is already in progress', async function() { - sinon.stub(repository, 'stageFiles').resolves('staged'); - sinon.stub(repository, 'unstageFiles').resolves('unstaged'); - - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - assert.strictEqual(await wrapper.find('FilePatchView').prop('toggleFile')(), 'staged'); - - wrapper.setProps({stagingStatus: 'staged'}); - assert.isNull(await wrapper.find('FilePatchView').prop('toggleFile')()); - - const promise = wrapper.instance().patchChangePromise; - wrapper.setProps({filePatch: filePatch.clone()}); - await promise; - - assert.strictEqual(await wrapper.find('FilePatchView').prop('toggleFile')(), 'unstaged'); - }); - }); - - describe('selected row and selection mode tracking', function() { - it('captures the selected row set', function() { - const wrapper = shallow(buildApp()); - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); - - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'line'); - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); - }); - - it('does not re-render if the row set and selection mode are unchanged', function() { - const wrapper = shallow(buildApp()); - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); - - sinon.spy(wrapper.instance(), 'render'); - - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'line'); - - assert.isTrue(wrapper.instance().render.called); - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); - - wrapper.instance().render.resetHistory(); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([2, 1]), 'line'); - - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); - assert.isFalse(wrapper.instance().render.called); - - wrapper.instance().render.resetHistory(); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'hunk'); - - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); - assert.isTrue(wrapper.instance().render.called); - }); - - describe('discardLines()', function() { - it('records an event', async function() { - const wrapper = shallow(buildApp()); - sinon.stub(reporterProxy, 'addEvent'); - await wrapper.find('FilePatchView').prop('discardRows')(new Set([1, 2])); - assert.isTrue(reporterProxy.addEvent.calledWith('discard-unstaged-changes', { - package: 'github', - component: 'FilePatchController', - lineCount: 2, - eventSource: undefined, - })); - }); - }); - - describe('undoLastDiscard()', function() { - it('records an event', function() { - const wrapper = shallow(buildApp()); - sinon.stub(reporterProxy, 'addEvent'); - wrapper.find('FilePatchView').prop('undoLastDiscard')(); - assert.isTrue(reporterProxy.addEvent.calledWith('undo-last-discard', { - package: 'github', - component: 'FilePatchController', - eventSource: undefined, - })); - }); - }); - }); - - describe('toggleRows()', function() { - it('is a no-op with no selected rows', async function() { - const wrapper = shallow(buildApp()); - - sinon.spy(repository, 'applyPatchToIndex'); - - await wrapper.find('FilePatchView').prop('toggleRows')(); - assert.isFalse(repository.applyPatchToIndex.called); - }); - - it('applies a stage patch to the index', async function() { - const wrapper = shallow(buildApp()); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1])); - - sinon.spy(filePatch, 'getStagePatchForLines'); - sinon.spy(repository, 'applyPatchToIndex'); - - await wrapper.find('FilePatchView').prop('toggleRows')(); - - assert.sameMembers(Array.from(filePatch.getStagePatchForLines.lastCall.args[0]), [1]); - assert.isTrue(repository.applyPatchToIndex.calledWith(filePatch.getStagePatchForLines.returnValues[0])); - }); - - it('toggles a different row set if provided', async function() { - const wrapper = shallow(buildApp()); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1]), 'line'); - - sinon.spy(filePatch, 'getStagePatchForLines'); - sinon.spy(repository, 'applyPatchToIndex'); - - await wrapper.find('FilePatchView').prop('toggleRows')(new Set([2]), 'hunk'); - - assert.sameMembers(Array.from(filePatch.getStagePatchForLines.lastCall.args[0]), [2]); - assert.isTrue(repository.applyPatchToIndex.calledWith(filePatch.getStagePatchForLines.returnValues[0])); - - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [2]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); - }); - - it('applies an unstage patch to the index', async function() { - await repository.stageFiles(['a.txt']); - const otherPatch = await repository.getFilePatchForPath('a.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: otherPatch, stagingStatus: 'staged'})); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([2])); - - sinon.spy(otherPatch, 'getUnstagePatchForLines'); - sinon.spy(repository, 'applyPatchToIndex'); - - await wrapper.find('FilePatchView').prop('toggleRows')(); - - assert.sameMembers(Array.from(otherPatch.getUnstagePatchForLines.lastCall.args[0]), [2]); - assert.isTrue(repository.applyPatchToIndex.calledWith(otherPatch.getUnstagePatchForLines.returnValues[0])); - }); - }); - - if (process.platform !== 'win32') { - describe('toggleModeChange()', function() { - it("it stages an unstaged file's new mode", async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); - await fs.chmod(p, 0o755); - repository.refresh(); - const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); - - const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'unstaged'})); - - sinon.spy(repository, 'stageFileModeChange'); - await wrapper.find('FilePatchView').prop('toggleModeChange')(); - - assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100755')); - }); - - it("it stages a staged file's old mode", async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); - await fs.chmod(p, 0o755); - await repository.stageFiles(['a.txt']); - repository.refresh(); - const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: true}); - - const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'staged'})); - - sinon.spy(repository, 'stageFileModeChange'); - await wrapper.find('FilePatchView').prop('toggleModeChange')(); - - assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100644')); - }); - }); - - describe('toggleSymlinkChange', function() { - it('handles an addition and typechange with a special repository method', async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.symlink(dest, p); - - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); - - await fs.unlink(p); - await fs.writeFile(p, 'fdsa\n', 'utf8'); - - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); - - sinon.spy(repository, 'stageFileSymlinkChange'); - - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - - assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); - }); - - it('stages non-addition typechanges normally', async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.symlink(dest, p); - - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); - - await fs.unlink(p); - - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); - - sinon.spy(repository, 'stageFiles'); - - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - - assert.isTrue(repository.stageFiles.calledWith(['waslink.txt'])); - }); - - it('handles a deletion and typechange with a special repository method', async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.writeFile(p, 'fdsa\n', 'utf8'); - - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); - - await fs.unlink(p); - await fs.symlink(dest, p); - await repository.stageFiles(['waslink.txt']); - - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); - - sinon.spy(repository, 'stageFileSymlinkChange'); - - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - - assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); - }); - - it('unstages non-deletion typechanges normally', async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.symlink(dest, p); - - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); - - await fs.unlink(p); - - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); - - sinon.spy(repository, 'unstageFiles'); - - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - - assert.isTrue(repository.unstageFiles.calledWith(['waslink.txt'])); - }); - }); - } - - it('calls discardLines with selected rows', async function() { - const discardLines = sinon.spy(); - const wrapper = shallow(buildApp({discardLines})); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); - - await wrapper.find('FilePatchView').prop('discardRows')(); - - const lastArgs = discardLines.lastCall.args; - assert.strictEqual(lastArgs[0], filePatch); - assert.sameMembers(Array.from(lastArgs[1]), [1, 2]); - assert.strictEqual(lastArgs[2], repository); - }); - - it('calls discardLines with explicitly provided rows', async function() { - const discardLines = sinon.spy(); - const wrapper = shallow(buildApp({discardLines})); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); - - await wrapper.find('FilePatchView').prop('discardRows')(new Set([4, 5]), 'hunk'); - - const lastArgs = discardLines.lastCall.args; - assert.strictEqual(lastArgs[0], filePatch); - assert.sameMembers(Array.from(lastArgs[1]), [4, 5]); - assert.strictEqual(lastArgs[2], repository); - - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [4, 5]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); - }); -}); diff --git a/test/controllers/multi-file-patch-controller.test.js b/test/controllers/multi-file-patch-controller.test.js index 858932cab7..7079614ddf 100644 --- a/test/controllers/multi-file-patch-controller.test.js +++ b/test/controllers/multi-file-patch-controller.test.js @@ -1,93 +1,441 @@ +import path from 'path'; +import fs from 'fs-extra'; import React from 'react'; import {shallow} from 'enzyme'; import MultiFilePatchController from '../../lib/controllers/multi-file-patch-controller'; -import {buildMultiFilePatch} from '../../lib/models/patch'; -import RefHolder from '../../lib/models/ref-holder'; - -describe('MultiFilePatchController', function() { - let multiFilePatch; - - beforeEach(function() { - multiFilePatch = buildMultiFilePatch([ - { - oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100755', status: 'modified', - hunks: [ - { - oldStartLine: 1, oldLineCount: 2, newStartLine: 1, newLineCount: 4, - lines: [' line-0', '+line-1', '+line-2', ' line-3'], - }, - ], - }, - { - oldPath: 'second', oldMode: '100644', newPath: 'second', newMode: '100644', status: 'modified', - hunks: [ - { - oldStartLine: 5, oldLineCount: 3, newStartLine: 5, newLineCount: 3, - lines: [' line-5', '+line-6', '-line-7', ' line-8'], - }, - ], - }, - { - oldPath: 'third', oldMode: '100755', newPath: 'third', newMode: '100755', status: 'added', - hunks: [ - { - oldStartLine: 1, oldLineCount: 0, newStartLine: 1, newLineCount: 3, - lines: ['+line-0', '+line-1', '+line-2'], - }, - ], - }, - ]); +import * as reporterProxy from '../../lib/reporter-proxy'; +import {cloneRepository, buildRepository} from '../helpers'; + +describe.only('MultiFilePatchController', function() { + let atomEnv, repository, filePatch; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + + const workdirPath = await cloneRepository(); + repository = await buildRepository(workdirPath); + + // a.txt: unstaged changes + await fs.writeFile(path.join(workdirPath, 'a.txt'), '00\n01\n02\n03\n04\n05\n06'); + + filePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); + }); + + afterEach(function() { + atomEnv.destroy(); }); - function buildApp(override = {}) { + function buildApp(overrideProps = {}) { const props = { - multiFilePatch, - ...override, + repository, + stagingStatus: 'unstaged', + relPath: 'a.txt', + isPartiallyStaged: false, + filePatch, + hasUndoHistory: false, + workspace: atomEnv.workspace, + commands: atomEnv.commands, + keymaps: atomEnv.keymaps, + tooltips: atomEnv.tooltips, + config: atomEnv.config, + destroy: () => {}, + discardLines: () => {}, + undoLastDiscard: () => {}, + surfaceFileAtPath: () => {}, + ...overrideProps, }; return ; } - it('renders a FilePatchController for each file patch', function() { - const wrapper = shallow(buildApp()); + it('passes extra props to the FilePatchView', function() { + const extra = Symbol('extra'); + const wrapper = shallow(buildApp({extra})); + + assert.strictEqual(wrapper.find('MultiFilePatchView').prop('extra'), extra); + }); - assert.lengthOf(wrapper.find('FilePatchController'), 3); + it('calls undoLastDiscard through with set arguments', function() { + const undoLastDiscard = sinon.spy(); + const wrapper = shallow(buildApp({relPath: 'b.txt', undoLastDiscard})); + wrapper.find('MultiFilePatchView').prop('undoLastDiscard')(); - // O(n^2) doesn't matter when n is small :stars: - assert.isTrue( - multiFilePatch.getFilePatches().every(fp => { - return wrapper - .find('FilePatchController') - .someWhere(w => w.prop('filePatch') === fp); - }), - ); + assert.isTrue(undoLastDiscard.calledWith('b.txt', repository)); }); - it('passes additional props to each controller', function() { - const extra = Symbol('hooray'); - const wrapper = shallow(buildApp({extra})); + it('calls surfaceFileAtPath with set arguments', function() { + const surfaceFileAtPath = sinon.spy(); + const wrapper = shallow(buildApp({relPath: 'c.txt', surfaceFileAtPath})); + wrapper.find('MultiFilePatchView').prop('surfaceFile')(); + + assert.isTrue(surfaceFileAtPath.calledWith('c.txt', 'unstaged')); + }); + + describe('diveIntoMirrorPatch()', function() { + it('destroys the current pane and opens the staged changes', async function() { + const destroy = sinon.spy(); + sinon.stub(atomEnv.workspace, 'open').resolves(); + const wrapper = shallow(buildApp({relPath: 'c.txt', stagingStatus: 'unstaged', destroy})); + + await wrapper.find('MultiFilePatchView').prop('diveIntoMirrorPatch')(); + + assert.isTrue(destroy.called); + assert.isTrue(atomEnv.workspace.open.calledWith( + 'atom-github://file-patch/c.txt' + + `?workdir=${encodeURIComponent(repository.getWorkingDirectoryPath())}&stagingStatus=staged`, + )); + }); + + it('destroys the current pane and opens the unstaged changes', async function() { + const destroy = sinon.spy(); + sinon.stub(atomEnv.workspace, 'open').resolves(); + const wrapper = shallow(buildApp({relPath: 'd.txt', stagingStatus: 'staged', destroy})); + + await wrapper.find('MultiFilePatchView').prop('diveIntoMirrorPatch')(); + + assert.isTrue(destroy.called); + assert.isTrue(atomEnv.workspace.open.calledWith( + 'atom-github://file-patch/d.txt' + + `?workdir=${encodeURIComponent(repository.getWorkingDirectoryPath())}&stagingStatus=unstaged`, + )); + }); + }); + + describe('openFile()', function() { + it('opens an editor on the current file', async function() { + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + const editor = await wrapper.find('MultiFilePatchView').prop('openFile')([]); + + assert.strictEqual(editor.getPath(), path.join(repository.getWorkingDirectoryPath(), 'a.txt')); + }); + + it('sets the cursor to a single position', async function() { + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + const editor = await wrapper.find('MultiFilePatchView').prop('openFile')([[1, 1]]); + + assert.deepEqual(editor.getCursorBufferPositions().map(p => p.serialize()), [[1, 1]]); + }); + + it('adds cursors at a set of positions', async function() { + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + const editor = await wrapper.find('MultiFilePatchView').prop('openFile')([[1, 1], [3, 1], [5, 0]]); + + assert.deepEqual(editor.getCursorBufferPositions().map(p => p.serialize()), [[1, 1], [3, 1], [5, 0]]); + }); + }); + + describe('toggleFile()', function() { + it('stages the current file if unstaged', async function() { + sinon.spy(repository, 'stageFiles'); + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + + await wrapper.find('MultiFilePatchView').prop('toggleFile')(); + + assert.isTrue(repository.stageFiles.calledWith(['a.txt'])); + }); + + it('unstages the current file if staged', async function() { + sinon.spy(repository, 'unstageFiles'); + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'staged'})); + + await wrapper.find('MultiFilePatchView').prop('toggleFile')(); + + assert.isTrue(repository.unstageFiles.calledWith(['a.txt'])); + }); + + it('is a no-op if a staging operation is already in progress', async function() { + sinon.stub(repository, 'stageFiles').resolves('staged'); + sinon.stub(repository, 'unstageFiles').resolves('unstaged'); + + const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + assert.strictEqual(await wrapper.find('MultiFilePatchView').prop('toggleFile')(), 'staged'); + + wrapper.setProps({stagingStatus: 'staged'}); + assert.isNull(await wrapper.find('MultiFilePatchView').prop('toggleFile')()); + + const promise = wrapper.instance().patchChangePromise; + wrapper.setProps({filePatch: filePatch.clone()}); + await promise; + + assert.strictEqual(await wrapper.find('MultiFilePatchView').prop('toggleFile')(), 'unstaged'); + }); + }); + + describe('selected row and selection mode tracking', function() { + it('captures the selected row set', function() { + const wrapper = shallow(buildApp()); + assert.sameMembers(Array.from(wrapper.find('MultiFilePatchView').prop('selectedRows')), []); + assert.strictEqual(wrapper.find('MultiFilePatchView').prop('selectionMode'), 'hunk'); + + wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'line'); + assert.sameMembers(Array.from(wrapper.find('MultiFilePatchView').prop('selectedRows')), [1, 2]); + assert.strictEqual(wrapper.find('MultiFilePatchView').prop('selectionMode'), 'line'); + }); + + it('does not re-render if the row set and selection mode are unchanged', function() { + const wrapper = shallow(buildApp()); + assert.sameMembers(Array.from(wrapper.find('MultiFilePatchView').prop('selectedRows')), []); + assert.strictEqual(wrapper.find('MultiFilePatchView').prop('selectionMode'), 'hunk'); + + sinon.spy(wrapper.instance(), 'render'); + + wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'line'); + + assert.isTrue(wrapper.instance().render.called); + assert.sameMembers(Array.from(wrapper.find('MultiFilePatchView').prop('selectedRows')), [1, 2]); + assert.strictEqual(wrapper.find('MultiFilePatchView').prop('selectionMode'), 'line'); + + wrapper.instance().render.resetHistory(); + wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([2, 1]), 'line'); + + assert.sameMembers(Array.from(wrapper.find('MultiFilePatchView').prop('selectedRows')), [1, 2]); + assert.strictEqual(wrapper.find('MultiFilePatchView').prop('selectionMode'), 'line'); + assert.isFalse(wrapper.instance().render.called); + + wrapper.instance().render.resetHistory(); + wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'hunk'); + + assert.sameMembers(Array.from(wrapper.find('MultiFilePatchView').prop('selectedRows')), [1, 2]); + assert.strictEqual(wrapper.find('MultiFilePatchView').prop('selectionMode'), 'hunk'); + assert.isTrue(wrapper.instance().render.called); + }); + + describe('discardLines()', function() { + it('records an event', async function() { + const wrapper = shallow(buildApp()); + sinon.stub(reporterProxy, 'addEvent'); + await wrapper.find('MultiFilePatchView').prop('discardRows')(new Set([1, 2])); + assert.isTrue(reporterProxy.addEvent.calledWith('discard-unstaged-changes', { + package: 'github', + component: 'MultiFilePatchController', + lineCount: 2, + eventSource: undefined, + })); + }); + }); + + describe('undoLastDiscard()', function() { + it('records an event', function() { + const wrapper = shallow(buildApp()); + sinon.stub(reporterProxy, 'addEvent'); + wrapper.find('MultiFilePatchView').prop('undoLastDiscard')(); + assert.isTrue(reporterProxy.addEvent.calledWith('undo-last-discard', { + package: 'github', + component: 'MultiFilePatchController', + eventSource: undefined, + })); + }); + }); + }); + + describe('toggleRows()', function() { + it('is a no-op with no selected rows', async function() { + const wrapper = shallow(buildApp()); + + sinon.spy(repository, 'applyPatchToIndex'); + + await wrapper.find('MultiFilePatchView').prop('toggleRows')(); + assert.isFalse(repository.applyPatchToIndex.called); + }); + + it('applies a stage patch to the index', async function() { + const wrapper = shallow(buildApp()); + wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([1])); + + sinon.spy(filePatch, 'getStagePatchForLines'); + sinon.spy(repository, 'applyPatchToIndex'); + + await wrapper.find('MultiFilePatchView').prop('toggleRows')(); + + assert.sameMembers(Array.from(filePatch.getStagePatchForLines.lastCall.args[0]), [1]); + assert.isTrue(repository.applyPatchToIndex.calledWith(filePatch.getStagePatchForLines.returnValues[0])); + }); + + it('toggles a different row set if provided', async function() { + const wrapper = shallow(buildApp()); + wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([1]), 'line'); + + sinon.spy(filePatch, 'getStagePatchForLines'); + sinon.spy(repository, 'applyPatchToIndex'); + + await wrapper.find('MultiFilePatchView').prop('toggleRows')(new Set([2]), 'hunk'); + + assert.sameMembers(Array.from(filePatch.getStagePatchForLines.lastCall.args[0]), [2]); + assert.isTrue(repository.applyPatchToIndex.calledWith(filePatch.getStagePatchForLines.returnValues[0])); + + assert.sameMembers(Array.from(wrapper.find('MultiFilePatchView').prop('selectedRows')), [2]); + assert.strictEqual(wrapper.find('MultiFilePatchView').prop('selectionMode'), 'hunk'); + }); + + it('applies an unstage patch to the index', async function() { + await repository.stageFiles(['a.txt']); + const otherPatch = await repository.getFilePatchForPath('a.txt', {staged: true}); + const wrapper = shallow(buildApp({filePatch: otherPatch, stagingStatus: 'staged'})); + wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([2])); + + sinon.spy(otherPatch, 'getUnstagePatchForLines'); + sinon.spy(repository, 'applyPatchToIndex'); + + await wrapper.find('MultiFilePatchView').prop('toggleRows')(); + + assert.sameMembers(Array.from(otherPatch.getUnstagePatchForLines.lastCall.args[0]), [2]); + assert.isTrue(repository.applyPatchToIndex.calledWith(otherPatch.getUnstagePatchForLines.returnValues[0])); + }); + }); + + if (process.platform !== 'win32') { + describe('toggleModeChange()', function() { + it("it stages an unstaged file's new mode", async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); + await fs.chmod(p, 0o755); + repository.refresh(); + const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); + + const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'unstaged'})); + + sinon.spy(repository, 'stageFileModeChange'); + await wrapper.find('MultiFilePatchView').prop('toggleModeChange')(); + + assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100755')); + }); + + it("it stages a staged file's old mode", async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); + await fs.chmod(p, 0o755); + await repository.stageFiles(['a.txt']); + repository.refresh(); + const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: true}); + + const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'staged'})); + + sinon.spy(repository, 'stageFileModeChange'); + await wrapper.find('MultiFilePatchView').prop('toggleModeChange')(); + + assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100644')); + }); + }); + + describe('toggleSymlinkChange', function() { + it('handles an addition and typechange with a special repository method', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.symlink(dest, p); + + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); + + await fs.unlink(p); + await fs.writeFile(p, 'fdsa\n', 'utf8'); + + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); + + sinon.spy(repository, 'stageFileSymlinkChange'); + + await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(); + + assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); + }); + + it('stages non-addition typechanges normally', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.symlink(dest, p); + + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); + + await fs.unlink(p); + + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); + + sinon.spy(repository, 'stageFiles'); + + await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(); + + assert.isTrue(repository.stageFiles.calledWith(['waslink.txt'])); + }); + + it('handles a deletion and typechange with a special repository method', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.writeFile(p, 'fdsa\n', 'utf8'); + + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); + + await fs.unlink(p); + await fs.symlink(dest, p); + await repository.stageFiles(['waslink.txt']); + + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); + + sinon.spy(repository, 'stageFileSymlinkChange'); + + await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(); + + assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); + }); + + it('unstages non-deletion typechanges normally', async function() { + const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); + const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); + await fs.writeFile(dest, 'asdf\n', 'utf8'); + await fs.symlink(dest, p); + + await repository.stageFiles(['waslink.txt', 'destination']); + await repository.commit('zero'); + + await fs.unlink(p); + + repository.refresh(); + const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); + const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); + + sinon.spy(repository, 'unstageFiles'); + + await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(); + + assert.isTrue(repository.unstageFiles.calledWith(['waslink.txt'])); + }); + }); + } + + it('calls discardLines with selected rows', async function() { + const discardLines = sinon.spy(); + const wrapper = shallow(buildApp({discardLines})); + wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); + + await wrapper.find('MultiFilePatchView').prop('discardRows')(); - assert.isTrue( - wrapper - .find('FilePatchController') - .everyWhere(w => w.prop('extra') === extra), - ); + const lastArgs = discardLines.lastCall.args; + assert.strictEqual(lastArgs[0], filePatch); + assert.sameMembers(Array.from(lastArgs[1]), [1, 2]); + assert.strictEqual(lastArgs[2], repository); }); - it('passes a refInitialFocus only to the active FilePatchController', function() { - const refInitialFocus = new RefHolder(); - const wrapper = shallow(buildApp({refInitialFocus})); + it('calls discardLines with explicitly provided rows', async function() { + const discardLines = sinon.spy(); + const wrapper = shallow(buildApp({discardLines})); + wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); - assert.strictEqual(wrapper.find('FilePatchController[relPath="first"]').prop('refInitialFocus'), refInitialFocus); - assert.notExists(wrapper.find('FilePatchController[relPath="second"]').prop('refInitialFocus')); - assert.notExists(wrapper.find('FilePatchController[relPath="third"]').prop('refInitialFocus')); + await wrapper.find('MultiFilePatchView').prop('discardRows')(new Set([4, 5]), 'hunk'); - wrapper.find('FilePatchController[relPath="second"]').prop('handleMouseDown')('second'); - wrapper.update(); + const lastArgs = discardLines.lastCall.args; + assert.strictEqual(lastArgs[0], filePatch); + assert.sameMembers(Array.from(lastArgs[1]), [4, 5]); + assert.strictEqual(lastArgs[2], repository); - assert.notExists(wrapper.find('FilePatchController[relPath="first"]').prop('refInitialFocus')); - assert.strictEqual(wrapper.find('FilePatchController[relPath="second"]').prop('refInitialFocus'), refInitialFocus); - assert.notExists(wrapper.find('FilePatchController[relPath="third"]').prop('refInitialFocus')); + assert.sameMembers(Array.from(wrapper.find('MultiFilePatchView').prop('selectedRows')), [4, 5]); + assert.strictEqual(wrapper.find('MultiFilePatchView').prop('selectionMode'), 'hunk'); }); }); diff --git a/test/views/file-patch-view.test.js b/test/views/file-patch-view.test.js index 4f8ca64ad4..de957868a7 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/file-patch-view.test.js @@ -2,13 +2,13 @@ import React from 'react'; import {shallow, mount} from 'enzyme'; import {cloneRepository, buildRepository} from '../helpers'; -import FilePatchView from '../../lib/views/file-patch-view'; +import MultiFilePatchView from '../../lib/views/multi-file-patch-view'; import {buildFilePatch} from '../../lib/models/patch'; import {nullFile} from '../../lib/models/patch/file'; import FilePatch from '../../lib/models/patch/file-patch'; import RefHolder from '../../lib/models/ref-holder'; -describe('FilePatchView', function() { +describe('MultiFilePatchView', function() { let atomEnv, workspace, repository, filePatch; beforeEach(async function() { @@ -77,7 +77,7 @@ describe('FilePatchView', function() { ...overrideProps, }; - return ; + return ; } it('renders the file header', function() { From 6f88f76b1bd86bdc0b9e44acbc4812798006dba5 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 6 Nov 2018 13:28:39 -0800 Subject: [PATCH 0840/4053] add unit test for unsetting commit template --- test/models/repository.test.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 1a60416bff..39294c7857 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -2105,6 +2105,30 @@ describe('Repository', function() { ); await assert.async.strictEqual(repository.getCommitMessage(), fs.readFileSync(templatePath, 'utf8')); }); + it('updates commit message to empty string if commit.template is unset', async function() { + const {repository, observer, subscriptions} = await wireUpObserver(); + sub = subscriptions; + await observer.start(); + + assert.strictEqual(repository.getCommitMessage(), ''); + + const templatePath = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); + await repository.git.setConfig('commit.template', templatePath); + await expectEvents( + repository, + path.join('.git', 'config'), + ); + + await assert.async.strictEqual(repository.getCommitMessage(), fs.readFileSync(templatePath, 'utf8')); + + await repository.git.unsetConfig('commit.template'); + + await expectEvents( + repository, + path.join('.git', 'config'), + ); + await assert.async.strictEqual(repository.getCommitMessage(), ''); + }); }); describe('merge events', function() { From 040e44981252603e3c8250333a7dbf721ecab8bc Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 6 Nov 2018 22:48:39 +0100 Subject: [PATCH 0841/4053] more renaming --- .../multi-file-patch-controller.test.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/controllers/multi-file-patch-controller.test.js b/test/controllers/multi-file-patch-controller.test.js index 7079614ddf..29b86f23ce 100644 --- a/test/controllers/multi-file-patch-controller.test.js +++ b/test/controllers/multi-file-patch-controller.test.js @@ -8,7 +8,7 @@ import * as reporterProxy from '../../lib/reporter-proxy'; import {cloneRepository, buildRepository} from '../helpers'; describe.only('MultiFilePatchController', function() { - let atomEnv, repository, filePatch; + let atomEnv, repository, multiFilePatch; beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); @@ -19,7 +19,7 @@ describe.only('MultiFilePatchController', function() { // a.txt: unstaged changes await fs.writeFile(path.join(workdirPath, 'a.txt'), '00\n01\n02\n03\n04\n05\n06'); - filePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); + multiFilePatch = await repository.getStagedChangesPatch(); }); afterEach(function() { @@ -32,7 +32,7 @@ describe.only('MultiFilePatchController', function() { stagingStatus: 'unstaged', relPath: 'a.txt', isPartiallyStaged: false, - filePatch, + multiFilePatch, hasUndoHistory: false, workspace: atomEnv.workspace, commands: atomEnv.commands, @@ -155,7 +155,7 @@ describe.only('MultiFilePatchController', function() { assert.isNull(await wrapper.find('MultiFilePatchView').prop('toggleFile')()); const promise = wrapper.instance().patchChangePromise; - wrapper.setProps({filePatch: filePatch.clone()}); + wrapper.setProps({multiFilePatch: multiFilePatch.clone()}); await promise; assert.strictEqual(await wrapper.find('MultiFilePatchView').prop('toggleFile')(), 'unstaged'); @@ -243,26 +243,26 @@ describe.only('MultiFilePatchController', function() { const wrapper = shallow(buildApp()); wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([1])); - sinon.spy(filePatch, 'getStagePatchForLines'); + sinon.spy(multiFilePatch, 'getStagePatchForLines'); sinon.spy(repository, 'applyPatchToIndex'); await wrapper.find('MultiFilePatchView').prop('toggleRows')(); - assert.sameMembers(Array.from(filePatch.getStagePatchForLines.lastCall.args[0]), [1]); - assert.isTrue(repository.applyPatchToIndex.calledWith(filePatch.getStagePatchForLines.returnValues[0])); + assert.sameMembers(Array.from(multiFilePatch.getStagePatchForLines.lastCall.args[0]), [1]); + assert.isTrue(repository.applyPatchToIndex.calledWith(multiFilePatch.getStagePatchForLines.returnValues[0])); }); it('toggles a different row set if provided', async function() { const wrapper = shallow(buildApp()); wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([1]), 'line'); - sinon.spy(filePatch, 'getStagePatchForLines'); + sinon.spy(multiFilePatch, 'getStagePatchForLines'); sinon.spy(repository, 'applyPatchToIndex'); await wrapper.find('MultiFilePatchView').prop('toggleRows')(new Set([2]), 'hunk'); - assert.sameMembers(Array.from(filePatch.getStagePatchForLines.lastCall.args[0]), [2]); - assert.isTrue(repository.applyPatchToIndex.calledWith(filePatch.getStagePatchForLines.returnValues[0])); + assert.sameMembers(Array.from(multiFilePatch.getStagePatchForLines.lastCall.args[0]), [2]); + assert.isTrue(repository.applyPatchToIndex.calledWith(multiFilePatch.getStagePatchForLines.returnValues[0])); assert.sameMembers(Array.from(wrapper.find('MultiFilePatchView').prop('selectedRows')), [2]); assert.strictEqual(wrapper.find('MultiFilePatchView').prop('selectionMode'), 'hunk'); @@ -418,7 +418,7 @@ describe.only('MultiFilePatchController', function() { await wrapper.find('MultiFilePatchView').prop('discardRows')(); const lastArgs = discardLines.lastCall.args; - assert.strictEqual(lastArgs[0], filePatch); + assert.strictEqual(lastArgs[0], multiFilePatch); assert.sameMembers(Array.from(lastArgs[1]), [1, 2]); assert.strictEqual(lastArgs[2], repository); }); @@ -431,7 +431,7 @@ describe.only('MultiFilePatchController', function() { await wrapper.find('MultiFilePatchView').prop('discardRows')(new Set([4, 5]), 'hunk'); const lastArgs = discardLines.lastCall.args; - assert.strictEqual(lastArgs[0], filePatch); + assert.strictEqual(lastArgs[0], multiFilePatch); assert.sameMembers(Array.from(lastArgs[1]), [4, 5]); assert.strictEqual(lastArgs[2], repository); From 4e400bde11987eb020a54e2e2a61856bd0d3f8b7 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 6 Nov 2018 14:52:43 -0800 Subject: [PATCH 0842/4053] address code review feedback. --- lib/models/repository-states/present.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 76bb0fd791..77c242c866 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -196,14 +196,13 @@ export default class Present extends State { if (filePathEndsWith(event.path, '.git', 'config')) { // this won't catch changes made to the template file itself... - let template = await this.fetchCommitMessageTemplate(); + const template = await this.fetchCommitMessageTemplate(); if (template === null) { - template = ''; - } - if (this.commitMessageTemplate !== template) { - this.setCommitMessageTemplate(template); + this.setCommitMessage(''); + } else if (this.commitMessageTemplate !== template) { this.setCommitMessage(template); } + this.setCommitMessageTemplate(template); } } } From 462150dfe61cdcdfe271490800f1ab441b17bdea Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 16:18:20 -0800 Subject: [PATCH 0843/4053] Assign default for params in MultiFilePatch constructor --- lib/models/patch/multi-file-patch.js | 3 ++- lib/models/repository-states/state.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index d406b35e44..61045efab1 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,7 +1,8 @@ import {TextBuffer} from 'atom'; export default class MultiFilePatch { - constructor(buffer, layers, filePatches) { + constructor(buffer = null, layers = {}, filePatches = []) { + this.buffer = buffer; this.patchLayer = layers.patch; diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 791b52174d..39f3bf598a 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -280,11 +280,11 @@ export default class State { } getChangedFilePatch() { - return Promise.resolve(new MultiFilePatch([])); + return Promise.resolve(new MultiFilePatch()); } getStagedChangesPatch() { - return Promise.resolve(new MultiFilePatch([])); + return Promise.resolve(new MultiFilePatch()); } readFileFromIndex(filePath) { From 7ae17364184f32c7d82878bc30571e47f3f0b813 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 16:19:51 -0800 Subject: [PATCH 0844/4053] Add methods to MultiFilePatch Added `anyPresent`, `didAnyChangeExecutableMode`, `anyHaveSymlink` --- lib/models/patch/multi-file-patch.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 61045efab1..c89eed6f00 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -25,6 +25,28 @@ export default class MultiFilePatch { } } + anyPresent() { + return this.buffer !== null; + } + + didAnyChangeExecutableMode() { + for (const filePatch of this.getFilePatches()) { + if (filePatch.didAnyChangeExecutableMode()) { + return true; + } + } + return false; + } + + anyHaveSymlink() { + for (const filePatch of this.getFilePatches()) { + if (filePatch.hasSymlink()) { + return true; + } + } + return false; + } + getBuffer() { return this.buffer; } From 95f17f5e26c6ca5f34efd3def6a96503c83350f9 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 16:25:34 -0800 Subject: [PATCH 0845/4053] Make `buildFilePatch` return a MultiFilePatch --- lib/models/patch/builder.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 9dbd146c57..e35b31da2e 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -8,15 +8,24 @@ import FilePatch from './file-patch'; import MultiFilePatch from './multi-file-patch'; export function buildFilePatch(diffs) { + const layeredBuffer = initializeBuffer(); + + let filePatch; if (diffs.length === 0) { - return emptyDiffFilePatch(); + filePatch = emptyDiffFilePatch(); } else if (diffs.length === 1) { - return singleDiffFilePatch(diffs[0]); + filePatch = singleDiffFilePatch(diffs[0], layeredBuffer); } else if (diffs.length === 2) { - return dualDiffFilePatch(...diffs); + filePatch = dualDiffFilePatch(diffs[0], diffs[1], layeredBuffer); } else { throw new Error(`Unexpected number of diffs: ${diffs.length}`); } + + const layers = { + patch: layeredBuffer.layers.patch, + hunk: layeredBuffer.layers.hunk, + }; + return new MultiFilePatch(layeredBuffer.buffer, layers, [filePatch]); } export function buildMultiFilePatch(diffs) { From 91f587c3bfeabb7799a2039d75a0c3745ba6f01b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 16:34:02 -0800 Subject: [PATCH 0846/4053] :fire: `getChagnedFilepath` hack --- lib/containers/changed-file-container.js | 2 +- lib/models/repository-states/present.js | 5 ----- lib/models/repository-states/state.js | 4 ---- lib/models/repository.js | 1 - 4 files changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/containers/changed-file-container.js b/lib/containers/changed-file-container.js index 30c9a245e4..eb6a8a7f8a 100644 --- a/lib/containers/changed-file-container.js +++ b/lib/containers/changed-file-container.js @@ -33,7 +33,7 @@ export default class ChangedFileContainer extends React.Component { const staged = this.props.stagingStatus === 'staged'; return yubikiri({ - multiFilePatch: repository.getChangedFilePatch(this.props.relPath, {staged}), + multiFilePatch: repository.getFilePatchForPath(this.props.relPath, {staged}), isPartiallyStaged: repository.isPartiallyStaged(this.props.relPath), hasUndoHistory: repository.hasDiscardHistory(this.props.relPath), }); diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 863c59a1e3..97ebccd24e 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -626,11 +626,6 @@ export default class Present extends State { return {stagedFiles, unstagedFiles, mergeConflictFiles}; } - // hack hack hack - async getChangedFilePatch(...args) { - return new MultiFilePatch([await this.getFilePatchForPath(...args)]); - } - getFilePatchForPath(filePath, {staged} = {staged: false}) { return this.cache.getOrSet(Keys.filePatch.oneWith(filePath, {staged}), async () => { const diffs = await this.git().getDiffsForFilePath(filePath, {staged}); diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 39f3bf598a..fe0ff5a4a9 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -279,10 +279,6 @@ export default class State { return Promise.resolve(FilePatch.createNull()); } - getChangedFilePatch() { - return Promise.resolve(new MultiFilePatch()); - } - getStagedChangesPatch() { return Promise.resolve(new MultiFilePatch()); } diff --git a/lib/models/repository.js b/lib/models/repository.js index 08970a981d..6801b95262 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -328,7 +328,6 @@ const delegates = [ 'getFilePatchForPath', 'getStagedChangesPatch', 'readFileFromIndex', - 'getChangedFilePatch', 'getLastCommit', 'getRecentCommits', From 118761345a16500ffe99c749230b96bbe523146d Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 16:44:35 -0800 Subject: [PATCH 0847/4053] Oops, call the right method `didChangeExecutableMode` --- lib/models/patch/multi-file-patch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index c89eed6f00..e4d30fda23 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -31,7 +31,7 @@ export default class MultiFilePatch { didAnyChangeExecutableMode() { for (const filePatch of this.getFilePatches()) { - if (filePatch.didAnyChangeExecutableMode()) { + if (filePatch.didChangeExecutableMode()) { return true; } } From 9d2becd754baf5fcfb68dfd33b7880a9bc6009a0 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 16:56:48 -0800 Subject: [PATCH 0848/4053] Make State#getFilePatchForPath return a MultiFilePatch instead of FilePatch --- lib/models/repository-states/state.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index fe0ff5a4a9..72c451e53d 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -276,7 +276,7 @@ export default class State { } getFilePatchForPath(filePath, options = {}) { - return Promise.resolve(FilePatch.createNull()); + return Promise.resolve(new MultiFilePatch()); } getStagedChangesPatch() { From db5ad10ce3bb5561dab268a8160b2e2190216bad Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 16:57:20 -0800 Subject: [PATCH 0849/4053] Pass all layers to MultiFilePatch in `buildFilePatch` --- lib/models/patch/builder.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index e35b31da2e..458aa9dbc4 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -21,11 +21,7 @@ export function buildFilePatch(diffs) { throw new Error(`Unexpected number of diffs: ${diffs.length}`); } - const layers = { - patch: layeredBuffer.layers.patch, - hunk: layeredBuffer.layers.hunk, - }; - return new MultiFilePatch(layeredBuffer.buffer, layers, [filePatch]); + return new MultiFilePatch(layeredBuffer.buffer, layeredBuffer.layers, [filePatch]); } export function buildMultiFilePatch(diffs) { From 0cda4425efe49d6950e9f91246778cf941bc7e86 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 17:11:29 -0800 Subject: [PATCH 0850/4053] this.multiFilePatch --> this.props.multiFilPatch --- lib/views/multi-file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 670acf49c8..600067d149 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -450,7 +450,7 @@ export default class MultiFilePatchView extends React.Component { renderHunkHeaders(filePatch) { const toggleVerb = this.props.stagingStatus === 'unstaged' ? 'Stage' : 'Unstage'; const selectedHunks = new Set( - Array.from(this.props.selectedRows, row => this.multiFilePatch.getHunkAt(row)), + Array.from(this.props.selectedRows, row => this.props.multiFilePatch.getHunkAt(row)), ); return ( From 6023fe778761296d0516ba39b07de520fde15026 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 17:11:51 -0800 Subject: [PATCH 0851/4053] Add MultiFilePatch#getMaxLineWidth --- lib/models/patch/multi-file-patch.js | 51 ++++++++++++++++------------ 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index e4d30fda23..730f0662d6 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -25,28 +25,6 @@ export default class MultiFilePatch { } } - anyPresent() { - return this.buffer !== null; - } - - didAnyChangeExecutableMode() { - for (const filePatch of this.getFilePatches()) { - if (filePatch.didChangeExecutableMode()) { - return true; - } - } - return false; - } - - anyHaveSymlink() { - for (const filePatch of this.getFilePatches()) { - if (filePatch.hasSymlink()) { - return true; - } - } - return false; - } - getBuffer() { return this.buffer; } @@ -204,6 +182,35 @@ export default class MultiFilePatch { return filePatches; } + anyPresent() { + return this.buffer !== null; + } + + didAnyChangeExecutableMode() { + for (const filePatch of this.getFilePatches()) { + if (filePatch.didChangeExecutableMode()) { + return true; + } + } + return false; + } + + anyHaveSymlink() { + for (const filePatch of this.getFilePatches()) { + if (filePatch.hasSymlink()) { + return true; + } + } + return false; + } + + getMaxLineNumberWidth() { + return this.getFilePatches().reduce((maxWidth, filePatch) => { + const width = filePatch.getMaxLineNumberWidth(); + return maxWidth >= width ? maxWidth : width; + }, 0); + } + /* * Construct an apply-able patch String. */ From f50d098588d37ab03a6b853d3085f44fc85a06b1 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 17:13:07 -0800 Subject: [PATCH 0852/4053] Pass all layers through to MultiFilePatch in `buildMultiFilePatch` --- lib/models/patch/builder.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 458aa9dbc4..9ab2eca8c1 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -60,11 +60,7 @@ export function buildMultiFilePatch(diffs) { const filePatches = actions.map(action => action()); - const layers = { - patch: layeredBuffer.layers.patch, - hunk: layeredBuffer.layers.hunk, - }; - return new MultiFilePatch(layeredBuffer.buffer, layers, filePatches); + return new MultiFilePatch(layeredBuffer.buffer, layeredBuffer.layers, filePatches); } function emptyDiffFilePatch() { From 843f1f80af8952dae62c31e3f2cbffc5abbb0cf0 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 6 Nov 2018 18:11:42 -0800 Subject: [PATCH 0853/4053] :fire: auto height now that we're in a single editor Co-Authored-By: Tilde Ann Thurium --- styles/commit-preview-view.less | 1 - 1 file changed, 1 deletion(-) diff --git a/styles/commit-preview-view.less b/styles/commit-preview-view.less index a0fa33e21b..aff47bd9a6 100644 --- a/styles/commit-preview-view.less +++ b/styles/commit-preview-view.less @@ -5,7 +5,6 @@ z-index: 1; // Fixes scrollbar on macOS .github-FilePatchView { - height: auto; border-bottom: 1px solid @base-border-color; &:last-child { From 381f26b4ec418c2b7fff7b4d2e65d9bc14167ba2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 08:27:52 -0500 Subject: [PATCH 0854/4053] Retain the buffer created from a builder --- lib/models/patch/builder.js | 2 ++ lib/models/patch/multi-file-patch.js | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 9ab2eca8c1..7e3c9c0e69 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -152,6 +152,8 @@ const CHANGEKIND = { function initializeBuffer() { const buffer = new TextBuffer(); + buffer.retain(); + const layers = ['patch', 'hunk', 'unchanged', 'addition', 'deletion', 'noNewline'].reduce((obj, key) => { obj[key] = buffer.addMarkerLayer(); return obj; diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 730f0662d6..de6dd978af 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -2,7 +2,6 @@ import {TextBuffer} from 'atom'; export default class MultiFilePatch { constructor(buffer = null, layers = {}, filePatches = []) { - this.buffer = buffer; this.patchLayer = layers.patch; From 1e0385b16bef827e3240a211da5ae98065c64e77 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 7 Nov 2018 15:13:48 +0100 Subject: [PATCH 0855/4053] don't pass multifilepatch to toggle rows --- lib/views/multi-file-patch-view.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 600067d149..2a1df7781c 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -601,7 +601,6 @@ export default class MultiFilePatchView extends React.Component { toggleHunkSelection(hunk, containsSelection) { if (containsSelection) { return this.props.toggleRows( - this.props.multiFilePatch, this.props.selectedRows, this.props.selectionMode, {eventSource: 'button'}, @@ -615,7 +614,6 @@ export default class MultiFilePatchView extends React.Component { }, []), ); return this.props.toggleRows( - this.props.multiFilePatch, changeRows, 'hunk', {eventSource: 'button'}, @@ -784,7 +782,7 @@ export default class MultiFilePatchView extends React.Component { } didConfirm() { - return this.props.toggleRows(this.props.multiFilePatch, this.props.selectedRows, this.props.selectionMode); + return this.props.toggleRows(this.props.selectedRows, this.props.selectionMode); } didToggleSelectionMode() { From fc198875dcd21e46dc98144f9847ac0c02c53b22 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 09:20:25 -0500 Subject: [PATCH 0856/4053] Move getNextSelectionRange() up to MultiFilePatch --- lib/models/patch/file-patch.js | 4 -- lib/models/patch/multi-file-patch.js | 60 ++++++++++++++++++++++++++++ lib/models/patch/patch.js | 51 ----------------------- 3 files changed, 60 insertions(+), 55 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 7d7199f0bc..f6aee78c62 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -255,10 +255,6 @@ export default class FilePatch { }; } - getNextSelectionRange(lastFilePatch, lastSelectedRows) { - return this.getPatch().getNextSelectionRange(lastFilePatch.getPatch(), lastSelectedRows); - } - isEqual(other) { if (!(other instanceof this.constructor)) { return false; } diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index de6dd978af..e2640424fb 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -96,6 +96,66 @@ export default class MultiFilePatch { return this.getUnstagePatchForLines(new Set(hunk.getBufferRows())); } + getNextSelectionRange(lastMultiFilePatch, lastSelectedRows) { + if (lastSelectedRows.size === 0) { + const [firstPatch] = this.getFilePatches(); + if (!firstPatch) { + return [[0, 0], [0, 0]]; + } + + return firstPatch.getFirstChangeRange(); + } + + const lastMax = Math.max(...lastSelectedRows); + + let lastSelectionIndex = 0; + for (const lastFilePatch of lastMultiFilePatch.getFilePatches()) { + for (const hunk of lastFilePatch.getHunks()) { + let includesMax = false; + let hunkSelectionOffset = 0; + + changeLoop: for (const change of hunk.getChanges()) { + for (const {intersection, gap} of change.intersectRows(lastSelectedRows, true)) { + // Only include a partial range if this intersection includes the last selected buffer row. + includesMax = intersection.intersectsRow(lastMax); + const delta = includesMax ? lastMax - intersection.start.row + 1 : intersection.getRowCount(); + + if (gap) { + // Range of unselected changes. + hunkSelectionOffset += delta; + } + + if (includesMax) { + break changeLoop; + } + } + } + + lastSelectionIndex += hunkSelectionOffset; + + if (includesMax) { + break; + } + } + } + + let newSelectionRow = 0; + patchLoop: for (const filePatch of this.getFilePatches()) { + for (const hunk of filePatch.getHunks()) { + for (const change of hunk.getChanges()) { + if (lastSelectionIndex < change.bufferRowCount()) { + newSelectionRow = change.getStartBufferRow() + lastSelectionIndex; + break patchLoop; + } else { + lastSelectionIndex -= change.bufferRowCount(); + } + } + } + } + + return [[newSelectionRow, 0], [newSelectionRow, Infinity]]; + } + adoptBufferFrom(lastMultiFilePatch) { lastMultiFilePatch.getHunkLayer().clear(); lastMultiFilePatch.getUnchangedLayer().clear(); diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 7416215839..223e549302 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -256,57 +256,6 @@ export default class Patch { return [[firstRow, 0], [firstRow, Infinity]]; } - getNextSelectionRange(lastPatch, lastSelectedRows) { - if (lastSelectedRows.size === 0) { - return this.getFirstChangeRange(); - } - - const lastMax = Math.max(...lastSelectedRows); - - let lastSelectionIndex = 0; - for (const hunk of lastPatch.getHunks()) { - let includesMax = false; - let hunkSelectionOffset = 0; - - changeLoop: for (const change of hunk.getChanges()) { - for (const {intersection, gap} of change.intersectRows(lastSelectedRows, true)) { - // Only include a partial range if this intersection includes the last selected buffer row. - includesMax = intersection.intersectsRow(lastMax); - const delta = includesMax ? lastMax - intersection.start.row + 1 : intersection.getRowCount(); - - if (gap) { - // Range of unselected changes. - hunkSelectionOffset += delta; - } - - if (includesMax) { - break changeLoop; - } - } - } - - lastSelectionIndex += hunkSelectionOffset; - - if (includesMax) { - break; - } - } - - let newSelectionRow = 0; - hunkLoop: for (const hunk of this.getHunks()) { - for (const change of hunk.getChanges()) { - if (lastSelectionIndex < change.bufferRowCount()) { - newSelectionRow = change.getStartBufferRow() + lastSelectionIndex; - break hunkLoop; - } else { - lastSelectionIndex -= change.bufferRowCount(); - } - } - } - - return [[newSelectionRow, 0], [newSelectionRow, Infinity]]; - } - toString() { return this.getHunks().reduce((str, hunk) => str + hunk.toStringIn(this.getBuffer()), ''); } From 4ad126a7021fa5bf183c0c0a2eeb8a42581ec2e5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 10:07:27 -0500 Subject: [PATCH 0857/4053] Pass bound filePatch arguments in FilePatchHeaderView callbacks --- lib/views/multi-file-patch-view.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 2a1df7781c..d306baeb17 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -319,10 +319,10 @@ export default class MultiFilePatchView extends React.Component { tooltips={this.props.tooltips} - undoLastDiscard={this.undoLastDiscardFromButton} - diveIntoMirrorPatch={this.props.diveIntoMirrorPatch} + undoLastDiscard={() => this.undoLastDiscardFromButton(filePatch)} + diveIntoMirrorPatch={() => this.props.diveIntoMirrorPatch(filePatch)} openFile={this.didOpenFile} - toggleFile={this.props.toggleFile} + toggleFile={() => this.props.toggleFile(filePatch)} /> From e6d861f09b36837e53aa2640dcc7ff28f35c338e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 7 Nov 2018 16:37:52 +0100 Subject: [PATCH 0858/4053] discardRows don't need multifilepatch as arg either --- lib/views/multi-file-patch-view.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index d306baeb17..d72d7982de 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -591,7 +591,6 @@ export default class MultiFilePatchView extends React.Component { discardSelectionFromCommand = () => { return this.props.discardRows( - this.props.multiFilePatch, this.props.selectedRows, this.props.selectionMode, {eventSource: {command: 'github:discard-selected-lines'}}, @@ -624,7 +623,6 @@ export default class MultiFilePatchView extends React.Component { discardHunkSelection(hunk, containsSelection) { if (containsSelection) { return this.props.discardRows( - this.props.multiFilePatch, this.props.selectedRows, this.props.selectionMode, {eventSource: 'button'}, From e1786d057a5cf0558a9df392527643d6e6ef1fed Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 10:51:26 -0500 Subject: [PATCH 0859/4053] Use the TextBuffer from MultiFilePatch to render patch strings --- lib/models/patch/file-patch.js | 6 +++--- lib/models/patch/multi-file-patch.js | 2 +- lib/models/patch/patch.js | 4 ++-- test/models/patch/patch.test.js | 10 ++++++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index f6aee78c62..97e3b75b56 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -265,7 +265,7 @@ export default class FilePatch { ); } - toString() { + toStringIn(buffer) { if (!this.isPresent()) { return ''; } @@ -281,7 +281,7 @@ export default class FilePatch { patch: this.getNewSymlink() ? this.getPatch().clone({status: 'added'}) : this.getPatch(), }); - return left.toString() + right.toString(); + return left.toStringIn(buffer) + right.toStringIn(buffer); } else if (this.getStatus() === 'added' && this.getNewFile().isSymlink()) { const symlinkPath = this.getNewSymlink(); return this.getHeaderString() + `@@ -0,0 +1 @@\n+${symlinkPath}\n\\ No newline at end of file\n`; @@ -289,7 +289,7 @@ export default class FilePatch { const symlinkPath = this.getOldSymlink(); return this.getHeaderString() + `@@ -1 +0,0 @@\n-${symlinkPath}\n\\ No newline at end of file\n`; } else { - return this.getHeaderString() + this.getPatch().toString(); + return this.getHeaderString() + this.getPatch().toStringIn(buffer); } } diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index e2640424fb..02007ea785 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -274,6 +274,6 @@ export default class MultiFilePatch { * Construct an apply-able patch String. */ toString() { - return this.filePatches.map(fp => fp.toString()).join(''); + return this.filePatches.map(fp => fp.toStringIn(this.buffer)).join(''); } } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 223e549302..697381747e 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -256,8 +256,8 @@ export default class Patch { return [[firstRow, 0], [firstRow, Infinity]]; } - toString() { - return this.getHunks().reduce((str, hunk) => str + hunk.toStringIn(this.getBuffer()), ''); + toStringIn(buffer) { + return this.getHunks().reduce((str, hunk) => str + hunk.toStringIn(buffer), ''); } isPresent() { diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index b412df6c45..b3d0c857f1 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -746,10 +746,11 @@ describe('Patch', function() { new Unchanged(markRange(layers.unchanged, 9)), ], }); + const marker = markRange(layers.patch, 0, 9); - const p = new Patch({status: 'modified', hunks: [hunk0, hunk1], buffer, layers}); + const p = new Patch({status: 'modified', hunks: [hunk0, hunk1], marker}); - assert.strictEqual(p.toString(), [ + assert.strictEqual(p.toStringIn(buffer), [ '@@ -0,2 +0,3 @@\n', ' 0000\n', '+0001\n', @@ -777,9 +778,10 @@ describe('Patch', function() { new Unchanged(markRange(layers.unchanged, 5)), ], }); + const marker = markRange(layers.patch, 0, 5); - const p = new Patch({status: 'modified', hunks: [hunk], buffer, layers}); - assert.strictEqual(p.toString(), [ + const p = new Patch({status: 'modified', hunks: [hunk], marker}); + assert.strictEqual(p.toStringIn(buffer), [ '@@ -1,5 +1,5 @@\n', ' \n', ' \n', From 42f4602b8d52c80a45b793abc2adc13d0b170a44 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 12:01:28 -0500 Subject: [PATCH 0860/4053] Patch assertion helpers need to accept a TextBuffer --- test/helpers.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/helpers.js b/test/helpers.js index 4c58f0475a..86c39ab3e2 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -156,8 +156,9 @@ export function assertEqualSortedArraysByKey(arr1, arr2, key) { // Helpers for test/models/patch classes class PatchBufferAssertions { - constructor(patch) { + constructor(patch, buffer) { this.patch = patch; + this.buffer = buffer; } hunk(hunkIndex, {startRow, endRow, header, regions}) { @@ -174,7 +175,7 @@ class PatchBufferAssertions { const spec = regions[i]; assert.strictEqual(region.constructor.name.toLowerCase(), spec.kind); - assert.strictEqual(region.toStringIn(this.patch.getBuffer()), spec.string); + assert.strictEqual(region.toStringIn(this.buffer), spec.string); assert.deepEqual(region.getRange().serialize(), spec.range); } } @@ -187,12 +188,12 @@ class PatchBufferAssertions { } } -export function assertInPatch(patch) { - return new PatchBufferAssertions(patch); +export function assertInPatch(patch, buffer) { + return new PatchBufferAssertions(patch, buffer); } -export function assertInFilePatch(filePatch) { - return assertInPatch(filePatch.getPatch()); +export function assertInFilePatch(filePatch, buffer) { + return assertInPatch(filePatch.getPatch(), buffer); } let activeRenderers = []; From 45c2ddf4b794169daf5f7b06b438ea89db5a4c24 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 12:02:23 -0500 Subject: [PATCH 0861/4053] Patch tests are :white_check_mark: :fireworks: --- lib/models/patch/patch.js | 32 ++-- test/models/patch/patch.test.js | 282 ++++++++------------------------ 2 files changed, 84 insertions(+), 230 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 697381747e..d7e7a53391 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -50,7 +50,7 @@ export default class Patch { return new this.constructor({ status: opts.status !== undefined ? opts.status : this.getStatus(), hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), - buffer: opts.buffer !== undefined ? opts.buffer : this.getBuffer(), + marker: opts.marker !== undefined ? opts.marker : this.getMarker(), }); } @@ -138,11 +138,7 @@ export default class Patch { const wholeFile = rowSet.size === this.changedLineCount; const status = this.getStatus() === 'deleted' && !wholeFile ? 'modified' : this.getStatus(); - return { - patch: this.clone({hunks, status, marker}), - buffer, - layers, - }; + return this.clone({hunks, status, marker}); } buildUnstagePatchForLines(originalBuffer, nextLayeredBuffer, rowSet) { @@ -234,11 +230,7 @@ export default class Patch { const layers = builder.getLayers(); const marker = layers.patch.markRange([[0, 0], [buffer.getLastRow(), Infinity]]); - return { - patch: this.clone({hunks, status, marker}), - buffer, - layers, - }; + return this.clone({hunks, status, marker}); } getFirstChangeRange() { @@ -281,9 +273,8 @@ export default class Patch { class NullPatch { constructor() { - this.buffer = new TextBuffer(); - - this.buffer.retain(); + const buffer = new TextBuffer(); + this.marker = buffer.markRange([[0, 0], [0, 0]]); } getStatus() { @@ -294,8 +285,8 @@ class NullPatch { return []; } - getBuffer() { - return this.buffer; + getMarker() { + return this.marker; } getByteSize() { @@ -310,23 +301,23 @@ class NullPatch { if ( opts.status === undefined && opts.hunks === undefined && - opts.buffer === undefined + opts.marker === undefined ) { return this; } else { return new Patch({ status: opts.status !== undefined ? opts.status : this.getStatus(), hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), - buffer: opts.buffer !== undefined ? opts.buffer : this.getBuffer(), + marker: opts.marker !== undefined ? opts.marker : this.getMarker(), }); } } - getStagePatchForLines() { + buildStagePatchForLines() { return this; } - getUnstagePatchForLines() { + buildUnstagePatchForLines() { return this; } @@ -449,6 +440,7 @@ class BufferBuilder { getLayers() { return { + patch: this.layers.get('patch'), hunk: this.layers.get('hunk'), unchanged: this.layers.get(Unchanged), addition: this.layers.get(Addition), diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index b3d0c857f1..b6c923f1a8 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -148,13 +148,21 @@ describe('Patch', function() { }); describe('stage patch generation', function() { + let stageLayeredBuffer; + + beforeEach(function() { + const stageBuffer = new TextBuffer(); + const stageLayers = buildLayers(stageBuffer); + stageLayeredBuffer = {buffer: stageBuffer, layers: stageLayers}; + }); + it('creates a patch that applies selected lines from only the first hunk', function() { - const patch = buildPatchFixture(); - const stagePatch = patch.getStagePatchForLines(new Set([2, 3, 4, 5])); + const {patch, buffer: originalBuffer} = buildPatchFixture(); + const stagePatch = patch.buildStagePatchForLines(originalBuffer, stageLayeredBuffer, new Set([2, 3, 4, 5])); // buffer rows: 0 1 2 3 4 5 6 const expectedBufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n'; - assert.strictEqual(stagePatch.getBuffer().getText(), expectedBufferText); - assertInPatch(stagePatch).hunks( + assert.strictEqual(stageLayeredBuffer.buffer.getText(), expectedBufferText); + assertInPatch(stagePatch, stageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 6, @@ -170,12 +178,12 @@ describe('Patch', function() { }); it('creates a patch that applies selected lines from a single non-first hunk', function() { - const patch = buildPatchFixture(); - const stagePatch = patch.getStagePatchForLines(new Set([8, 13, 14, 16])); + const {patch, buffer: originalBuffer} = buildPatchFixture(); + const stagePatch = patch.buildStagePatchForLines(originalBuffer, stageLayeredBuffer, new Set([8, 13, 14, 16])); // buffer rows: 0 1 2 3 4 5 6 7 8 9 const expectedBufferText = '0007\n0008\n0010\n0011\n0012\n0013\n0014\n0015\n0016\n0018\n'; - assert.strictEqual(stagePatch.getBuffer().getText(), expectedBufferText); - assertInPatch(stagePatch).hunks( + assert.strictEqual(stageLayeredBuffer.buffer.getText(), expectedBufferText); + assertInPatch(stagePatch, stageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 9, @@ -194,8 +202,8 @@ describe('Patch', function() { }); it('creates a patch that applies selected lines from several hunks', function() { - const patch = buildPatchFixture(); - const stagePatch = patch.getStagePatchForLines(new Set([1, 5, 15, 16, 17, 25])); + const {patch, buffer: originalBuffer} = buildPatchFixture(); + const stagePatch = patch.buildStagePatchForLines(originalBuffer, stageLayeredBuffer, new Set([1, 5, 15, 16, 17, 25])); const expectedBufferText = // buffer rows // 0 1 2 3 4 @@ -204,8 +212,8 @@ describe('Patch', function() { '0007\n0010\n0011\n0012\n0013\n0014\n0015\n0016\n0017\n0018\n' + // 15 16 17 '0024\n0025\n No newline at end of file\n'; - assert.strictEqual(stagePatch.getBuffer().getText(), expectedBufferText); - assertInPatch(stagePatch).hunks( + assert.strictEqual(stageLayeredBuffer.buffer.getText(), expectedBufferText); + assertInPatch(stagePatch, stageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 4, @@ -243,15 +251,15 @@ describe('Patch', function() { }); it('marks ranges for each change region on the correct marker layer', function() { - const patch = buildPatchFixture(); - const stagePatch = patch.getStagePatchForLines(new Set([1, 5, 15, 16, 17, 25])); + const {patch, buffer: originalBuffer} = buildPatchFixture(); + patch.buildStagePatchForLines(originalBuffer, stageLayeredBuffer, new Set([1, 5, 15, 16, 17, 25])); const layerRanges = [ - ['hunk', stagePatch.getHunkLayer()], - ['unchanged', stagePatch.getUnchangedLayer()], - ['addition', stagePatch.getAdditionLayer()], - ['deletion', stagePatch.getDeletionLayer()], - ['noNewline', stagePatch.getNoNewlineLayer()], + ['hunk', stageLayeredBuffer.layers.hunk], + ['unchanged', stageLayeredBuffer.layers.unchanged], + ['addition', stageLayeredBuffer.layers.addition], + ['deletion', stageLayeredBuffer.layers.deletion], + ['noNewline', stageLayeredBuffer.layers.noNewline], ].reduce((obj, [key, layer]) => { obj[key] = layer.getMarkers().map(marker => marker.getRange().serialize()); return obj; @@ -299,12 +307,13 @@ describe('Patch', function() { ], }), ]; + const marker = markRange(layers.patch, 0, 5); - const patch = new Patch({status: 'deleted', hunks, buffer, layers}); + const patch = new Patch({status: 'deleted', hunks, marker}); - const stagedPatch = patch.getStagePatchForLines(new Set([1, 3, 4])); + const stagedPatch = patch.buildStagePatchForLines(buffer, stageLayeredBuffer, new Set([1, 3, 4])); assert.strictEqual(stagedPatch.getStatus(), 'modified'); - assertInPatch(stagedPatch).hunks( + assertInPatch(stagedPatch, stageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 5, @@ -332,28 +341,37 @@ describe('Patch', function() { ], }), ]; - const patch = new Patch({status: 'deleted', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, 2); + const patch = new Patch({status: 'deleted', hunks, marker}); - const stagePatch0 = patch.getStagePatchForLines(new Set([0, 1, 2])); + const stagePatch0 = patch.buildStagePatchForLines(buffer, stageLayeredBuffer, new Set([0, 1, 2])); assert.strictEqual(stagePatch0.getStatus(), 'deleted'); }); it('returns a nullPatch as a nullPatch', function() { const nullPatch = Patch.createNull(); - assert.strictEqual(nullPatch.getStagePatchForLines(new Set([1, 2, 3])), nullPatch); + assert.strictEqual(nullPatch.buildStagePatchForLines(new Set([1, 2, 3])), nullPatch); }); }); describe('unstage patch generation', function() { + let unstageLayeredBuffer; + + beforeEach(function() { + const unstageBuffer = new TextBuffer(); + const unstageLayers = buildLayers(unstageBuffer); + unstageLayeredBuffer = {buffer: unstageBuffer, layers: unstageLayers}; + }); + it('creates a patch that updates the index to unapply selected lines from a single hunk', function() { - const patch = buildPatchFixture(); - const unstagePatch = patch.getUnstagePatchForLines(new Set([8, 12, 13])); + const {patch, buffer: originalBuffer} = buildPatchFixture(); + const unstagePatch = patch.buildUnstagePatchForLines(originalBuffer, unstageLayeredBuffer, new Set([8, 12, 13])); assert.strictEqual( - unstagePatch.getBuffer().getText(), + unstageLayeredBuffer.buffer.getText(), // 0 1 2 3 4 5 6 7 8 '0007\n0008\n0009\n0010\n0011\n0012\n0013\n0017\n0018\n', ); - assertInPatch(unstagePatch).hunks( + assertInPatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 8, @@ -370,10 +388,10 @@ describe('Patch', function() { }); it('creates a patch that updates the index to unapply lines from several hunks', function() { - const patch = buildPatchFixture(); - const unstagePatch = patch.getUnstagePatchForLines(new Set([1, 4, 5, 16, 17, 20, 25])); + const {patch, buffer: originalBuffer} = buildPatchFixture(); + const unstagePatch = patch.buildUnstagePatchForLines(originalBuffer, unstageLayeredBuffer, new Set([1, 4, 5, 16, 17, 20, 25])); assert.strictEqual( - unstagePatch.getBuffer().getText(), + unstageLayeredBuffer.buffer.getText(), // 0 1 2 3 4 5 '0000\n0001\n0003\n0004\n0005\n0006\n' + // 6 7 8 9 10 11 12 13 @@ -383,7 +401,7 @@ describe('Patch', function() { // 17 18 19 '0024\n0025\n No newline at end of file\n', ); - assertInPatch(unstagePatch).hunks( + assertInPatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 5, @@ -431,15 +449,14 @@ describe('Patch', function() { }); it('marks ranges for each change region on the correct marker layer', function() { - const patch = buildPatchFixture(); - const unstagePatch = patch.getUnstagePatchForLines(new Set([1, 4, 5, 16, 17, 20, 25])); - + const {patch, buffer: originalBuffer} = buildPatchFixture(); + patch.buildUnstagePatchForLines(originalBuffer, unstageLayeredBuffer, new Set([1, 4, 5, 16, 17, 20, 25])); const layerRanges = [ - ['hunk', unstagePatch.getHunkLayer()], - ['unchanged', unstagePatch.getUnchangedLayer()], - ['addition', unstagePatch.getAdditionLayer()], - ['deletion', unstagePatch.getDeletionLayer()], - ['noNewline', unstagePatch.getNoNewlineLayer()], + ['hunk', unstageLayeredBuffer.layers.hunk], + ['unchanged', unstageLayeredBuffer.layers.unchanged], + ['addition', unstageLayeredBuffer.layers.addition], + ['deletion', unstageLayeredBuffer.layers.deletion], + ['noNewline', unstageLayeredBuffer.layers.noNewline], ].reduce((obj, [key, layer]) => { obj[key] = layer.getMarkers().map(marker => marker.getRange().serialize()); return obj; @@ -490,11 +507,12 @@ describe('Patch', function() { ], }), ]; - const patch = new Patch({status: 'added', hunks, buffer, layers}); - const unstagePatch = patch.getUnstagePatchForLines(new Set([1, 2])); + const marker = markRange(layers.patch, 0, 2); + const patch = new Patch({status: 'added', hunks, marker}); + const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([1, 2])); assert.strictEqual(unstagePatch.getStatus(), 'modified'); - assert.strictEqual(unstagePatch.getBuffer().getText(), '0000\n0001\n0002\n'); - assertInPatch(unstagePatch).hunks( + assert.strictEqual(unstageLayeredBuffer.buffer.getText(), '0000\n0001\n0002\n'); + assertInPatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -522,21 +540,22 @@ describe('Patch', function() { ], }), ]; - const patch = new Patch({status: 'added', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, 2); + const patch = new Patch({status: 'added', hunks, marker}); - const unstagePatch = patch.getUnstagePatchForLines(new Set([0, 1, 2])); + const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([0, 1, 2])); assert.strictEqual(unstagePatch.getStatus(), 'deleted'); }); it('returns a nullPatch as a nullPatch', function() { const nullPatch = Patch.createNull(); - assert.strictEqual(nullPatch.getUnstagePatchForLines(new Set([1, 2, 3])), nullPatch); + assert.strictEqual(nullPatch.buildUnstagePatchForLines(new Set([1, 2, 3])), nullPatch); }); }); describe('getFirstChangeRange', function() { it('accesses the range of the first change from the first hunk', function() { - const patch = buildPatchFixture(); + const {patch} = buildPatchFixture(); assert.deepEqual(patch.getFirstChangeRange(), [[1, 0], [1, Infinity]]); }); @@ -550,177 +569,20 @@ describe('Patch', function() { regions: [], }), ]; - const patch = new Patch({status: 'modified', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0); + const patch = new Patch({status: 'modified', hunks, marker}); assert.deepEqual(patch.getFirstChangeRange(), [[0, 0], [0, 0]]); }); it('returns the origin if the patch is empty', function() { const buffer = new TextBuffer({text: ''}); const layers = buildLayers(buffer); - const patch = new Patch({status: 'modified', hunks: [], buffer, layers}); + const marker = markRange(layers.patch, 0); + const patch = new Patch({status: 'modified', hunks: [], marker}); assert.deepEqual(patch.getFirstChangeRange(), [[0, 0], [0, 0]]); }); }); - describe('next selection range derivation', function() { - it('selects the first change region after the highest buffer row', function() { - const lastPatch = buildPatchFixture(); - // Selected: - // deletions (1-2) and partial addition (4 from 3-5) from hunk 0 - // one deletion row (13 from 12-16) from the middle of hunk 1; - // nothing in hunks 2 or 3 - const lastSelectedRows = new Set([1, 2, 4, 5, 13]); - - const nBuffer = new TextBuffer({text: - // 0 1 2 3 4 - '0000\n0003\n0004\n0005\n0006\n' + - // 5 6 7 8 9 10 11 12 13 14 15 - '0007\n0008\n0009\n0010\n0011\n0012\n0014\n0015\n0016\n0017\n0018\n' + - // 16 17 18 19 20 - '0019\n0020\n0021\n0022\n0023\n' + - // 21 22 23 - '0024\n0025\n No newline at end of file\n', - }); - const nLayers = buildLayers(nBuffer); - const nHunks = [ - new Hunk({ - oldStartRow: 3, oldRowCount: 3, newStartRow: 3, newRowCount: 5, // next row drift = +2 - marker: markRange(nLayers.hunk, 0, 4), - regions: [ - new Unchanged(markRange(nLayers.unchanged, 0)), // 0 - new Addition(markRange(nLayers.addition, 1)), // + 1 - new Unchanged(markRange(nLayers.unchanged, 2)), // 2 - new Addition(markRange(nLayers.addition, 3)), // + 3 - new Unchanged(markRange(nLayers.unchanged, 4)), // 4 - ], - }), - new Hunk({ - oldStartRow: 12, oldRowCount: 9, newStartRow: 14, newRowCount: 7, // next row drift = +2 -2 = 0 - marker: markRange(nLayers.hunk, 5, 15), - regions: [ - new Unchanged(markRange(nLayers.unchanged, 5)), // 5 - new Addition(markRange(nLayers.addition, 6)), // +6 - new Unchanged(markRange(nLayers.unchanged, 7, 9)), // 7 8 9 - new Deletion(markRange(nLayers.deletion, 10, 13)), // -10 -11 -12 -13 - new Addition(markRange(nLayers.addition, 14)), // +14 - new Unchanged(markRange(nLayers.unchanged, 15)), // 15 - ], - }), - new Hunk({ - oldStartRow: 26, oldRowCount: 4, newStartRow: 26, newRowCount: 3, // next row drift = 0 -1 = -1 - marker: markRange(nLayers.hunk, 16, 20), - regions: [ - new Unchanged(markRange(nLayers.unchanged, 16)), // 16 - new Addition(markRange(nLayers.addition, 17)), // +17 - new Deletion(markRange(nLayers.deletion, 18, 19)), // -18 -19 - new Unchanged(markRange(nLayers.unchanged, 20)), // 20 - ], - }), - new Hunk({ - oldStartRow: 32, oldRowCount: 1, newStartRow: 31, newRowCount: 2, - marker: markRange(nLayers.hunk, 22, 24), - regions: [ - new Unchanged(markRange(nLayers.unchanged, 22)), // 22 - new Addition(markRange(nLayers.addition, 23)), // +23 - new NoNewline(markRange(nLayers.noNewline, 24)), - ], - }), - ]; - const nextPatch = new Patch({status: 'modified', hunks: nHunks, buffer: nBuffer, layers: nLayers}); - - const nextRange = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); - // Original buffer row 14 = the next changed row = new buffer row 11 - assert.deepEqual(nextRange, [[11, 0], [11, Infinity]]); - }); - - it('offsets the chosen selection index by hunks that were completely selected', function() { - const buffer = buildBuffer(11); - const layers = buildLayers(buffer); - const lastPatch = new Patch({ - status: 'modified', - hunks: [ - new Hunk({ - oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 3, - marker: markRange(layers.hunk, 0, 5), - regions: [ - new Unchanged(markRange(layers.unchanged, 0)), - new Addition(markRange(layers.addition, 1, 2)), - new Deletion(markRange(layers.deletion, 3, 4)), - new Unchanged(markRange(layers.unchanged, 5)), - ], - }), - new Hunk({ - oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, - marker: markRange(layers.hunk, 6, 11), - regions: [ - new Unchanged(markRange(layers.unchanged, 6)), - new Addition(markRange(layers.addition, 7, 8)), - new Deletion(markRange(layers.deletion, 9, 10)), - new Unchanged(markRange(layers.unchanged, 11)), - ], - }), - ], - buffer, - layers, - }); - // Select: - // * all changes from hunk 0 - // * partial addition (8 of 7-8) from hunk 1 - const lastSelectedRows = new Set([1, 2, 3, 4, 8]); - - const nextBuffer = new TextBuffer({text: '0006\n0007\n0008\n0009\n0010\n0011\n'}); - const nextLayers = buildLayers(nextBuffer); - const nextPatch = new Patch({ - status: 'modified', - hunks: [ - new Hunk({ - oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, - marker: markRange(nextLayers.hunk, 0, 5), - regions: [ - new Unchanged(markRange(nextLayers.unchanged, 0)), - new Addition(markRange(nextLayers.addition, 1)), - new Deletion(markRange(nextLayers.deletion, 3, 4)), - new Unchanged(markRange(nextLayers.unchanged, 5)), - ], - }), - ], - buffer: nextBuffer, - layers: nextLayers, - }); - - const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); - assert.deepEqual(range, [[3, 0], [3, Infinity]]); - }); - - it('selects the first row of the first change of the patch if no rows were selected before', function() { - const lastPatch = buildPatchFixture(); - const lastSelectedRows = new Set(); - - const buffer = lastPatch.getBuffer(); - const layers = buildLayers(buffer); - const nextPatch = new Patch({ - status: 'modified', - hunks: [ - new Hunk({ - oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 4, - marker: markRange(layers.hunk, 0, 4), - regions: [ - new Unchanged(markRange(layers.unchanged, 0)), - new Addition(markRange(layers.addition, 1, 2)), - new Deletion(markRange(layers.deletion, 3)), - new Unchanged(markRange(layers.unchanged, 4)), - ], - }), - ], - buffer, - layers, - }); - - const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); - assert.deepEqual(range, [[1, 0], [1, Infinity]]); - }); - }); - it('prints itself as an apply-ready string', function() { const buffer = buildBuffer(10); const layers = buildLayers(buffer); From dc0c14dabd06a52365443f34b71cc6b53c5bb198 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 12:03:10 -0500 Subject: [PATCH 0862/4053] Adapt callers to changed buildStagePatchForLines() return value --- lib/models/patch/file-patch.js | 14 +++----------- lib/models/patch/multi-file-patch.js | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 97e3b75b56..3f0ca221c3 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -196,24 +196,16 @@ export default class FilePatch { } } - const {patch, buffer, layers} = this.patch.getStagePatchForLines( + const patch = this.patch.buildStagePatchForLines( originalBuffer, nextLayeredBuffer, selectedLineSet, ); if (this.getStatus() === 'deleted') { // Populate newFile - return { - filePatch: this.clone({newFile, patch}), - buffer, - layers, - }; + return this.clone({newFile, patch}); } else { - return { - filePatch: this.clone({patch}), - buffer, - layers, - }; + return this.clone({patch}); } } diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 02007ea785..e68725fc11 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -64,7 +64,7 @@ export default class MultiFilePatch { getStagePatchForLines(selectedLineSet) { const nextLayeredBuffer = this.buildLayeredBuffer(); - const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { + const nextFilePatches = Array.from(this.getFilePatchesContaining(selectedLineSet), fp => { return fp.buildStagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); }); From c148a8df3038d70ca90506ccd276959d6e56d6b5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 12:04:35 -0500 Subject: [PATCH 0863/4053] Catch that other "getFilePatchesContaining() returns a Set" call --- lib/models/patch/multi-file-patch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index e68725fc11..ca7450add7 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -81,7 +81,7 @@ export default class MultiFilePatch { getUnstagePatchForLines(selectedLineSet) { const nextLayeredBuffer = this.buildLayeredBuffer(); - const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { + const nextFilePatches = Array.from(this.getFilePatchesContaining(selectedLineSet), fp => { return fp.buildUnstagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); }); From 188d2eaab1702714f7aff4cce6020cda9526444e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 13:03:21 -0500 Subject: [PATCH 0864/4053] Cover the last few Patch methods --- test/models/patch/patch.test.js | 40 ++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index b6c923f1a8..e1dd627c6b 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -147,6 +147,20 @@ describe('Patch', function() { assert.strictEqual(dup2.getMarker(), nMarker); }); + it('returns an empty Range at the beginning of its Marker', function() { + const {patch} = buildPatchFixture(); + assert.deepEqual(patch.getStartRange().serialize(), [[0, 0], [0, 0]]); + }); + + it('determines whether or not a buffer row belongs to this patch', function() { + const {patch} = buildPatchFixture(); + + assert.isTrue(patch.containsRow(0)); + assert.isTrue(patch.containsRow(5)); + assert.isTrue(patch.containsRow(26)); + assert.isFalse(patch.containsRow(27)); + }); + describe('stage patch generation', function() { let stageLayeredBuffer; @@ -547,6 +561,28 @@ describe('Patch', function() { assert.strictEqual(unstagePatch.getStatus(), 'deleted'); }); + it('returns an addition when unstaging a deletion', function() { + const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + const layers = buildLayers(buffer); + const hunks = [ + new Hunk({ + oldStartRow: 1, + oldRowCount: 0, + newStartRow: 1, + newRowCount: 3, + marker: markRange(layers.hunk, 0, 2), + regions: [ + new Addition(markRange(layers.addition, 0, 2)), + ], + }), + ]; + const marker = markRange(layers.patch, 0, 2); + const patch = new Patch({status: 'deleted', hunks, marker}); + + const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([0, 1, 2])); + assert.strictEqual(unstagePatch.getStatus(), 'added'); + }); + it('returns a nullPatch as a nullPatch', function() { const nullPatch = Patch.createNull(); assert.strictEqual(nullPatch.buildUnstagePatchForLines(new Set([1, 2, 3])), nullPatch); @@ -704,6 +740,8 @@ function markRange(buffer, start, end = start) { function buildPatchFixture() { const buffer = buildBuffer(26, true); + buffer.append('\n\n\n\n\n\n'); + const layers = buildLayers(buffer); const hunks = [ @@ -753,7 +791,7 @@ function buildPatchFixture() { ], }), ]; - const marker = markRange(layers.patch, 0, Infinity); + const marker = markRange(layers.patch, 0, 26); return { patch: new Patch({status: 'modified', hunks, marker}), From 2fff9721fbb734555f00fcba1ac757d9333ec522 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 13:13:50 -0500 Subject: [PATCH 0865/4053] Bring NullPatch up to date --- lib/models/patch/patch.js | 32 ++++++++++++++++---------------- test/models/patch/patch.test.js | 11 ++++++----- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index d7e7a53391..20b0363bba 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -265,7 +265,7 @@ export default class Patch { if (this.hunks.length !== other.hunks.length) { return false; } if (this.hunks.some((hunk, i) => !hunk.isEqual(other.hunks[i]))) { return false; } - if (this.buffer.getText() !== other.buffer.getText()) { return false; } + if (!this.marker.getRange().isEqual(other.marker.getRange())) { return false; } return true; } @@ -281,19 +281,27 @@ class NullPatch { return null; } + getMarker() { + return this.marker; + } + + getStartRange() { + return Range.fromObject([[0, 0], [0, 0]]); + } + getHunks() { return []; } - getMarker() { - return this.marker; + getChangedLineCount() { + return 0; } - getByteSize() { - return 0; + containsRow() { + return false; } - getChangedLineCount() { + getMaxLineNumberWidth() { return 0; } @@ -322,18 +330,10 @@ class NullPatch { } getFirstChangeRange() { - return [[0, 0], [0, 0]]; - } - - getNextSelectionRange() { - return [[0, 0], [0, 0]]; - } - - getMaxLineNumberWidth() { - return 0; + return Range.fromObject([[0, 0], [0, 0]]); } - toString() { + toStringIn() { return ''; } diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index e1dd627c6b..2f7738eab7 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -694,14 +694,15 @@ describe('Patch', function() { it('has a stubbed nullPatch counterpart', function() { const nullPatch = Patch.createNull(); assert.isNull(nullPatch.getStatus()); - assert.deepEqual(nullPatch.getHunks(), []); assert.deepEqual(nullPatch.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); - assert.isFalse(nullPatch.isPresent()); - assert.strictEqual(nullPatch.toString(), ''); + assert.deepEqual(nullPatch.getStartRange().serialize(), [[0, 0], [0, 0]]); + assert.deepEqual(nullPatch.getHunks(), []); assert.strictEqual(nullPatch.getChangedLineCount(), 0); + assert.isFalse(nullPatch.containsRow(0)); assert.strictEqual(nullPatch.getMaxLineNumberWidth(), 0); - assert.deepEqual(nullPatch.getFirstChangeRange(), [[0, 0], [0, 0]]); - assert.deepEqual(nullPatch.getNextSelectionRange(), [[0, 0], [0, 0]]); + assert.deepEqual(nullPatch.getFirstChangeRange().serialize(), [[0, 0], [0, 0]]); + assert.strictEqual(nullPatch.toStringIn(), ''); + assert.isFalse(nullPatch.isPresent()); }); }); From 462dffda3afbf708e5e8566c73de36b1237b4c2e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 7 Nov 2018 20:23:36 +0100 Subject: [PATCH 0866/4053] missed a rename oops --- .../{file-patch-view.test.js => multi-file-patch-view.test.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/views/{file-patch-view.test.js => multi-file-patch-view.test.js} (99%) diff --git a/test/views/file-patch-view.test.js b/test/views/multi-file-patch-view.test.js similarity index 99% rename from test/views/file-patch-view.test.js rename to test/views/multi-file-patch-view.test.js index de957868a7..0524889884 100644 --- a/test/views/file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -8,7 +8,7 @@ import {nullFile} from '../../lib/models/patch/file'; import FilePatch from '../../lib/models/patch/file-patch'; import RefHolder from '../../lib/models/ref-holder'; -describe('MultiFilePatchView', function() { +describe.only('MultiFilePatchView', function() { let atomEnv, workspace, repository, filePatch; beforeEach(async function() { From 6b1744b019f52e482481f9222e4be1d79a2b0c74 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 15:48:40 -0500 Subject: [PATCH 0867/4053] FilePatch tests :white_check_mark: + coverage --- lib/models/patch/file-patch.js | 33 +-- test/models/patch/file-patch.test.js | 332 +++++++-------------------- 2 files changed, 89 insertions(+), 276 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 3f0ca221c3..7f7ce899dc 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -1,7 +1,6 @@ import {nullFile} from './file'; import Patch from './patch'; import {toGitPathSep} from '../../helpers'; -import {addEvent} from '../../reporter-proxy'; export default class FilePatch { static createNull() { @@ -12,12 +11,6 @@ export default class FilePatch { this.oldFile = oldFile; this.newFile = newFile; this.patch = patch; - // const metricsData = {package: 'github'}; - // if (this.getPatch()) { - // metricsData.sizeInBytes = this.getByteSize(); - // } - // - // addEvent('file-patch-constructed', metricsData); } isPresent() { @@ -183,16 +176,17 @@ export default class FilePatch { } buildStagePatchForLines(originalBuffer, nextLayeredBuffer, selectedLineSet) { - let newFile = this.getOldFile(); - - if (this.hasTypechange() && this.getStatus() === 'deleted') { - // Handle the special case when symlink is created where an entire file was deleted. In order to stage the file - // deletion, we must ensure that the created file patch has no new file. + let newFile = this.getNewFile(); + if (this.getStatus() === 'deleted') { if ( this.patch.getChangedLineCount() === selectedLineSet.size && Array.from(selectedLineSet, row => this.patch.containsRow(row)).every(Boolean) ) { + // Whole file deletion staged. newFile = nullFile; + } else { + // Partial file deletion, which becomes a modification. + newFile = this.getOldFile(); } } @@ -201,12 +195,7 @@ export default class FilePatch { nextLayeredBuffer, selectedLineSet, ); - if (this.getStatus() === 'deleted') { - // Populate newFile - return this.clone({newFile, patch}); - } else { - return this.clone({patch}); - } + return this.clone({newFile, patch}); } buildUnstagePatchForLines(originalBuffer, nextLayeredBuffer, selectedLineSet) { @@ -235,16 +224,12 @@ export default class FilePatch { } } - const {patch, buffer, layers} = this.patch.buildUnstagePatchForLines( + const patch = this.patch.buildUnstagePatchForLines( originalBuffer, nextLayeredBuffer, selectedLineSet, ); - return { - filePatch: this.clone({oldFile, newFile, patch}), - buffer, - layers, - }; + return this.clone({oldFile, newFile, patch}); } isEqual(other) { diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 0726890900..0fe38ffcff 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -6,7 +6,6 @@ import Patch from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; import {Unchanged, Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; import {assertInFilePatch} from '../../helpers'; -import * as reporterProxy from '../../../lib/reporter-proxy'; describe('FilePatch', function() { it('delegates methods to its files and patch', function() { @@ -23,15 +22,11 @@ describe('FilePatch', function() { }), ]; const marker = markRange(layers.patch); - const patch = new Patch({status: 'modified', hunks, buffer, layers, marker}); + const patch = new Patch({status: 'modified', hunks, marker}); const oldFile = new File({path: 'a.txt', mode: '120000', symlink: 'dest.txt'}); const newFile = new File({path: 'b.txt', mode: '100755'}); - sinon.stub(reporterProxy, 'addEvent'); - assert.isFalse(reporterProxy.addEvent.called); - const filePatch = new FilePatch(oldFile, newFile, patch); - assert.isTrue(reporterProxy.addEvent.calledOnceWithExactly('file-patch-constructed', {package: 'github', sizeInBytes: 15})); assert.isTrue(filePatch.isPresent()); @@ -43,28 +38,8 @@ describe('FilePatch', function() { assert.strictEqual(filePatch.getNewMode(), '100755'); assert.isUndefined(filePatch.getNewSymlink()); - assert.strictEqual(filePatch.getByteSize(), 15); - assert.strictEqual(filePatch.getBuffer().getText(), '0000\n0001\n0002\n'); assert.strictEqual(filePatch.getMarker(), marker); assert.strictEqual(filePatch.getMaxLineNumberWidth(), 1); - - const nBuffer = new TextBuffer({text: '0001\n0002\n'}); - const nLayers = buildLayers(nBuffer); - const nHunks = [ - new Hunk({ - oldStartRow: 3, oldRowCount: 1, newStartRow: 3, newRowCount: 2, - marker: markRange(nLayers.hunk, 0, 1), - regions: [ - new Unchanged(markRange(nLayers.unchanged, 0)), - new Addition(markRange(nLayers.addition, 1)), - ], - }), - ]; - const nPatch = new Patch({status: 'modified', hunks: nHunks, buffer: nBuffer, layers: nLayers}); - const nFilePatch = new FilePatch(oldFile, newFile, nPatch); - - const range = nFilePatch.getNextSelectionRange(filePatch, new Set([1])); - assert.deepEqual(range, [[1, 0], [1, Infinity]]); }); it('accesses a file path from either side of the patch', function() { @@ -181,81 +156,6 @@ describe('FilePatch', function() { assert.deepEqual(filePatch.getStartRange().serialize(), [[1, 0], [1, 0]]); }); - it('adopts a buffer and layers from a prior FilePatch', function() { - const oldFile = new File({path: 'a.txt', mode: '100755'}); - const newFile = new File({path: 'b.txt', mode: '100755'}); - - const prevBuffer = new TextBuffer({text: '0000\n0001\n0002\n'}); - const prevLayers = buildLayers(prevBuffer); - const prevHunks = [ - new Hunk({ - oldStartRow: 2, oldRowCount: 2, newStartRow: 2, newRowCount: 3, - marker: markRange(prevLayers.hunk, 0, 2), - regions: [ - new Unchanged(markRange(prevLayers.unchanged, 0)), - new Addition(markRange(prevLayers.addition, 1)), - new Unchanged(markRange(prevLayers.unchanged, 2)), - ], - }), - ]; - const prevPatch = new Patch({status: 'modified', hunks: prevHunks, buffer: prevBuffer, layers: prevLayers}); - const prevFilePatch = new FilePatch(oldFile, newFile, prevPatch); - - const nextBuffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n No newline at end of file'}); - const nextLayers = buildLayers(nextBuffer); - const nextHunks = [ - new Hunk({ - oldStartRow: 2, oldRowCount: 2, newStartRow: 2, newRowCount: 3, - marker: markRange(nextLayers.hunk, 0, 2), - regions: [ - new Unchanged(markRange(nextLayers.unchanged, 0)), - new Addition(markRange(nextLayers.addition, 1)), - new Unchanged(markRange(nextLayers.unchanged, 2)), - ], - }), - new Hunk({ - oldStartRow: 10, oldRowCount: 2, newStartRow: 11, newRowCount: 1, - marker: markRange(nextLayers.hunk, 3, 5), - regions: [ - new Unchanged(markRange(nextLayers.unchanged, 3)), - new Deletion(markRange(nextLayers.deletion, 4)), - new NoNewline(markRange(nextLayers.noNewline, 5)), - ], - }), - ]; - const nextPatch = new Patch({status: 'modified', hunks: nextHunks, buffer: nextBuffer, layers: nextLayers}); - const nextFilePatch = new FilePatch(oldFile, newFile, nextPatch); - - nextFilePatch.adoptBufferFrom(prevFilePatch); - - assert.strictEqual(nextFilePatch.getBuffer(), prevBuffer); - assert.strictEqual(nextFilePatch.getHunkLayer(), prevLayers.hunk); - assert.strictEqual(nextFilePatch.getUnchangedLayer(), prevLayers.unchanged); - assert.strictEqual(nextFilePatch.getAdditionLayer(), prevLayers.addition); - assert.strictEqual(nextFilePatch.getDeletionLayer(), prevLayers.deletion); - assert.strictEqual(nextFilePatch.getNoNewlineLayer(), prevLayers.noNewline); - - const rangesFrom = layer => layer.getMarkers().map(marker => marker.getRange().serialize()); - assert.deepEqual(rangesFrom(nextFilePatch.getHunkLayer()), [ - [[0, 0], [2, 4]], - [[3, 0], [5, 26]], - ]); - assert.deepEqual(rangesFrom(nextFilePatch.getUnchangedLayer()), [ - [[0, 0], [0, 4]], - [[2, 0], [2, 4]], - [[3, 0], [3, 4]], - ]); - assert.deepEqual(rangesFrom(nextFilePatch.getAdditionLayer()), [ - [[1, 0], [1, 4]], - ]); - assert.deepEqual(rangesFrom(nextFilePatch.getDeletionLayer()), [ - [[4, 0], [4, 4]], - ]); - assert.deepEqual(rangesFrom(nextFilePatch.getNoNewlineLayer()), [ - [[5, 0], [5, 26]], - ]); - }); - describe('file-level change detection', function() { let emptyPatch; @@ -347,7 +247,15 @@ describe('FilePatch', function() { assert.strictEqual(clone3.getPatch(), patch1); }); - describe('getStagePatchForLines()', function() { + describe('buildStagePatchForLines()', function() { + let stagedLayeredBuffer; + + beforeEach(function() { + const buffer = new TextBuffer(); + const layers = buildLayers(buffer); + stagedLayeredBuffer = {buffer, layers}; + }); + it('returns a new FilePatch that applies only the selected lines', function() { const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n'}); const layers = buildLayers(buffer); @@ -363,17 +271,18 @@ describe('FilePatch', function() { ], }), ]; - const patch = new Patch({status: 'modified', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, 4); + const patch = new Patch({status: 'modified', hunks, marker}); const oldFile = new File({path: 'file.txt', mode: '100644'}); const newFile = new File({path: 'file.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); - const stagedPatch = filePatch.getStagePatchForLines(new Set([1, 3])); + const stagedPatch = filePatch.buildStagePatchForLines(buffer, stagedLayeredBuffer, new Set([1, 3])); assert.strictEqual(stagedPatch.getStatus(), 'modified'); assert.strictEqual(stagedPatch.getOldFile(), oldFile); assert.strictEqual(stagedPatch.getNewFile(), newFile); - assert.strictEqual(stagedPatch.getBuffer().getText(), '0000\n0001\n0003\n0004\n'); - assertInFilePatch(stagedPatch).hunks( + assert.strictEqual(stagedLayeredBuffer.buffer.getText(), '0000\n0001\n0003\n0004\n'); + assertInFilePatch(stagedPatch, stagedLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 3, @@ -389,10 +298,11 @@ describe('FilePatch', function() { }); describe('staging lines from deleted files', function() { + let buffer; let oldFile, deletionPatch; beforeEach(function() { - const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); const layers = buildLayers(buffer); const hunks = [ new Hunk({ @@ -403,19 +313,20 @@ describe('FilePatch', function() { ], }), ]; - const patch = new Patch({status: 'deleted', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, 2); + const patch = new Patch({status: 'deleted', hunks, marker}); oldFile = new File({path: 'file.txt', mode: '100644'}); deletionPatch = new FilePatch(oldFile, nullFile, patch); }); it('handles staging part of the file', function() { - const stagedPatch = deletionPatch.getStagePatchForLines(new Set([1, 2])); + const stagedPatch = deletionPatch.buildStagePatchForLines(buffer, stagedLayeredBuffer, new Set([1, 2])); assert.strictEqual(stagedPatch.getStatus(), 'modified'); assert.strictEqual(stagedPatch.getOldFile(), oldFile); assert.strictEqual(stagedPatch.getNewFile(), oldFile); - assert.strictEqual(stagedPatch.getBuffer().getText(), '0000\n0001\n0002\n'); - assertInFilePatch(stagedPatch).hunks( + assert.strictEqual(stagedLayeredBuffer.buffer.getText(), '0000\n0001\n0002\n'); + assertInFilePatch(stagedPatch, stagedLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -429,12 +340,12 @@ describe('FilePatch', function() { }); it('handles staging all lines, leaving nothing unstaged', function() { - const stagedPatch = deletionPatch.getStagePatchForLines(new Set([1, 2, 3])); + const stagedPatch = deletionPatch.buildStagePatchForLines(buffer, stagedLayeredBuffer, new Set([0, 1, 2])); assert.strictEqual(stagedPatch.getStatus(), 'deleted'); assert.strictEqual(stagedPatch.getOldFile(), oldFile); assert.isFalse(stagedPatch.getNewFile().isPresent()); - assert.strictEqual(stagedPatch.getBuffer().getText(), '0000\n0001\n0002\n'); - assertInFilePatch(stagedPatch).hunks( + assert.strictEqual(stagedLayeredBuffer.buffer.getText(), '0000\n0001\n0002\n'); + assertInFilePatch(stagedPatch, stagedLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -447,8 +358,8 @@ describe('FilePatch', function() { }); it('unsets the newFile when a symlink is created where a file was deleted', function() { - const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); - const layers = buildLayers(buffer); + const nBuffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + const layers = buildLayers(nBuffer); const hunks = [ new Hunk({ oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 0, @@ -458,65 +369,28 @@ describe('FilePatch', function() { ], }), ]; - const patch = new Patch({status: 'deleted', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, 2); + const patch = new Patch({status: 'deleted', hunks, marker}); oldFile = new File({path: 'file.txt', mode: '100644'}); const newFile = new File({path: 'file.txt', mode: '120000'}); const replacePatch = new FilePatch(oldFile, newFile, patch); - const stagedPatch = replacePatch.getStagePatchForLines(new Set([0, 1, 2])); + const stagedPatch = replacePatch.buildStagePatchForLines(nBuffer, stagedLayeredBuffer, new Set([0, 1, 2])); assert.strictEqual(stagedPatch.getOldFile(), oldFile); assert.isFalse(stagedPatch.getNewFile().isPresent()); }); }); }); - it('stages an entire hunk at once', function() { - const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n0005\n'}); - const layers = buildLayers(buffer); - const hunks = [ - new Hunk({ - oldStartRow: 10, oldRowCount: 2, newStartRow: 10, newRowCount: 3, - marker: markRange(layers.hunk, 0, 2), - regions: [ - new Unchanged(markRange(layers.unchanged, 0)), - new Addition(markRange(layers.addition, 1)), - new Unchanged(markRange(layers.unchanged, 2)), - ], - }), - new Hunk({ - oldStartRow: 20, oldRowCount: 3, newStartRow: 19, newRowCount: 2, - marker: markRange(layers.hunk, 3, 5), - regions: [ - new Unchanged(markRange(layers.unchanged, 3)), - new Deletion(markRange(layers.deletion, 4)), - new Unchanged(markRange(layers.unchanged, 5)), - ], - }), - ]; - const patch = new Patch({status: 'modified', hunks, buffer, layers}); - const oldFile = new File({path: 'file.txt', mode: '100644'}); - const newFile = new File({path: 'file.txt', mode: '100644'}); - const filePatch = new FilePatch(oldFile, newFile, patch); + describe('getUnstagePatchForLines()', function() { + let unstageLayeredBuffer; - const stagedPatch = filePatch.getStagePatchForHunk(hunks[1]); - assert.strictEqual(stagedPatch.getBuffer().getText(), '0003\n0004\n0005\n'); - assert.strictEqual(stagedPatch.getOldFile(), oldFile); - assert.strictEqual(stagedPatch.getNewFile(), newFile); - assertInFilePatch(stagedPatch).hunks( - { - startRow: 0, - endRow: 2, - header: '@@ -20,3 +18,2 @@', - regions: [ - {kind: 'unchanged', string: ' 0003\n', range: [[0, 0], [0, 4]]}, - {kind: 'deletion', string: '-0004\n', range: [[1, 0], [1, 4]]}, - {kind: 'unchanged', string: ' 0005\n', range: [[2, 0], [2, 4]]}, - ], - }, - ); - }); + beforeEach(function() { + const buffer = new TextBuffer(); + const layers = buildLayers(buffer); + unstageLayeredBuffer = {buffer, layers}; + }); - describe('getUnstagePatchForLines()', function() { it('returns a new FilePatch that unstages only the specified lines', function() { const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n'}); const layers = buildLayers(buffer); @@ -532,17 +406,18 @@ describe('FilePatch', function() { ], }), ]; - const patch = new Patch({status: 'modified', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, 4); + const patch = new Patch({status: 'modified', hunks, marker}); const oldFile = new File({path: 'file.txt', mode: '100644'}); const newFile = new File({path: 'file.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); - const unstagedPatch = filePatch.getUnstagePatchForLines(new Set([1, 3])); + const unstagedPatch = filePatch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([1, 3])); assert.strictEqual(unstagedPatch.getStatus(), 'modified'); assert.strictEqual(unstagedPatch.getOldFile(), newFile); assert.strictEqual(unstagedPatch.getNewFile(), newFile); - assert.strictEqual(unstagedPatch.getBuffer().getText(), '0000\n0001\n0002\n0003\n0004\n'); - assertInFilePatch(unstagedPatch).hunks( + assert.strictEqual(unstageLayeredBuffer.buffer.getText(), '0000\n0001\n0002\n0003\n0004\n'); + assertInFilePatch(unstagedPatch, unstageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 4, @@ -559,10 +434,11 @@ describe('FilePatch', function() { }); describe('unstaging lines from an added file', function() { + let buffer; let newFile, addedPatch, addedFilePatch; beforeEach(function() { - const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); const layers = buildLayers(buffer); const hunks = [ new Hunk({ @@ -573,17 +449,18 @@ describe('FilePatch', function() { ], }), ]; + const marker = markRange(layers.patch, 0, 2); newFile = new File({path: 'file.txt', mode: '100644'}); - addedPatch = new Patch({status: 'added', hunks, buffer, layers}); + addedPatch = new Patch({status: 'added', hunks, marker}); addedFilePatch = new FilePatch(nullFile, newFile, addedPatch); }); it('handles unstaging part of the file', function() { - const unstagePatch = addedFilePatch.getUnstagePatchForLines(new Set([2])); + const unstagePatch = addedFilePatch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([2])); assert.strictEqual(unstagePatch.getStatus(), 'modified'); assert.strictEqual(unstagePatch.getOldFile(), newFile); assert.strictEqual(unstagePatch.getNewFile(), newFile); - assertInFilePatch(unstagePatch).hunks( + assertInFilePatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -597,11 +474,11 @@ describe('FilePatch', function() { }); it('handles unstaging all lines, leaving nothing staged', function() { - const unstagePatch = addedFilePatch.getUnstagePatchForLines(new Set([0, 1, 2])); + const unstagePatch = addedFilePatch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([0, 1, 2])); assert.strictEqual(unstagePatch.getStatus(), 'deleted'); assert.strictEqual(unstagePatch.getOldFile(), newFile); assert.isFalse(unstagePatch.getNewFile().isPresent()); - assertInFilePatch(unstagePatch).hunks( + assertInFilePatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -616,10 +493,10 @@ describe('FilePatch', function() { it('unsets the newFile when a symlink is deleted and a file is created in its place', function() { const oldSymlink = new File({path: 'file.txt', mode: '120000', symlink: 'wat.txt'}); const patch = new FilePatch(oldSymlink, newFile, addedPatch); - const unstagePatch = patch.getUnstagePatchForLines(new Set([0, 1, 2])); + const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([0, 1, 2])); assert.strictEqual(unstagePatch.getOldFile(), newFile); assert.isFalse(unstagePatch.getNewFile().isPresent()); - assertInFilePatch(unstagePatch).hunks( + assertInFilePatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -633,10 +510,10 @@ describe('FilePatch', function() { }); describe('unstaging lines from a removed file', function() { - let oldFile, removedFilePatch; + let oldFile, removedFilePatch, buffer; beforeEach(function() { - const buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); + buffer = new TextBuffer({text: '0000\n0001\n0002\n'}); const layers = buildLayers(buffer); const hunks = [ new Hunk({ @@ -648,16 +525,17 @@ describe('FilePatch', function() { }), ]; oldFile = new File({path: 'file.txt', mode: '100644'}); - const removedPatch = new Patch({status: 'deleted', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, 2); + const removedPatch = new Patch({status: 'deleted', hunks, marker}); removedFilePatch = new FilePatch(oldFile, nullFile, removedPatch); }); it('handles unstaging part of the file', function() { - const discardPatch = removedFilePatch.getUnstagePatchForLines(new Set([1])); + const discardPatch = removedFilePatch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([1])); assert.strictEqual(discardPatch.getStatus(), 'added'); assert.strictEqual(discardPatch.getOldFile(), nullFile); assert.strictEqual(discardPatch.getNewFile(), oldFile); - assertInFilePatch(discardPatch).hunks( + assertInFilePatch(discardPatch, unstageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 0, @@ -670,11 +548,15 @@ describe('FilePatch', function() { }); it('handles unstaging the entire file', function() { - const discardPatch = removedFilePatch.getUnstagePatchForLines(new Set([0, 1, 2])); + const discardPatch = removedFilePatch.buildUnstagePatchForLines( + buffer, + unstageLayeredBuffer, + new Set([0, 1, 2]), + ); assert.strictEqual(discardPatch.getStatus(), 'added'); assert.strictEqual(discardPatch.getOldFile(), nullFile); assert.strictEqual(discardPatch.getNewFile(), oldFile); - assertInFilePatch(discardPatch).hunks( + assertInFilePatch(discardPatch, unstageLayeredBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -688,53 +570,7 @@ describe('FilePatch', function() { }); }); - it('unstages an entire hunk at once', function() { - const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n0005\n'}); - const layers = buildLayers(buffer); - const hunks = [ - new Hunk({ - oldStartRow: 10, oldRowCount: 2, newStartRow: 10, newRowCount: 3, - marker: markRange(layers.hunk, 0, 2), - regions: [ - new Unchanged(markRange(layers.unchanged, 0)), - new Addition(markRange(layers.addition, 1)), - new Unchanged(markRange(layers.unchanged, 2)), - ], - }), - new Hunk({ - oldStartRow: 20, oldRowCount: 3, newStartRow: 19, newRowCount: 2, - marker: markRange(layers.hunk, 3, 5), - regions: [ - new Unchanged(markRange(layers.unchanged, 3)), - new Deletion(markRange(layers.deletion, 4)), - new Unchanged(markRange(layers.unchanged, 5)), - ], - }), - ]; - const patch = new Patch({status: 'modified', hunks, buffer, layers}); - const oldFile = new File({path: 'file.txt', mode: '100644'}); - const newFile = new File({path: 'file.txt', mode: '100644'}); - const filePatch = new FilePatch(oldFile, newFile, patch); - - const unstagedPatch = filePatch.getUnstagePatchForHunk(hunks[0]); - assert.strictEqual(unstagedPatch.getBuffer().getText(), '0000\n0001\n0002\n'); - assert.strictEqual(unstagedPatch.getOldFile(), newFile); - assert.strictEqual(unstagedPatch.getNewFile(), newFile); - assertInFilePatch(unstagedPatch).hunks( - { - startRow: 0, - endRow: 2, - header: '@@ -10,3 +10,2 @@', - regions: [ - {kind: 'unchanged', string: ' 0000\n', range: [[0, 0], [0, 4]]}, - {kind: 'deletion', string: '-0001\n', range: [[1, 0], [1, 4]]}, - {kind: 'unchanged', string: ' 0002\n', range: [[2, 0], [2, 4]]}, - ], - }, - ); - }); - - describe('toString()', function() { + describe('toStringIn()', function() { it('converts the patch to the standard textual format', function() { const buffer = new TextBuffer({text: '0000\n0001\n0002\n0003\n0004\n0005\n0006\n0007\n'}); const layers = buildLayers(buffer); @@ -759,7 +595,8 @@ describe('FilePatch', function() { ], }), ]; - const patch = new Patch({status: 'modified', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, 7); + const patch = new Patch({status: 'modified', hunks, marker}); const oldFile = new File({path: 'a.txt', mode: '100644'}); const newFile = new File({path: 'b.txt', mode: '100755'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -778,7 +615,7 @@ describe('FilePatch', function() { ' 0005\n' + '+0006\n' + ' 0007\n'; - assert.strictEqual(filePatch.toString(), expectedString); + assert.strictEqual(filePatch.toStringIn(buffer), expectedString); }); it('correctly formats a file with no newline at the end', function() { @@ -795,7 +632,8 @@ describe('FilePatch', function() { ], }), ]; - const patch = new Patch({status: 'modified', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, 2); + const patch = new Patch({status: 'modified', hunks, marker}); const oldFile = new File({path: 'a.txt', mode: '100644'}); const newFile = new File({path: 'b.txt', mode: '100755'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -808,7 +646,7 @@ describe('FilePatch', function() { ' 0000\n' + '+0001\n' + '\\ No newline at end of file\n'; - assert.strictEqual(filePatch.toString(), expectedString); + assert.strictEqual(filePatch.toStringIn(buffer), expectedString); }); describe('typechange file patches', function() { @@ -824,7 +662,8 @@ describe('FilePatch', function() { ], }), ]; - const patch = new Patch({status: 'added', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, 1); + const patch = new Patch({status: 'added', hunks, marker}); const oldFile = new File({path: 'a.txt', mode: '120000', symlink: 'dest.txt'}); const newFile = new File({path: 'a.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -844,7 +683,7 @@ describe('FilePatch', function() { '@@ -1,0 +1,2 @@\n' + '+0000\n' + '+0001\n'; - assert.strictEqual(filePatch.toString(), expectedString); + assert.strictEqual(filePatch.toStringIn(buffer), expectedString); }); it('handles typechange patches for a file replaced with a symlink', function() { @@ -859,7 +698,8 @@ describe('FilePatch', function() { ], }), ]; - const patch = new Patch({status: 'deleted', hunks, buffer, layers}); + const marker = markRange(layers.patch, 0, 1); + const patch = new Patch({status: 'deleted', hunks, marker}); const oldFile = new File({path: 'a.txt', mode: '100644'}); const newFile = new File({path: 'a.txt', mode: '120000', symlink: 'dest.txt'}); const filePatch = new FilePatch(oldFile, newFile, patch); @@ -879,15 +719,12 @@ describe('FilePatch', function() { '@@ -0,0 +1 @@\n' + '+dest.txt\n' + '\\ No newline at end of file\n'; - assert.strictEqual(filePatch.toString(), expectedString); + assert.strictEqual(filePatch.toStringIn(buffer), expectedString); }); }); }); it('has a nullFilePatch that stubs all FilePatch methods', function() { - const buffer = new TextBuffer({text: '0\n1\n2\n3\n'}); - const marker = markRange(buffer, 0, 1); - const nullFilePatch = FilePatch.createNull(); assert.isFalse(nullFilePatch.isPresent()); @@ -900,27 +737,18 @@ describe('FilePatch', function() { assert.isNull(nullFilePatch.getNewMode()); assert.isNull(nullFilePatch.getOldSymlink()); assert.isNull(nullFilePatch.getNewSymlink()); - assert.strictEqual(nullFilePatch.getByteSize(), 0); - assert.strictEqual(nullFilePatch.getBuffer().getText(), ''); assert.lengthOf(nullFilePatch.getAdditionRanges(), 0); assert.lengthOf(nullFilePatch.getDeletionRanges(), 0); assert.lengthOf(nullFilePatch.getNoNewlineRanges(), 0); - assert.lengthOf(nullFilePatch.getHunkLayer().getMarkers(), 0); - assert.lengthOf(nullFilePatch.getUnchangedLayer().getMarkers(), 0); - assert.lengthOf(nullFilePatch.getAdditionLayer().getMarkers(), 0); - assert.lengthOf(nullFilePatch.getDeletionLayer().getMarkers(), 0); - assert.lengthOf(nullFilePatch.getNoNewlineLayer().getMarkers(), 0); assert.isFalse(nullFilePatch.didChangeExecutableMode()); assert.isFalse(nullFilePatch.hasSymlink()); assert.isFalse(nullFilePatch.hasTypechange()); assert.isNull(nullFilePatch.getPath()); assert.isNull(nullFilePatch.getStatus()); assert.lengthOf(nullFilePatch.getHunks(), 0); - assert.isFalse(nullFilePatch.getStagePatchForLines(new Set([0])).isPresent()); - assert.isFalse(nullFilePatch.getStagePatchForHunk(new Hunk({regions: [], marker})).isPresent()); - assert.isFalse(nullFilePatch.getUnstagePatchForLines(new Set([0])).isPresent()); - assert.isFalse(nullFilePatch.getUnstagePatchForHunk(new Hunk({regions: [], marker})).isPresent()); - assert.strictEqual(nullFilePatch.toString(), ''); + assert.isFalse(nullFilePatch.buildStagePatchForLines(new Set([0])).isPresent()); + assert.isFalse(nullFilePatch.buildUnstagePatchForLines(new Set([0])).isPresent()); + assert.strictEqual(nullFilePatch.toStringIn(new TextBuffer()), ''); }); }); From c8c0ef04286fb3c16be509b5908b4b2de4ee586e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 15:51:06 -0500 Subject: [PATCH 0868/4053] :fire: dot only --- test/controllers/multi-file-patch-controller.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers/multi-file-patch-controller.test.js b/test/controllers/multi-file-patch-controller.test.js index 29b86f23ce..f33be04ea2 100644 --- a/test/controllers/multi-file-patch-controller.test.js +++ b/test/controllers/multi-file-patch-controller.test.js @@ -7,7 +7,7 @@ import MultiFilePatchController from '../../lib/controllers/multi-file-patch-con import * as reporterProxy from '../../lib/reporter-proxy'; import {cloneRepository, buildRepository} from '../helpers'; -describe.only('MultiFilePatchController', function() { +describe('MultiFilePatchController', function() { let atomEnv, repository, multiFilePatch; beforeEach(async function() { From 772c36d50da4d4dfd1a0f8c4dea538b8ab6e7d3a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 15:53:45 -0500 Subject: [PATCH 0869/4053] :fire: another dot only --- test/views/multi-file-patch-view.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 0524889884..de957868a7 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -8,7 +8,7 @@ import {nullFile} from '../../lib/models/patch/file'; import FilePatch from '../../lib/models/patch/file-patch'; import RefHolder from '../../lib/models/ref-holder'; -describe.only('MultiFilePatchView', function() { +describe('MultiFilePatchView', function() { let atomEnv, workspace, repository, filePatch; beforeEach(async function() { From bbfd5100d1f79a46a0b72d962c09a55eaa52fa76 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 7 Nov 2018 22:19:39 +0100 Subject: [PATCH 0870/4053] getpatchlayer --- lib/models/patch/multi-file-patch.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index ca7450add7..9230ea3a2a 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -32,6 +32,10 @@ export default class MultiFilePatch { return this.hunkLayer; } + getPatchLayer() { + return this.patchLayer; + } + getUnchangedLayer() { return this.unchangedLayer; } From ae8a4fb7490b58d20a9507d25b43131834b595e9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 7 Nov 2018 16:32:07 -0500 Subject: [PATCH 0871/4053] WIP MultiFilePatch tests --- lib/models/patch/multi-file-patch.js | 4 + test/models/patch/multi-file-patch.test.js | 228 ++++++++++++++++++++- 2 files changed, 231 insertions(+), 1 deletion(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index ca7450add7..200e757569 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -28,6 +28,10 @@ export default class MultiFilePatch { return this.buffer; } + getPatchLayer() { + return this.patchLayer; + } + getHunkLayer() { return this.hunkLayer; } diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 7a64d74614..2e350cd561 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -1,4 +1,5 @@ import {TextBuffer} from 'atom'; +import dedent from 'dedent-js'; import MultiFilePatch from '../../../lib/models/patch/multi-file-patch'; import FilePatch from '../../../lib/models/patch/file-patch'; @@ -6,6 +7,7 @@ import File from '../../../lib/models/patch/file'; import Patch from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; import {Unchanged, Addition, Deletion} from '../../../lib/models/patch/region'; +import {assertInFilePatch} from '../../helpers'; describe('MultiFilePatch', function() { let buffer, layers; @@ -89,6 +91,7 @@ describe('MultiFilePatch', function() { nextPatch.adoptBufferFrom(lastPatch); assert.strictEqual(nextPatch.getBuffer(), lastBuffer); + assert.strictEqual(nextPatch.getPatchLayer(), lastLayers.patch); assert.strictEqual(nextPatch.getHunkLayer(), lastLayers.hunk); assert.strictEqual(nextPatch.getUnchangedLayer(), lastLayers.unchanged); assert.strictEqual(nextPatch.getAdditionLayer(), lastLayers.addition); @@ -98,6 +101,229 @@ describe('MultiFilePatch', function() { assert.lengthOf(nextPatch.getHunkLayer().getMarkers(), 8); }); + it('generates a stage patch for arbitrary buffer rows', function() { + const filePatches = [ + buildFilePatchFixture(0), + buildFilePatchFixture(1), + buildFilePatchFixture(2), + buildFilePatchFixture(3), + ]; + const original = new MultiFilePatch(buffer, layers, filePatches); + const stagePatch = original.getStagePatchForLines(new Set([18, 24, 44, 45])); + + assert.strictEqual(stagePatch.getBuffer().getText(), dedent` + file-1 line-0 + file-1 line-1 + file-1 line-2 + file-1 line-3 + file-1 line-4 + file-1 line-6 + file-1 line-7 + file-3 line-0 + file-3 line-1 + file-3 line-2 + file-3 line-3 + `); + + assert.lengthOf(stagePatch.getFilePatches(), 2); + const [fp0, fp1] = stagePatch.getFilePatches(); + assert.strictEqual(fp0.getOldPath(), 'file-1.txt'); + assertInFilePatch(fp0, buffer).hunks( + { + startRow: 0, endRow: 3, + header: '@@ -0,4 +0,3 @@', + regions: [ + {kind: 'unchanged', string: ' file-1 line-0\n', range: [[0, 0], [0, 13]]}, + {kind: 'addition', string: '+file-1 line-1\n', range: [[1, 0], [1, 13]]}, + {kind: 'unchanged', string: ' file-1 line-2\n file-1 line-3\n', range: [[2, 0], [3, 13]]}, + ], + }, + { + startRow: 4, endRow: 8, + header: '@@ -10,3 +9,3 @@', + regions: [ + {kind: 'unchanged', string: ' file-1 line-4\n', range: [[4, 0], [4, 13]]}, + {kind: 'deletion', string: '-file-1 line-6\n', range: [[5, 0], [5, 13]]}, + {kind: 'unchanged', string: ' file-1 line-7\n', range: [[6, 0], [6, 13]]}, + ], + }, + ); + + assert.strictEqual(fp1.getOldPath(), 'file-3.txt'); + assertInFilePatch(fp1, buffer).hunks( + { + startRow: 9, endRow: 12, + header: '@@ -0,3 +0.3 @@', + regions: [ + {kind: 'unchanged', string: ' file-3 line-0\n', range: [[7, 0], [7, 13]]}, + {kind: 'addition', string: '+file-3 line-1\n', range: [[8, 0], [8, 13]]}, + {kind: 'deletion', string: '-file-3 line-2\n', range: [[9, 0], [9, 13]]}, + {kind: 'unchanged', string: ' file-3 line-3\n', range: [[10, 0], [10, 13]]}, + ], + }, + ); + }); + + // FIXME adapt these to the lifted method. + // describe('next selection range derivation', function() { + // it('selects the first change region after the highest buffer row', function() { + // const lastPatch = buildPatchFixture(); + // // Selected: + // // deletions (1-2) and partial addition (4 from 3-5) from hunk 0 + // // one deletion row (13 from 12-16) from the middle of hunk 1; + // // nothing in hunks 2 or 3 + // const lastSelectedRows = new Set([1, 2, 4, 5, 13]); + // + // const nBuffer = new TextBuffer({text: + // // 0 1 2 3 4 + // '0000\n0003\n0004\n0005\n0006\n' + + // // 5 6 7 8 9 10 11 12 13 14 15 + // '0007\n0008\n0009\n0010\n0011\n0012\n0014\n0015\n0016\n0017\n0018\n' + + // // 16 17 18 19 20 + // '0019\n0020\n0021\n0022\n0023\n' + + // // 21 22 23 + // '0024\n0025\n No newline at end of file\n', + // }); + // const nLayers = buildLayers(nBuffer); + // const nHunks = [ + // new Hunk({ + // oldStartRow: 3, oldRowCount: 3, newStartRow: 3, newRowCount: 5, // next row drift = +2 + // marker: markRange(nLayers.hunk, 0, 4), + // regions: [ + // new Unchanged(markRange(nLayers.unchanged, 0)), // 0 + // new Addition(markRange(nLayers.addition, 1)), // + 1 + // new Unchanged(markRange(nLayers.unchanged, 2)), // 2 + // new Addition(markRange(nLayers.addition, 3)), // + 3 + // new Unchanged(markRange(nLayers.unchanged, 4)), // 4 + // ], + // }), + // new Hunk({ + // oldStartRow: 12, oldRowCount: 9, newStartRow: 14, newRowCount: 7, // next row drift = +2 -2 = 0 + // marker: markRange(nLayers.hunk, 5, 15), + // regions: [ + // new Unchanged(markRange(nLayers.unchanged, 5)), // 5 + // new Addition(markRange(nLayers.addition, 6)), // +6 + // new Unchanged(markRange(nLayers.unchanged, 7, 9)), // 7 8 9 + // new Deletion(markRange(nLayers.deletion, 10, 13)), // -10 -11 -12 -13 + // new Addition(markRange(nLayers.addition, 14)), // +14 + // new Unchanged(markRange(nLayers.unchanged, 15)), // 15 + // ], + // }), + // new Hunk({ + // oldStartRow: 26, oldRowCount: 4, newStartRow: 26, newRowCount: 3, // next row drift = 0 -1 = -1 + // marker: markRange(nLayers.hunk, 16, 20), + // regions: [ + // new Unchanged(markRange(nLayers.unchanged, 16)), // 16 + // new Addition(markRange(nLayers.addition, 17)), // +17 + // new Deletion(markRange(nLayers.deletion, 18, 19)), // -18 -19 + // new Unchanged(markRange(nLayers.unchanged, 20)), // 20 + // ], + // }), + // new Hunk({ + // oldStartRow: 32, oldRowCount: 1, newStartRow: 31, newRowCount: 2, + // marker: markRange(nLayers.hunk, 22, 24), + // regions: [ + // new Unchanged(markRange(nLayers.unchanged, 22)), // 22 + // new Addition(markRange(nLayers.addition, 23)), // +23 + // new NoNewline(markRange(nLayers.noNewline, 24)), + // ], + // }), + // ]; + // const nextPatch = new Patch({status: 'modified', hunks: nHunks, buffer: nBuffer, layers: nLayers}); + // + // const nextRange = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); + // // Original buffer row 14 = the next changed row = new buffer row 11 + // assert.deepEqual(nextRange, [[11, 0], [11, Infinity]]); + // }); + // + // it('offsets the chosen selection index by hunks that were completely selected', function() { + // const buffer = buildBuffer(11); + // const layers = buildLayers(buffer); + // const lastPatch = new Patch({ + // status: 'modified', + // hunks: [ + // new Hunk({ + // oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 3, + // marker: markRange(layers.hunk, 0, 5), + // regions: [ + // new Unchanged(markRange(layers.unchanged, 0)), + // new Addition(markRange(layers.addition, 1, 2)), + // new Deletion(markRange(layers.deletion, 3, 4)), + // new Unchanged(markRange(layers.unchanged, 5)), + // ], + // }), + // new Hunk({ + // oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, + // marker: markRange(layers.hunk, 6, 11), + // regions: [ + // new Unchanged(markRange(layers.unchanged, 6)), + // new Addition(markRange(layers.addition, 7, 8)), + // new Deletion(markRange(layers.deletion, 9, 10)), + // new Unchanged(markRange(layers.unchanged, 11)), + // ], + // }), + // ], + // buffer, + // layers, + // }); + // // Select: + // // * all changes from hunk 0 + // // * partial addition (8 of 7-8) from hunk 1 + // const lastSelectedRows = new Set([1, 2, 3, 4, 8]); + // + // const nextBuffer = new TextBuffer({text: '0006\n0007\n0008\n0009\n0010\n0011\n'}); + // const nextLayers = buildLayers(nextBuffer); + // const nextPatch = new Patch({ + // status: 'modified', + // hunks: [ + // new Hunk({ + // oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, + // marker: markRange(nextLayers.hunk, 0, 5), + // regions: [ + // new Unchanged(markRange(nextLayers.unchanged, 0)), + // new Addition(markRange(nextLayers.addition, 1)), + // new Deletion(markRange(nextLayers.deletion, 3, 4)), + // new Unchanged(markRange(nextLayers.unchanged, 5)), + // ], + // }), + // ], + // buffer: nextBuffer, + // layers: nextLayers, + // }); + // + // const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); + // assert.deepEqual(range, [[3, 0], [3, Infinity]]); + // }); + // + // it('selects the first row of the first change of the patch if no rows were selected before', function() { + // const lastPatch = buildPatchFixture(); + // const lastSelectedRows = new Set(); + // + // const buffer = lastPatch.getBuffer(); + // const layers = buildLayers(buffer); + // const nextPatch = new Patch({ + // status: 'modified', + // hunks: [ + // new Hunk({ + // oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 4, + // marker: markRange(layers.hunk, 0, 4), + // regions: [ + // new Unchanged(markRange(layers.unchanged, 0)), + // new Addition(markRange(layers.addition, 1, 2)), + // new Deletion(markRange(layers.deletion, 3)), + // new Unchanged(markRange(layers.unchanged, 4)), + // ], + // }), + // ], + // buffer, + // layers, + // }); + // + // const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); + // assert.deepEqual(range, [[1, 0], [1, Infinity]]); + // }); + // }); + function buildFilePatchFixture(index) { const rowOffset = buffer.getLastRow(); for (let i = 0; i < 8; i++) { @@ -132,7 +358,7 @@ describe('MultiFilePatch', function() { ]; const marker = mark(layers.patch, 0, 7); - const patch = new Patch({status: 'modified', hunks, buffer, layers, marker}); + const patch = new Patch({status: 'modified', hunks, marker}); const oldFile = new File({path: `file-${index}.txt`, mode: '100644'}); const newFile = new File({path: `file-${index}.txt`, mode: '100644'}); From 2b1b4a9b7133da9d8f06b6bff4a827c7b26b0764 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 7 Nov 2018 22:51:07 +0100 Subject: [PATCH 0872/4053] probably should be for..in since we're interating through an array --- lib/models/patch/multi-file-patch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 9230ea3a2a..ce38ccc42a 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -231,7 +231,7 @@ export default class MultiFilePatch { const filePatches = new Set(); let lastFilePatch = null; - for (const row in sortedRowSet) { + for (const row of sortedRowSet) { // Because the rows are sorted, consecutive rows will almost certainly belong to the same patch, so we can save // many avoidable marker index lookups by comparing with the last. if (lastFilePatch && lastFilePatch.containsRow(row)) { From 14184980799a854e5ec31fe6fa5796ee8ef2d9ce Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 7 Nov 2018 13:53:16 -0800 Subject: [PATCH 0873/4053] fix ChangedFileContainer tests --- .../containers/changed-file-container.test.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/containers/changed-file-container.test.js b/test/containers/changed-file-container.test.js index 40d4c09b21..3e40987e79 100644 --- a/test/containers/changed-file-container.test.js +++ b/test/containers/changed-file-container.test.js @@ -54,45 +54,45 @@ describe('ChangedFileContainer', function() { assert.isTrue(wrapper.find('LoadingView').exists()); }); - it('renders a FilePatchView', async function() { + it('renders a MultiFilePatchController', async function() { const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - await assert.async.isTrue(wrapper.update().find('FilePatchView').exists()); + await assert.async.isTrue(wrapper.update().find('MultiFilePatchController').exists()); }); it('adopts the buffer from the previous FilePatch when a new one arrives', async function() { const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - await assert.async.isTrue(wrapper.update().find('FilePatchController').exists()); + await assert.async.isTrue(wrapper.update().find('MultiFilePatchController').exists()); - const prevPatch = wrapper.find('FilePatchController').prop('filePatch'); + const prevPatch = wrapper.find('MultiFilePatchController').prop('multiFilePatch'); const prevBuffer = prevPatch.getBuffer(); await fs.writeFile(path.join(repository.getWorkingDirectoryPath(), 'a.txt'), 'changed\nagain\n'); repository.refresh(); - await assert.async.notStrictEqual(wrapper.update().find('FilePatchController').prop('filePatch'), prevPatch); + await assert.async.notStrictEqual(wrapper.update().find('MultiFilePatchController').prop('multiFilePatch'), prevPatch); - const nextBuffer = wrapper.find('FilePatchController').prop('filePatch').getBuffer(); + const nextBuffer = wrapper.find('MultiFilePatchController').prop('multiFilePatch').getBuffer(); assert.strictEqual(nextBuffer, prevBuffer); }); it('does not adopt a buffer from an unchanged patch', async function() { const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - await assert.async.isTrue(wrapper.update().find('FilePatchController').exists()); + await assert.async.isTrue(wrapper.update().find('MultiFilePatchController').exists()); - const prevPatch = wrapper.find('FilePatchController').prop('filePatch'); + const prevPatch = wrapper.find('MultiFilePatchController').prop('multiFilePatch'); sinon.spy(prevPatch, 'adoptBufferFrom'); wrapper.setProps({}); assert.isFalse(prevPatch.adoptBufferFrom.called); - const nextPatch = wrapper.find('FilePatchController').prop('filePatch'); + const nextPatch = wrapper.find('MultiFilePatchController').prop('multiFilePatch'); assert.strictEqual(nextPatch, prevPatch); }); it('passes unrecognized props to the FilePatchView', async function() { const extra = Symbol('extra'); const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged', extra})); - await assert.async.strictEqual(wrapper.update().find('FilePatchView').prop('extra'), extra); + await assert.async.strictEqual(wrapper.update().find('MultiFilePatchView').prop('extra'), extra); }); }); From 8a49372279866da3fdb39c6873a841579de1af8b Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 7 Nov 2018 17:35:21 -0800 Subject: [PATCH 0874/4053] fix some of the RootController tests --- test/controllers/root-controller.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 5cd8fe293d..1c1071a31b 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -495,6 +495,7 @@ describe('RootController', function() { fs.writeFileSync(path.join(workdirPath, 'a.txt'), 'modification\n'); const unstagedFilePatch = await repository.getFilePatchForPath('a.txt'); + const unstagedFilePatch = multiFilePatch.getFilePatches()[0]; const editor = await workspace.open(path.join(workdirPath, 'a.txt')); @@ -511,14 +512,14 @@ describe('RootController', function() { sinon.stub(notificationManager, 'addError'); // unmodified buffer const hunkLines = unstagedFilePatch.getHunks()[0].getBufferRows(); - await wrapper.instance().discardLines(unstagedFilePatch, new Set([hunkLines[0]])); + await wrapper.instance().discardLines(multiFilePatch, new Set([hunkLines[0]])); assert.isTrue(repository.applyPatchToWorkdir.calledOnce); assert.isFalse(notificationManager.addError.called); // modified buffer repository.applyPatchToWorkdir.reset(); editor.setText('modify contents'); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows())); + await wrapper.instance().discardLines(multiFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows())); assert.isFalse(repository.applyPatchToWorkdir.called); const notificationArgs = notificationManager.addError.args[0]; assert.equal(notificationArgs[0], 'Cannot discard lines.'); From 680f3731fc39bc4360f4d1307263bcf6623533d3 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 7 Nov 2018 17:37:22 -0800 Subject: [PATCH 0875/4053] WIP - why `RootController` tests no worky --- test/controllers/root-controller.test.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 1c1071a31b..9348ea779f 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -20,7 +20,6 @@ import * as reporterProxy from '../../lib/reporter-proxy'; import RootController from '../../lib/controllers/root-controller'; -describe('RootController', function() { let atomEnv, app; let workspace, commandRegistry, notificationManager, tooltips, config, confirm, deserializers, grammars, project; let workdirContextPool; @@ -494,7 +493,7 @@ describe('RootController', function() { const repository = await buildRepository(workdirPath); fs.writeFileSync(path.join(workdirPath, 'a.txt'), 'modification\n'); - const unstagedFilePatch = await repository.getFilePatchForPath('a.txt'); + const multiFilePatch = await repository.getFilePatchForPath('a.txt'); const unstagedFilePatch = multiFilePatch.getFilePatches()[0]; const editor = await workspace.open(path.join(workdirPath, 'a.txt')); @@ -561,14 +560,16 @@ describe('RootController', function() { describe('undoLastDiscard(partialDiscardFilePath)', () => { describe('when partialDiscardFilePath is not null', () => { - let unstagedFilePatch, repository, absFilePath, wrapper; + let unstagedFilePatch, multiFilePatch, repository, absFilePath, wrapper; beforeEach(async () => { const workdirPath = await cloneRepository('multi-line-file'); repository = await buildRepository(workdirPath); absFilePath = path.join(workdirPath, 'sample.js'); fs.writeFileSync(absFilePath, 'foo\nbar\nbaz\n'); - unstagedFilePatch = await repository.getFilePatchForPath('sample.js'); + multiFilePatch = await repository.getFilePatchForPath('sample.js'); + + unstagedFilePatch = multiFilePatch.getFilePatches()[0]; app = React.cloneElement(app, {repository}); wrapper = shallow(app); @@ -581,14 +582,16 @@ describe('RootController', function() { it('reverses last discard for file path', async () => { const contents1 = fs.readFileSync(absFilePath, 'utf8'); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); + await wrapper.instance().discardLines(multiFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2)), repository); const contents2 = fs.readFileSync(absFilePath, 'utf8'); + assert.notEqual(contents1, contents2); await repository.refresh(); - unstagedFilePatch = await repository.getFilePatchForPath('sample.js'); + multiFilePatch = await repository.getFilePatchForPath('sample.js'); + wrapper.setState({filePatch: unstagedFilePatch}); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(2, 4))); + await wrapper.instance().discardLines(multiFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(2, 4))); const contents3 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents2, contents3); @@ -600,7 +603,7 @@ describe('RootController', function() { it('does not undo if buffer is modified', async () => { const contents1 = fs.readFileSync(absFilePath, 'utf8'); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); + await wrapper.instance().discardLines(multiFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); const contents2 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents1, contents2); From 1513921d647bbdc65993f8955861c89b96d499fd Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 7 Nov 2018 18:04:31 -0800 Subject: [PATCH 0876/4053] fix test syntax oops --- test/controllers/root-controller.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 9348ea779f..31ff53cf6d 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -20,6 +20,7 @@ import * as reporterProxy from '../../lib/reporter-proxy'; import RootController from '../../lib/controllers/root-controller'; +describe('RootController', function() { let atomEnv, app; let workspace, commandRegistry, notificationManager, tooltips, config, confirm, deserializers, grammars, project; let workdirContextPool; @@ -486,6 +487,7 @@ import RootController from '../../lib/controllers/root-controller'; }); }); + // these tests no worky describe('discarding and restoring changed lines', () => { describe('discardLines(filePatch, lines)', () => { it('only discards lines if buffer is unmodified, otherwise notifies user', async () => { From 43199eed86510ba0b599f3cf9e97e0269b279da9 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 7 Nov 2018 16:39:16 -0800 Subject: [PATCH 0877/4053] Correctly invalidate cache in `applyPatchToIndex` based on MFP file paths --- lib/models/repository-states/present.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 38118725cb..b8592892a0 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -273,11 +273,16 @@ export default class Present extends State { ); } - applyPatchToIndex(filePatch) { + applyPatchToIndex(multiFilePatch) { + const filePathSet = multiFilePatch.getFilePatches().reduce((pathSet, filePatch) => { + pathSet.add(filePatch.getOldPath()); + pathSet.add(filePatch.getNewPath()); + return pathSet; + }, new Set()); return this.invalidate( - () => Keys.cacheOperationKeys([filePatch.getOldPath(), filePatch.getNewPath()]), + () => Keys.cacheOperationKeys(Array.from(filePathSet)), () => { - const patchStr = filePatch.toString(); + const patchStr = multiFilePatch.toString(); return this.git().applyPatch(patchStr, {index: true}); }, ); From 05dff689962469201bf21faa2356f355df0f18df Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 7 Nov 2018 16:53:44 -0800 Subject: [PATCH 0878/4053] :fire: duplicate `getPatchLayer` --- lib/models/patch/multi-file-patch.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 93255307d1..f1035d6709 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -36,10 +36,6 @@ export default class MultiFilePatch { return this.hunkLayer; } - getPatchLayer() { - return this.patchLayer; - } - getUnchangedLayer() { return this.unchangedLayer; } From 15039ba608f5f04b14f186da53e57184386681f8 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 7 Nov 2018 17:17:21 -0800 Subject: [PATCH 0879/4053] :fire: unused import --- lib/models/repository-states/present.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index b8592892a0..eba9a85290 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -14,7 +14,6 @@ import BranchSet from '../branch-set'; import Remote from '../remote'; import RemoteSet from '../remote-set'; import Commit from '../commit'; -import MultiFilePatch from '../patch/multi-file-patch'; import OperationStates from '../operation-states'; import {addEvent} from '../../reporter-proxy'; import {filePathEndsWith} from '../../helpers'; From 344f4b983cd8e4248cc4d8c7216280454b353019 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 7 Nov 2018 18:27:06 -0800 Subject: [PATCH 0880/4053] Clear and remark patch layer in `adoptBufferFrom` Co-Authored-By: Ash Wilson --- lib/models/patch/multi-file-patch.js | 23 +++++++++++------------ lib/models/patch/patch.js | 8 ++++++++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index f1035d6709..87f3ebdde7 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -161,18 +161,27 @@ export default class MultiFilePatch { } adoptBufferFrom(lastMultiFilePatch) { + lastMultiFilePatch.getPatchLayer().clear(); lastMultiFilePatch.getHunkLayer().clear(); lastMultiFilePatch.getUnchangedLayer().clear(); lastMultiFilePatch.getAdditionLayer().clear(); lastMultiFilePatch.getDeletionLayer().clear(); lastMultiFilePatch.getNoNewlineLayer().clear(); + this.filePatchesByMarker.clear(); + this.hunksByMarker.clear(); + const nextBuffer = lastMultiFilePatch.getBuffer(); nextBuffer.setText(this.getBuffer().getText()); - for (const patch of this.getFilePatches()) { - for (const hunk of patch.getHunks()) { + for (const filePatch of this.getFilePatches()) { + filePatch.getPatch().reMarkOn(lastMultiFilePatch.getPatchLayer()); + this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); + + for (const hunk of filePatch.getHunks()) { hunk.reMarkOn(lastMultiFilePatch.getHunkLayer()); + this.hunksByMarker.set(hunk.getMarker(), hunk); + for (const region of hunk.getRegions()) { const target = region.when({ unchanged: () => lastMultiFilePatch.getUnchangedLayer(), @@ -185,16 +194,6 @@ export default class MultiFilePatch { } } - this.filePatchesByMarker.clear(); - this.hunksByMarker.clear(); - - for (const filePatch of this.filePatches) { - this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); - for (const hunk of filePatch.getHunks()) { - this.hunksByMarker.set(hunk.getMarker(), hunk); - } - } - this.patchLayer = lastMultiFilePatch.getPatchLayer(); this.hunkLayer = lastMultiFilePatch.getHunkLayer(); this.unchangedLayer = lastMultiFilePatch.getUnchangedLayer(); diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 20b0363bba..b2e4efd6ec 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -24,6 +24,10 @@ export default class Patch { return this.marker; } + getRange() { + return this.getMarker().getRange(); + } + getStartRange() { const startPoint = this.getMarker().getRange().start; return Range.fromObject([startPoint, startPoint]); @@ -41,6 +45,10 @@ export default class Patch { return this.marker.getRange().intersectsRow(row); } + reMarkOn(markable) { + this.marker = markable.markRange(this.getRange(), {invalidate: 'never', exclusive: false}); + } + getMaxLineNumberWidth() { const lastHunk = this.hunks[this.hunks.length - 1]; return lastHunk ? lastHunk.getMaxLineNumberWidth() : 0; From eb714c545404edede5f474d483e0bfa633333d27 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 7 Nov 2018 18:58:35 -0800 Subject: [PATCH 0881/4053] Add missing methods to NullPatch --- lib/models/patch/patch.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index b2e4efd6ec..d660309415 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -293,6 +293,10 @@ class NullPatch { return this.marker; } + getRange() { + return this.getMarker().getRange(); + } + getStartRange() { return Range.fromObject([[0, 0], [0, 0]]); } @@ -309,6 +313,10 @@ class NullPatch { return false; } + reMarkOn(markable) { + this.marker = markable.markRange(this.getRange(), {invalidate: 'never', exclusive: false}); + } + getMaxLineNumberWidth() { return 0; } From 8d9aa251abd161fad254203de24dce7b17b37610 Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 8 Nov 2018 19:49:49 +0900 Subject: [PATCH 0882/4053] Restyle file and hunk headers --- styles/commit-preview-view.less | 20 -------------------- styles/file-patch-view.less | 21 +++++++++++++++++---- styles/hunk-header-view.less | 3 +++ 3 files changed, 20 insertions(+), 24 deletions(-) delete mode 100644 styles/commit-preview-view.less diff --git a/styles/commit-preview-view.less b/styles/commit-preview-view.less deleted file mode 100644 index aff47bd9a6..0000000000 --- a/styles/commit-preview-view.less +++ /dev/null @@ -1,20 +0,0 @@ -@import "variables"; - -.github-CommitPreview-root { - overflow: auto; - z-index: 1; // Fixes scrollbar on macOS - - .github-FilePatchView { - border-bottom: 1px solid @base-border-color; - - &:last-child { - border-bottom: none; - } - - & + .github-FilePatchView { - margin-top: @component-padding; - border-top: 1px solid @base-border-color; - } - } - -} diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 918c5e891c..2e3f73e6db 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -2,8 +2,7 @@ @import "octicon-utf-codes"; @import "octicon-mixins"; -@hunk-fg-color: @text-color-subtle; -@hunk-bg-color: @pane-item-background-color; +@header-bg-color: mix(@syntax-text-color, @syntax-background-color, 6%); .github-FilePatchView { display: flex; @@ -24,14 +23,28 @@ padding: @component-padding; } + // TODO: Use better selector + .react-atom-decoration { + padding: @component-padding; + padding-left: 0; + background-color: @syntax-background-color; + + & + .react-atom-decoration { + padding-top: 0; + } + } + &-header { display: flex; justify-content: space-between; align-items: center; padding: @component-padding/2; padding-left: @component-padding; - border-bottom: 1px solid @base-border-color; - background-color: @pane-item-background-color; + border: 1px solid @base-border-color; + border-radius: @component-border-radius; + font-family: system-ui; + background-color: @header-bg-color; + cursor: default; .btn { font-size: .9em; diff --git a/styles/hunk-header-view.less b/styles/hunk-header-view.less index a3f88c8ba0..18e682fb87 100644 --- a/styles/hunk-header-view.less +++ b/styles/hunk-header-view.less @@ -9,6 +9,8 @@ display: flex; align-items: stretch; font-size: .9em; + border: 1px solid @base-border-color; + border-radius: @component-border-radius; background-color: @hunk-bg-color; cursor: default; @@ -65,6 +67,7 @@ .github-HunkHeaderView--isSelected { color: contrast(@button-background-color-selected); background-color: @button-background-color-selected; + border-color: transparent; .github-HunkHeaderView-title { color: inherit; } From 1b1cbd233f5894be7d3dc539d01cf9f8a2e54095 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 09:29:40 -0500 Subject: [PATCH 0883/4053] Remove long-obsolete .setState() calls in RootController tests Co-Authored-By: Tilde Ann Thurium --- test/controllers/root-controller.test.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 31ff53cf6d..cec5601795 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -575,11 +575,6 @@ describe('RootController', function() { app = React.cloneElement(app, {repository}); wrapper = shallow(app); - wrapper.setState({ - filePath: 'sample.js', - filePatch: unstagedFilePatch, - stagingStatus: 'unstaged', - }); }); it('reverses last discard for file path', async () => { @@ -592,7 +587,6 @@ describe('RootController', function() { multiFilePatch = await repository.getFilePatchForPath('sample.js'); - wrapper.setState({filePatch: unstagedFilePatch}); await wrapper.instance().discardLines(multiFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(2, 4))); const contents3 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents2, contents3); @@ -618,7 +612,6 @@ describe('RootController', function() { await repository.refresh(); unstagedFilePatch = await repository.getFilePatchForPath('sample.js'); - wrapper.setState({filePatch: unstagedFilePatch}); await wrapper.instance().undoLastDiscard('sample.js'); const notificationArgs = notificationManager.addError.args[0]; assert.equal(notificationArgs[0], 'Cannot undo last discard.'); @@ -638,8 +631,6 @@ describe('RootController', function() { fs.writeFileSync(absFilePath, contents2 + change); await repository.refresh(); - unstagedFilePatch = await repository.getFilePatchForPath('sample.js'); - wrapper.setState({filePatch: unstagedFilePatch}); await wrapper.instance().undoLastDiscard('sample.js'); await assert.async.equal(fs.readFileSync(absFilePath, 'utf8'), contents1 + change); @@ -658,8 +649,6 @@ describe('RootController', function() { fs.writeFileSync(absFilePath, change + contents2); await repository.refresh(); - unstagedFilePatch = await repository.getFilePatchForPath('sample.js'); - wrapper.setState({filePatch: unstagedFilePatch}); // click 'Cancel' confirm.returns(2); @@ -714,8 +703,6 @@ describe('RootController', function() { // this would occur in the case of garbage collection cleaning out the blob await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); await repository.refresh(); - unstagedFilePatch = await repository.getFilePatchForPath('sample.js'); - wrapper.setState({filePatch: unstagedFilePatch}); const {beforeSha} = await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(2, 4))); // remove blob from git object store From 8c004aadee4055f9841dc389e994b502511e349e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 09:30:15 -0500 Subject: [PATCH 0884/4053] Update RootController discard and undo tests to use MultiFilePatches Co-Authored-By: Tilde Ann Thurium --- test/controllers/root-controller.test.js | 30 +++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index cec5601795..e1721eb08e 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -562,7 +562,8 @@ describe('RootController', function() { describe('undoLastDiscard(partialDiscardFilePath)', () => { describe('when partialDiscardFilePath is not null', () => { - let unstagedFilePatch, multiFilePatch, repository, absFilePath, wrapper; + let multiFilePatch, repository, absFilePath, wrapper; + beforeEach(async () => { const workdirPath = await cloneRepository('multi-line-file'); repository = await buildRepository(workdirPath); @@ -571,15 +572,15 @@ describe('RootController', function() { fs.writeFileSync(absFilePath, 'foo\nbar\nbaz\n'); multiFilePatch = await repository.getFilePatchForPath('sample.js'); - unstagedFilePatch = multiFilePatch.getFilePatches()[0]; - app = React.cloneElement(app, {repository}); wrapper = shallow(app); }); it('reverses last discard for file path', async () => { const contents1 = fs.readFileSync(absFilePath, 'utf8'); - await wrapper.instance().discardLines(multiFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2)), repository); + + const rows0 = new Set(multiFilePatch.getFilePatches()[0].getHunks()[0].getBufferRows().slice(0, 2)); + await wrapper.instance().discardLines(multiFilePatch, rows0, repository); const contents2 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents1, contents2); @@ -587,7 +588,8 @@ describe('RootController', function() { multiFilePatch = await repository.getFilePatchForPath('sample.js'); - await wrapper.instance().discardLines(multiFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(2, 4))); + const rows1 = new Set(multiFilePatch.getFilePatches()[0].getHunks()[0].getBufferRows().slice(2, 4)); + await wrapper.instance().discardLines(multiFilePatch, rows1); const contents3 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents2, contents3); @@ -599,7 +601,8 @@ describe('RootController', function() { it('does not undo if buffer is modified', async () => { const contents1 = fs.readFileSync(absFilePath, 'utf8'); - await wrapper.instance().discardLines(multiFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); + const rows0 = new Set(multiFilePatch.getFilePatches()[0].getHunks()[0].getBufferRows().slice(0, 2)); + await wrapper.instance().discardLines(multiFilePatch, rows0); const contents2 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents1, contents2); @@ -611,7 +614,6 @@ describe('RootController', function() { sinon.stub(notificationManager, 'addError'); await repository.refresh(); - unstagedFilePatch = await repository.getFilePatchForPath('sample.js'); await wrapper.instance().undoLastDiscard('sample.js'); const notificationArgs = notificationManager.addError.args[0]; assert.equal(notificationArgs[0], 'Cannot undo last discard.'); @@ -622,7 +624,8 @@ describe('RootController', function() { describe('when file content has changed since last discard', () => { it('successfully undoes discard if changes do not conflict', async () => { const contents1 = fs.readFileSync(absFilePath, 'utf8'); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); + const rows0 = new Set(multiFilePatch.getFilePatches()[0].getHunks()[0].getBufferRows().slice(0, 2)); + await wrapper.instance().discardLines(multiFilePatch, rows0); const contents2 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents1, contents2); @@ -640,7 +643,8 @@ describe('RootController', function() { await repository.git.exec(['config', 'merge.conflictstyle', 'diff3']); const contents1 = fs.readFileSync(absFilePath, 'utf8'); - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); + const rows0 = new Set(multiFilePatch.getFilePatches()[0].getHunks()[0].getBufferRows().slice(0, 2)); + await wrapper.instance().discardLines(multiFilePatch, rows0); const contents2 = fs.readFileSync(absFilePath, 'utf8'); assert.notEqual(contents1, contents2); @@ -701,9 +705,13 @@ describe('RootController', function() { it('clears the discard history if the last blob is no longer valid', async () => { // this would occur in the case of garbage collection cleaning out the blob - await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(0, 2))); + const rows0 = new Set(multiFilePatch.getFilePatches()[0].getHunks()[0].getBufferRows().slice(0, 2)); + await wrapper.instance().discardLines(multiFilePatch, rows0); await repository.refresh(); - const {beforeSha} = await wrapper.instance().discardLines(unstagedFilePatch, new Set(unstagedFilePatch.getHunks()[0].getBufferRows().slice(2, 4))); + + const multiFilePatch1 = await repository.getFilePatchForPath('sample.js'); + const rows1 = new Set(multiFilePatch1.getFilePatches()[0].getHunks()[0].getBufferRows().slice(2, 4)); + const {beforeSha} = await wrapper.instance().discardLines(multiFilePatch1, rows1); // remove blob from git object store fs.unlinkSync(path.join(repository.getGitDirectoryPath(), 'objects', beforeSha.slice(0, 2), beforeSha.slice(2))); From f3809cb395e4dcbeeddaa7ee05bd96304c52a2df Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 09:30:41 -0500 Subject: [PATCH 0885/4053] Accept a MultiFilePatch in Present::applyPatchToWorkdir() Co-Authored-By: Tilde Ann Thurium --- lib/models/patch/multi-file-patch.js | 11 +++++++++++ lib/models/repository-states/present.js | 13 ++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 87f3ebdde7..b56422b098 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -56,6 +56,17 @@ export default class MultiFilePatch { return this.filePatches; } + getPathSet() { + return this.getFilePatches().reduce((pathSet, filePatch) => { + for (const file of [filePatch.getOldFile(), filePatch.getNewFile()]) { + if (file.isPresent()) { + pathSet.add(file.getPath()); + } + } + return pathSet; + }, new Set()); + } + getFilePatchAt(bufferRow) { const [marker] = this.patchLayer.findMarkers({intersectsRow: bufferRow}); return this.filePatchesByMarker.get(marker); diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index eba9a85290..22a803c9e9 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -273,13 +273,8 @@ export default class Present extends State { } applyPatchToIndex(multiFilePatch) { - const filePathSet = multiFilePatch.getFilePatches().reduce((pathSet, filePatch) => { - pathSet.add(filePatch.getOldPath()); - pathSet.add(filePatch.getNewPath()); - return pathSet; - }, new Set()); return this.invalidate( - () => Keys.cacheOperationKeys(Array.from(filePathSet)), + () => Keys.cacheOperationKeys(Array.from(multiFilePatch.getPathSet())), () => { const patchStr = multiFilePatch.toString(); return this.git().applyPatch(patchStr, {index: true}); @@ -287,11 +282,11 @@ export default class Present extends State { ); } - applyPatchToWorkdir(filePatch) { + applyPatchToWorkdir(multiFilePatch) { return this.invalidate( - () => Keys.workdirOperationKeys([filePatch.getOldPath(), filePatch.getNewPath()]), + () => Keys.workdirOperationKeys(Array.from(multiFilePatch.getPathSet())), () => { - const patchStr = filePatch.toString(); + const patchStr = multiFilePatch.toString(); return this.git().applyPatch(patchStr); }, ); From 86a8da119b5c91670641aceafb3eb164c4d118e6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 10:54:27 -0500 Subject: [PATCH 0886/4053] Construct partial stage patches on an existing non-empty TextBuffer --- lib/models/patch/patch.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index d660309415..3d2ee1ea10 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -63,7 +63,8 @@ export default class Patch { } buildStagePatchForLines(originalBuffer, nextLayeredBuffer, rowSet) { - const builder = new BufferBuilder(originalBuffer, nextLayeredBuffer); + const originalBaseOffset = this.getMarker().getRange().start.row; + const builder = new BufferBuilder(originalBuffer, originalBaseOffset, nextLayeredBuffer); const hunks = []; let newRowDelta = 0; @@ -142,7 +143,7 @@ export default class Patch { const buffer = builder.getBuffer(); const layers = builder.getLayers(); - const marker = layers.patch.markRange([[0, 0], [buffer.getLastRow(), Infinity]]); + const marker = layers.patch.markRange([[0, 0], [buffer.getLastRow() - 1, Infinity]]); const wholeFile = rowSet.size === this.changedLineCount; const status = this.getStatus() === 'deleted' && !wholeFile ? 'modified' : this.getStatus(); @@ -150,7 +151,8 @@ export default class Patch { } buildUnstagePatchForLines(originalBuffer, nextLayeredBuffer, rowSet) { - const builder = new BufferBuilder(originalBuffer, nextLayeredBuffer); + const originalBaseOffset = this.getMarker().getRange().start.row; + const builder = new BufferBuilder(originalBuffer, originalBaseOffset, nextLayeredBuffer); const hunks = []; let newRowDelta = 0; @@ -363,7 +365,7 @@ class NullPatch { } class BufferBuilder { - constructor(original, nextLayeredBuffer) { + constructor(original, originalBaseOffset, nextLayeredBuffer) { this.originalBuffer = original; this.buffer = nextLayeredBuffer.buffer; @@ -376,11 +378,14 @@ class BufferBuilder { ['patch', nextLayeredBuffer.layers.patch], ]); - this.offset = 0; + // The ranges provided to builder methods are expected to be valid within the original buffer. Account for + // the position of the Patch within its original TextBuffer, and any existing content already on the next + // TextBuffer. + this.offset = this.buffer.getLastRow() - originalBaseOffset; this.hunkBufferText = ''; this.hunkRowCount = 0; - this.hunkStartOffset = 0; + this.hunkStartOffset = this.offset; this.hunkRegions = []; this.hunkRange = null; From 181adf8c8ed84ee749b1abd8d15347ed2d452ad9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 10:55:15 -0500 Subject: [PATCH 0887/4053] Passing test for cross-file partial stage patch generation --- test/models/patch/multi-file-patch.test.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 2e350cd561..7915983964 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -109,7 +109,7 @@ describe('MultiFilePatch', function() { buildFilePatchFixture(3), ]; const original = new MultiFilePatch(buffer, layers, filePatches); - const stagePatch = original.getStagePatchForLines(new Set([18, 24, 44, 45])); + const stagePatch = original.getStagePatchForLines(new Set([9, 14, 25, 26])); assert.strictEqual(stagePatch.getBuffer().getText(), dedent` file-1 line-0 @@ -123,15 +123,16 @@ describe('MultiFilePatch', function() { file-3 line-1 file-3 line-2 file-3 line-3 + `); assert.lengthOf(stagePatch.getFilePatches(), 2); const [fp0, fp1] = stagePatch.getFilePatches(); assert.strictEqual(fp0.getOldPath(), 'file-1.txt'); - assertInFilePatch(fp0, buffer).hunks( + assertInFilePatch(fp0, stagePatch.getBuffer()).hunks( { startRow: 0, endRow: 3, - header: '@@ -0,4 +0,3 @@', + header: '@@ -0,3 +0,4 @@', regions: [ {kind: 'unchanged', string: ' file-1 line-0\n', range: [[0, 0], [0, 13]]}, {kind: 'addition', string: '+file-1 line-1\n', range: [[1, 0], [1, 13]]}, @@ -139,8 +140,8 @@ describe('MultiFilePatch', function() { ], }, { - startRow: 4, endRow: 8, - header: '@@ -10,3 +9,3 @@', + startRow: 4, endRow: 6, + header: '@@ -10,3 +11,2 @@', regions: [ {kind: 'unchanged', string: ' file-1 line-4\n', range: [[4, 0], [4, 13]]}, {kind: 'deletion', string: '-file-1 line-6\n', range: [[5, 0], [5, 13]]}, @@ -150,10 +151,10 @@ describe('MultiFilePatch', function() { ); assert.strictEqual(fp1.getOldPath(), 'file-3.txt'); - assertInFilePatch(fp1, buffer).hunks( + assertInFilePatch(fp1, stagePatch.getBuffer()).hunks( { - startRow: 9, endRow: 12, - header: '@@ -0,3 +0.3 @@', + startRow: 7, endRow: 10, + header: '@@ -0,3 +0,3 @@', regions: [ {kind: 'unchanged', string: ' file-3 line-0\n', range: [[7, 0], [7, 13]]}, {kind: 'addition', string: '+file-3 line-1\n', range: [[8, 0], [8, 13]]}, From 972cab79a06ef6ac53d0f54d7ca5214969d8b0b0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 10:55:41 -0500 Subject: [PATCH 0888/4053] Preserve FilePatch order in getFilePatchesContaining() --- lib/models/patch/multi-file-patch.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index b56422b098..3d44201a16 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -79,7 +79,7 @@ export default class MultiFilePatch { getStagePatchForLines(selectedLineSet) { const nextLayeredBuffer = this.buildLayeredBuffer(); - const nextFilePatches = Array.from(this.getFilePatchesContaining(selectedLineSet), fp => { + const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { return fp.buildStagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); }); @@ -96,7 +96,7 @@ export default class MultiFilePatch { getUnstagePatchForLines(selectedLineSet) { const nextLayeredBuffer = this.buildLayeredBuffer(); - const nextFilePatches = Array.from(this.getFilePatchesContaining(selectedLineSet), fp => { + const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { return fp.buildUnstagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); }); @@ -237,9 +237,10 @@ export default class MultiFilePatch { */ getFilePatchesContaining(rowSet) { const sortedRowSet = Array.from(rowSet); - sortedRowSet.sort((a, b) => b - a); + sortedRowSet.sort((a, b) => a - b); - const filePatches = new Set(); + const filePatches = []; + const seen = new Set(); let lastFilePatch = null; for (const row of sortedRowSet) { // Because the rows are sorted, consecutive rows will almost certainly belong to the same patch, so we can save @@ -249,7 +250,12 @@ export default class MultiFilePatch { } lastFilePatch = this.getFilePatchAt(row); - filePatches.add(lastFilePatch); + if (seen.has(lastFilePatch)) { + continue; + } + + filePatches.push(lastFilePatch); + seen.add(lastFilePatch); } return filePatches; From 836207f209065ee54ee5ea9ef33c3b06cd434b63 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 8 Nov 2018 09:33:27 -0800 Subject: [PATCH 0889/4053] make CommitPreviewContainer tests pass Co-Authored-By: Vanessa Yuen --- test/containers/commit-preview-container.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/containers/commit-preview-container.test.js b/test/containers/commit-preview-container.test.js index b33b11f169..1feadd8e07 100644 --- a/test/containers/commit-preview-container.test.js +++ b/test/containers/commit-preview-container.test.js @@ -19,8 +19,11 @@ describe('CommitPreviewContainer', function() { }); function buildApp(override = {}) { + const props = { repository, + ...atomEnv, + destroy: () => {}, ...override, }; From 07cee58cc9cb5476ccb8ee6fe6a5772089b7197b Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 8 Nov 2018 17:06:59 +0100 Subject: [PATCH 0890/4053] fix nullpatch being displayed --- lib/models/patch/multi-file-patch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 3d44201a16..78003fd42f 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -262,7 +262,7 @@ export default class MultiFilePatch { } anyPresent() { - return this.buffer !== null; + return this.buffer !== null && this.filePatches.some(fp => fp.isPresent()); } didAnyChangeExecutableMode() { From ddd0630b6afde612dbba70b681bd671e8271bb56 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 8 Nov 2018 18:34:17 +0100 Subject: [PATCH 0891/4053] fix commitpreviewitem tests Co-Authored-By: Tilde Ann Thurium --- test/items/commit-preview-item.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/items/commit-preview-item.test.js b/test/items/commit-preview-item.test.js index e550c176a4..15af0dcadd 100644 --- a/test/items/commit-preview-item.test.js +++ b/test/items/commit-preview-item.test.js @@ -28,6 +28,11 @@ describe('CommitPreviewItem', function() { function buildPaneApp(override = {}) { const props = { workdirContextPool: pool, + workspace: atomEnv.workspace, + commands: atomEnv.commands, + keymaps: atomEnv.keymaps, + tooltips: atomEnv.tooltips, + config: atomEnv.config, ...override, }; From 6bfe0255c9584714dd1336325669893c930efcb6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 12:36:12 -0500 Subject: [PATCH 0892/4053] Turns out we don't actually need that Set --- lib/models/patch/multi-file-patch.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 78003fd42f..7cd739343e 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -240,7 +240,6 @@ export default class MultiFilePatch { sortedRowSet.sort((a, b) => a - b); const filePatches = []; - const seen = new Set(); let lastFilePatch = null; for (const row of sortedRowSet) { // Because the rows are sorted, consecutive rows will almost certainly belong to the same patch, so we can save @@ -250,12 +249,7 @@ export default class MultiFilePatch { } lastFilePatch = this.getFilePatchAt(row); - if (seen.has(lastFilePatch)) { - continue; - } - filePatches.push(lastFilePatch); - seen.add(lastFilePatch); } return filePatches; From 1eff20fe79dacd47c238db3d43bb3fcbcbc29c25 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 12:36:48 -0500 Subject: [PATCH 0893/4053] We actually care about typechanges, not just having a symlink Oops --- lib/models/patch/multi-file-patch.js | 9 ++------- lib/views/multi-file-patch-view.js | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 7cd739343e..c038edca70 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -268,13 +268,8 @@ export default class MultiFilePatch { return false; } - anyHaveSymlink() { - for (const filePatch of this.getFilePatches()) { - if (filePatch.hasSymlink()) { - return true; - } - } - return false; + anyHaveTypechange() { + return this.getFilePatches().some(fp => fp.hasTypechange()); } getMaxLineNumberWidth() { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index d72d7982de..ec16a26ae0 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -199,7 +199,7 @@ export default class MultiFilePatchView extends React.Component { stageModeCommand = ; } - if (this.props.multiFilePatch.anyHaveSymlink()) { + if (this.props.multiFilePatch.anyHaveTypechange()) { const command = this.props.stagingStatus === 'unstaged' ? 'github:stage-symlink-change' : 'github:unstage-symlink-change'; From ccd4eb6b0f603a73045bf69cd88489dd5e6bcc0c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 12:38:27 -0500 Subject: [PATCH 0894/4053] MultiFilePatch tests: (un)staged patch generation, predicates --- test/models/patch/multi-file-patch.test.js | 298 ++++++++++++++++++--- 1 file changed, 268 insertions(+), 30 deletions(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 7915983964..93875df0fe 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -3,7 +3,7 @@ import dedent from 'dedent-js'; import MultiFilePatch from '../../../lib/models/patch/multi-file-patch'; import FilePatch from '../../../lib/models/patch/file-patch'; -import File from '../../../lib/models/patch/file'; +import File, {nullFile} from '../../../lib/models/patch/file'; import Patch from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; import {Unchanged, Addition, Deletion} from '../../../lib/models/patch/region'; @@ -24,12 +24,59 @@ describe('MultiFilePatch', function() { }; }); + it('creates an empty patch when constructed with no arguments', function() { + const empty = new MultiFilePatch(); + assert.isFalse(empty.anyPresent()); + assert.lengthOf(empty.getFilePatches(), 0); + }); + + it('detects when it is not empty', function() { + const mp = new MultiFilePatch(buffer, layers, [buildFilePatchFixture(0)]); + assert.isTrue(mp.anyPresent()); + }); + it('has an accessor for its file patches', function() { const filePatches = [buildFilePatchFixture(0), buildFilePatchFixture(1)]; const mp = new MultiFilePatch(buffer, layers, filePatches); assert.strictEqual(mp.getFilePatches(), filePatches); }); + describe('didAnyChangeExecutableMode()', function() { + it('detects when at least one patch contains an executable mode change', function() { + const yes = new MultiFilePatch(buffer, layers, [ + buildFilePatchFixture(0, {oldFileMode: '100644', newFileMode: '100755'}), + buildFilePatchFixture(1), + ]); + assert.isTrue(yes.didAnyChangeExecutableMode()); + }); + + it('detects when none of the patches contain an executable mode change', function() { + const no = new MultiFilePatch(buffer, layers, [ + buildFilePatchFixture(0), + buildFilePatchFixture(1), + ]); + assert.isFalse(no.didAnyChangeExecutableMode()); + }); + }); + + describe('anyHaveTypechange()', function() { + it('detects when at least one patch contains a symlink change', function() { + const yes = new MultiFilePatch(buffer, layers, [ + buildFilePatchFixture(0, {oldFileMode: '100644', newFileMode: '120000', newFileSymlink: 'destination'}), + buildFilePatchFixture(1), + ]); + assert.isTrue(yes.anyHaveTypechange()); + }); + + it('detects when none of its patches contain a symlink change', function() { + const no = new MultiFilePatch(buffer, layers, [ + buildFilePatchFixture(0), + buildFilePatchFixture(1), + ]); + assert.isFalse(no.anyHaveTypechange()); + }); + }); + it('locates an individual FilePatch by marker lookup', function() { const filePatches = []; for (let i = 0; i < 10; i++) { @@ -43,6 +90,19 @@ describe('MultiFilePatch', function() { assert.strictEqual(mp.getFilePatchAt(79), filePatches[9]); }); + it('creates a set of all unique paths referenced by patches', function() { + const mp = new MultiFilePatch(buffer, layers, [ + buildFilePatchFixture(0, {oldFilePath: 'file-0-before.txt', newFilePath: 'file-0-after.txt'}), + buildFilePatchFixture(1, {status: 'added', newFilePath: 'file-1.txt'}), + buildFilePatchFixture(2, {oldFilePath: 'file-2.txt', newFilePath: 'file-2.txt'}), + ]); + + assert.sameMembers( + Array.from(mp.getPathSet()), + ['file-0-before.txt', 'file-0-after.txt', 'file-1.txt', 'file-2.txt'], + ); + }); + it('locates a Hunk by marker lookup', function() { const filePatches = [ buildFilePatchFixture(0), @@ -165,6 +225,157 @@ describe('MultiFilePatch', function() { ); }); + it('generates a stage patch from an arbitrary hunk', function() { + const filePatches = [ + buildFilePatchFixture(0), + buildFilePatchFixture(1), + ]; + const original = new MultiFilePatch(buffer, layers, filePatches); + const hunk = original.getFilePatches()[0].getHunks()[1]; + const stagePatch = original.getStagePatchForHunk(hunk); + + assert.strictEqual(stagePatch.getBuffer().getText(), dedent` + file-0 line-4 + file-0 line-5 + file-0 line-6 + file-0 line-7 + + `); + assert.lengthOf(stagePatch.getFilePatches(), 1); + const [fp0] = stagePatch.getFilePatches(); + assert.strictEqual(fp0.getOldPath(), 'file-0.txt'); + assert.strictEqual(fp0.getNewPath(), 'file-0.txt'); + assertInFilePatch(fp0, stagePatch.getBuffer()).hunks( + { + startRow: 0, endRow: 3, + header: '@@ -10,3 +10,3 @@', + regions: [ + {kind: 'unchanged', string: ' file-0 line-4\n', range: [[0, 0], [0, 13]]}, + {kind: 'addition', string: '+file-0 line-5\n', range: [[1, 0], [1, 13]]}, + {kind: 'deletion', string: '-file-0 line-6\n', range: [[2, 0], [2, 13]]}, + {kind: 'unchanged', string: ' file-0 line-7\n', range: [[3, 0], [3, 13]]}, + ], + }, + ); + }); + + it('generates an unstage patch for arbitrary buffer rows', function() { + const filePatches = [ + buildFilePatchFixture(0), + buildFilePatchFixture(1), + buildFilePatchFixture(2), + buildFilePatchFixture(3), + ]; + const original = new MultiFilePatch(buffer, layers, filePatches); + + const unstagePatch = original.getUnstagePatchForLines(new Set([1, 2, 21, 26, 29, 30])); + + assert.strictEqual(unstagePatch.getBuffer().getText(), dedent` + file-0 line-0 + file-0 line-1 + file-0 line-2 + file-0 line-3 + file-2 line-4 + file-2 line-5 + file-2 line-7 + file-3 line-0 + file-3 line-1 + file-3 line-2 + file-3 line-3 + file-3 line-4 + file-3 line-5 + file-3 line-6 + file-3 line-7 + + `); + + assert.lengthOf(unstagePatch.getFilePatches(), 3); + const [fp0, fp1, fp2] = unstagePatch.getFilePatches(); + assert.strictEqual(fp0.getOldPath(), 'file-0.txt'); + assertInFilePatch(fp0, unstagePatch.getBuffer()).hunks( + { + startRow: 0, endRow: 3, + header: '@@ -0,3 +0,3 @@', + regions: [ + {kind: 'unchanged', string: ' file-0 line-0\n', range: [[0, 0], [0, 13]]}, + {kind: 'deletion', string: '-file-0 line-1\n', range: [[1, 0], [1, 13]]}, + {kind: 'addition', string: '+file-0 line-2\n', range: [[2, 0], [2, 13]]}, + {kind: 'unchanged', string: ' file-0 line-3\n', range: [[3, 0], [3, 13]]}, + ], + }, + ); + + assert.strictEqual(fp1.getOldPath(), 'file-2.txt'); + assertInFilePatch(fp1, unstagePatch.getBuffer()).hunks( + { + startRow: 4, endRow: 6, + header: '@@ -10,3 +10,2 @@', + regions: [ + {kind: 'unchanged', string: ' file-2 line-4\n', range: [[4, 0], [4, 13]]}, + {kind: 'deletion', string: '-file-2 line-5\n', range: [[5, 0], [5, 13]]}, + {kind: 'unchanged', string: ' file-2 line-7\n', range: [[6, 0], [6, 13]]}, + ], + }, + ); + + assert.strictEqual(fp2.getOldPath(), 'file-3.txt'); + assertInFilePatch(fp2, unstagePatch.getBuffer()).hunks( + { + startRow: 7, endRow: 10, + header: '@@ -0,3 +0,4 @@', + regions: [ + {kind: 'unchanged', string: ' file-3 line-0\n file-3 line-1\n', range: [[7, 0], [8, 13]]}, + {kind: 'addition', string: '+file-3 line-2\n', range: [[9, 0], [9, 13]]}, + {kind: 'unchanged', string: ' file-3 line-3\n', range: [[10, 0], [10, 13]]}, + ], + }, + { + startRow: 11, endRow: 14, + header: '@@ -10,3 +11,3 @@', + regions: [ + {kind: 'unchanged', string: ' file-3 line-4\n', range: [[11, 0], [11, 13]]}, + {kind: 'deletion', string: '-file-3 line-5\n', range: [[12, 0], [12, 13]]}, + {kind: 'addition', string: '+file-3 line-6\n', range: [[13, 0], [13, 13]]}, + {kind: 'unchanged', string: ' file-3 line-7\n', range: [[14, 0], [14, 13]]}, + ], + }, + ); + }); + + it('generates an unstaged patch for an arbitrary hunk', function() { + const filePatches = [ + buildFilePatchFixture(0), + buildFilePatchFixture(1), + ]; + const original = new MultiFilePatch(buffer, layers, filePatches); + const hunk = original.getFilePatches()[1].getHunks()[0]; + const unstagePatch = original.getUnstagePatchForHunk(hunk); + + assert.strictEqual(unstagePatch.getBuffer().getText(), dedent` + file-1 line-0 + file-1 line-1 + file-1 line-2 + file-1 line-3 + + `); + assert.lengthOf(unstagePatch.getFilePatches(), 1); + const [fp0] = unstagePatch.getFilePatches(); + assert.strictEqual(fp0.getOldPath(), 'file-1.txt'); + assert.strictEqual(fp0.getNewPath(), 'file-1.txt'); + assertInFilePatch(fp0, unstagePatch.getBuffer()).hunks( + { + startRow: 0, endRow: 3, + header: '@@ -0,3 +0,3 @@', + regions: [ + {kind: 'unchanged', string: ' file-1 line-0\n', range: [[0, 0], [0, 13]]}, + {kind: 'deletion', string: '-file-1 line-1\n', range: [[1, 0], [1, 13]]}, + {kind: 'addition', string: '+file-1 line-2\n', range: [[2, 0], [2, 13]]}, + {kind: 'unchanged', string: ' file-1 line-3\n', range: [[3, 0], [3, 13]]}, + ], + }, + ); + }); + // FIXME adapt these to the lifted method. // describe('next selection range derivation', function() { // it('selects the first change region after the highest buffer row', function() { @@ -325,44 +536,71 @@ describe('MultiFilePatch', function() { // }); // }); - function buildFilePatchFixture(index) { + function buildFilePatchFixture(index, options = {}) { + const opts = { + oldFilePath: `file-${index}.txt`, + oldFileMode: '100644', + oldFileSymlink: null, + newFilePath: `file-${index}.txt`, + newFileMode: '100644', + newFileSymlink: null, + status: 'modified', + ...options, + }; + const rowOffset = buffer.getLastRow(); for (let i = 0; i < 8; i++) { buffer.append(`file-${index} line-${i}\n`); } + let oldFile = new File({path: opts.oldFilePath, mode: opts.oldFileMode, symlink: opts.oldFileSymlink}); + const newFile = new File({path: opts.newFilePath, mode: opts.newFileMode, symlink: opts.newFileSymlink}); + const mark = (layer, start, end = start) => layer.markRange([[rowOffset + start, 0], [rowOffset + end, Infinity]]); - const hunks = [ - new Hunk({ - oldStartRow: 0, newStartRow: 0, oldRowCount: 3, newRowCount: 3, - sectionHeading: `file-${index} hunk-0`, - marker: mark(layers.hunk, 0, 3), - regions: [ - new Unchanged(mark(layers.unchanged, 0)), - new Addition(mark(layers.addition, 1)), - new Deletion(mark(layers.deletion, 2)), - new Unchanged(mark(layers.unchanged, 3)), - ], - }), - new Hunk({ - oldStartRow: 10, newStartRow: 10, oldRowCount: 3, newRowCount: 3, - sectionHeading: `file-${index} hunk-1`, - marker: mark(layers.hunk, 4, 7), - regions: [ - new Unchanged(mark(layers.unchanged, 4)), - new Addition(mark(layers.addition, 5)), - new Deletion(mark(layers.deletion, 6)), - new Unchanged(mark(layers.unchanged, 7)), - ], - }), - ]; + let hunks = []; + if (opts.status === 'modified') { + hunks = [ + new Hunk({ + oldStartRow: 0, newStartRow: 0, oldRowCount: 3, newRowCount: 3, + sectionHeading: `file-${index} hunk-0`, + marker: mark(layers.hunk, 0, 3), + regions: [ + new Unchanged(mark(layers.unchanged, 0)), + new Addition(mark(layers.addition, 1)), + new Deletion(mark(layers.deletion, 2)), + new Unchanged(mark(layers.unchanged, 3)), + ], + }), + new Hunk({ + oldStartRow: 10, newStartRow: 10, oldRowCount: 3, newRowCount: 3, + sectionHeading: `file-${index} hunk-1`, + marker: mark(layers.hunk, 4, 7), + regions: [ + new Unchanged(mark(layers.unchanged, 4)), + new Addition(mark(layers.addition, 5)), + new Deletion(mark(layers.deletion, 6)), + new Unchanged(mark(layers.unchanged, 7)), + ], + }), + ]; + } else if (opts.status === 'added') { + hunks = [ + new Hunk({ + oldStartRow: 0, newStartRow: 0, oldRowCount: 8, newRowCount: 8, + sectionHeading: `file-${index} hunk-0`, + marker: mark(layers.hunk, 0, 7), + regions: [ + new Addition(mark(layers.addition, 0, 7)), + ], + }), + ]; - const marker = mark(layers.patch, 0, 7); - const patch = new Patch({status: 'modified', hunks, marker}); + oldFile = nullFile; + } - const oldFile = new File({path: `file-${index}.txt`, mode: '100644'}); - const newFile = new File({path: `file-${index}.txt`, mode: '100644'}); + const marker = mark(layers.patch, 0, 7); + const patch = new Patch({status: opts.status, hunks, marker}); return new FilePatch(oldFile, newFile, patch); } From d52b987b509031e5c3c9fcead9c651d70e26b5fa Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 8 Nov 2018 19:30:36 +0100 Subject: [PATCH 0895/4053] =?UTF-8?q?filePatch=20is=20soooo=20last=20week;?= =?UTF-8?q?=20we=20are=20on=20multiFilePatch=20now.=20=F0=9F=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/views/multi-file-patch-view.test.js | 33 +++++++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index de957868a7..9caa9fd6ca 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -3,13 +3,13 @@ import {shallow, mount} from 'enzyme'; import {cloneRepository, buildRepository} from '../helpers'; import MultiFilePatchView from '../../lib/views/multi-file-patch-view'; -import {buildFilePatch} from '../../lib/models/patch'; +import {buildFilePatch, buildMultiFilePatch} from '../../lib/models/patch'; import {nullFile} from '../../lib/models/patch/file'; import FilePatch from '../../lib/models/patch/file-patch'; import RefHolder from '../../lib/models/ref-holder'; -describe('MultiFilePatchView', function() { - let atomEnv, workspace, repository, filePatch; +describe.only('MultiFilePatchView', function() { + let atomEnv, workspace, repository, filePatches; beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); @@ -17,9 +17,10 @@ describe('MultiFilePatchView', function() { const workdirPath = await cloneRepository(); repository = await buildRepository(workdirPath); + // filePatches = repository.getStagedChangesPatch(); // path.txt: unstaged changes - filePatch = buildFilePatch([{ + filePatches = buildMultiFilePatch([{ oldPath: 'path.txt', oldMode: '100644', newPath: 'path.txt', @@ -37,6 +38,24 @@ describe('MultiFilePatchView', function() { lines: [' 0005', '+0006', '-0007', ' 0008'], }, ], + }, { + oldPath: 'path2.txt', + oldMode: '100644', + newPath: 'path2.txt', + newMode: '100644', + status: 'modified', + hunks: [ + { + oldStartLine: 4, oldLineCount: 3, newStartLine: 4, newLineCount: 4, + heading: 'zero', + lines: [' 0000', '+0001', '+0002', '-0003', ' 0004'], + }, + { + oldStartLine: 8, oldLineCount: 3, newStartLine: 9, newLineCount: 3, + heading: 'one', + lines: [' 0005', '+0006', '-0007', ' 0008'], + }, + ], }]); }); @@ -49,7 +68,7 @@ describe('MultiFilePatchView', function() { relPath: 'path.txt', stagingStatus: 'unstaged', isPartiallyStaged: false, - filePatch, + multiFilePatch: filePatches, hasUndoHistory: false, selectionMode: 'line', selectedRows: new Set(), @@ -89,7 +108,7 @@ describe('MultiFilePatchView', function() { const undoLastDiscard = sinon.spy(); const wrapper = shallow(buildApp({undoLastDiscard})); - wrapper.find('FilePatchHeaderView').prop('undoLastDiscard')(); + wrapper.find('FilePatchHeaderView').first().prop('undoLastDiscard')(); assert.isTrue(undoLastDiscard.calledWith({eventSource: 'button'})); }); @@ -98,7 +117,7 @@ describe('MultiFilePatchView', function() { const wrapper = mount(buildApp()); const editor = wrapper.find('AtomTextEditor'); - assert.strictEqual(editor.instance().getModel().getText(), filePatch.getBuffer().getText()); + assert.strictEqual(editor.instance().getModel().getText(), filePatches.getBuffer().getText()); }); it('enables autoHeight on the editor when requested', function() { From d03c37c8d0c3a23d5e70ed4130b839013e94e0c3 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 8 Nov 2018 19:32:44 +0100 Subject: [PATCH 0896/4053] we don't care about active/inactive anymore... i think. --- test/views/multi-file-patch-view.test.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 9caa9fd6ca..aed5aa1c90 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -136,15 +136,6 @@ describe.only('MultiFilePatchView', function() { assert.isTrue(wrapper.find('.github-FilePatchView--hunkMode').exists()); }); - it('sets the root class when active or inactive', function() { - const wrapper = shallow(buildApp({isActive: true})); - assert.isTrue(wrapper.find('.github-FilePatchView--active').exists()); - assert.isFalse(wrapper.find('.github-FilePatchView--inactive').exists()); - wrapper.setProps({isActive: false}); - assert.isFalse(wrapper.find('.github-FilePatchView--active').exists()); - assert.isTrue(wrapper.find('.github-FilePatchView--inactive').exists()); - }); - it('preserves the selection index when a new file patch arrives in line selection mode', function() { const selectedRowsChanged = sinon.spy(); const wrapper = mount(buildApp({ From 467a33eb69e989e10b50501503d6c5eb9e3d3f44 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 8 Nov 2018 19:35:09 +0100 Subject: [PATCH 0897/4053] no `.only` --- test/views/multi-file-patch-view.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index aed5aa1c90..9b6e0e62fd 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -8,7 +8,7 @@ import {nullFile} from '../../lib/models/patch/file'; import FilePatch from '../../lib/models/patch/file-patch'; import RefHolder from '../../lib/models/ref-holder'; -describe.only('MultiFilePatchView', function() { +describe('MultiFilePatchView', function() { let atomEnv, workspace, repository, filePatches; beforeEach(async function() { From ea76f598e9a631fbf05801ad4a8a9a792d86461e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 14:08:59 -0500 Subject: [PATCH 0898/4053] Full MultiFilePatch coverage for everything but getNextSelectionRange() --- test/models/patch/multi-file-patch.test.js | 74 +++++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 93875df0fe..41ad2b920a 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -6,7 +6,7 @@ import FilePatch from '../../../lib/models/patch/file-patch'; import File, {nullFile} from '../../../lib/models/patch/file'; import Patch from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; -import {Unchanged, Addition, Deletion} from '../../../lib/models/patch/region'; +import {Unchanged, Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; import {assertInFilePatch} from '../../helpers'; describe('MultiFilePatch', function() { @@ -77,6 +77,14 @@ describe('MultiFilePatch', function() { }); }); + it('computes the maximum line number width of any hunk in any patch', function() { + const mp = new MultiFilePatch(buffer, layers, [ + buildFilePatchFixture(0), + buildFilePatchFixture(1), + ]); + assert.strictEqual(mp.getMaxLineNumberWidth(), 2); + }); + it('locates an individual FilePatch by marker lookup', function() { const filePatches = []; for (let i = 0; i < 10; i++) { @@ -121,13 +129,50 @@ describe('MultiFilePatch', function() { assert.strictEqual(mp.getHunkAt(23), filePatches[2].getHunks()[1]); }); + it('represents itself as an apply-ready string', function() { + const mp = new MultiFilePatch(buffer, layers, [ + buildFilePatchFixture(0), + buildFilePatchFixture(1), + ]); + + assert.strictEqual(mp.toString(), dedent` + diff --git a/file-0.txt b/file-0.txt + --- a/file-0.txt + +++ b/file-0.txt + @@ -0,3 +0,3 @@ + file-0 line-0 + +file-0 line-1 + -file-0 line-2 + file-0 line-3 + @@ -10,3 +10,3 @@ + file-0 line-4 + +file-0 line-5 + -file-0 line-6 + file-0 line-7 + diff --git a/file-1.txt b/file-1.txt + --- a/file-1.txt + +++ b/file-1.txt + @@ -0,3 +0,3 @@ + file-1 line-0 + +file-1 line-1 + -file-1 line-2 + file-1 line-3 + @@ -10,3 +10,3 @@ + file-1 line-4 + +file-1 line-5 + -file-1 line-6 + file-1 line-7 + + `); + }); + it('adopts a buffer from a previous patch', function() { const lastBuffer = buffer; const lastLayers = layers; const lastFilePatches = [ buildFilePatchFixture(0), buildFilePatchFixture(1), - buildFilePatchFixture(2), + buildFilePatchFixture(2, {noNewline: true}), ]; const lastPatch = new MultiFilePatch(lastBuffer, layers, lastFilePatches); @@ -144,7 +189,7 @@ describe('MultiFilePatch', function() { buildFilePatchFixture(0), buildFilePatchFixture(1), buildFilePatchFixture(2), - buildFilePatchFixture(3), + buildFilePatchFixture(3, {noNewline: true}), ]; const nextPatch = new MultiFilePatch(buffer, layers, nextFilePatches); @@ -545,6 +590,7 @@ describe('MultiFilePatch', function() { newFileMode: '100644', newFileSymlink: null, status: 'modified', + noNewline: false, ...options, }; @@ -552,12 +598,22 @@ describe('MultiFilePatch', function() { for (let i = 0; i < 8; i++) { buffer.append(`file-${index} line-${i}\n`); } + if (opts.noNewline) { + buffer.append(' No newline at end of file\n'); + } let oldFile = new File({path: opts.oldFilePath, mode: opts.oldFileMode, symlink: opts.oldFileSymlink}); const newFile = new File({path: opts.newFilePath, mode: opts.newFileMode, symlink: opts.newFileSymlink}); const mark = (layer, start, end = start) => layer.markRange([[rowOffset + start, 0], [rowOffset + end, Infinity]]); + const withNoNewlineRegion = regions => { + if (opts.noNewline) { + regions.push(new NoNewline(mark(layers.noNewline, 8))); + } + return regions; + }; + let hunks = []; if (opts.status === 'modified') { hunks = [ @@ -575,13 +631,13 @@ describe('MultiFilePatch', function() { new Hunk({ oldStartRow: 10, newStartRow: 10, oldRowCount: 3, newRowCount: 3, sectionHeading: `file-${index} hunk-1`, - marker: mark(layers.hunk, 4, 7), - regions: [ + marker: mark(layers.hunk, 4, opts.noNewline ? 8 : 7), + regions: withNoNewlineRegion([ new Unchanged(mark(layers.unchanged, 4)), new Addition(mark(layers.addition, 5)), new Deletion(mark(layers.deletion, 6)), new Unchanged(mark(layers.unchanged, 7)), - ], + ]), }), ]; } else if (opts.status === 'added') { @@ -589,10 +645,10 @@ describe('MultiFilePatch', function() { new Hunk({ oldStartRow: 0, newStartRow: 0, oldRowCount: 8, newRowCount: 8, sectionHeading: `file-${index} hunk-0`, - marker: mark(layers.hunk, 0, 7), - regions: [ + marker: mark(layers.hunk, 0, opts.noNewline ? 8 : 7), + regions: withNoNewlineRegion([ new Addition(mark(layers.addition, 0, 7)), - ], + ]), }), ]; From c6a8aa052571a04c32a4a072a996bc54d3a87156 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 8 Nov 2018 20:10:24 +0100 Subject: [PATCH 0899/4053] new clone method for MFP --- lib/models/patch/multi-file-patch.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index c038edca70..06a80953b5 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -24,6 +24,21 @@ export default class MultiFilePatch { } } + clone(opts = {}) { + return new this.constructor({ + buffer: opts.buffer !== undefined ? opts.buffer : this.getBuffer(), + layers: opts.layers !== undefined ? opts.layers : { + patch: this.getPatchLayer(), + hunk: this.getHunkLayer(), + unchanged: this.getUnchangedLayer(), + addition: this.getAdditionLayer(), + deletion: this.getDeletionLayer(), + noNewline: this.getNoNewlineLayer(), + }, + filePatches: opts.filePatches !== undefined ? opts.filePatches : this.getFilePatches(), + }); + } + getBuffer() { return this.buffer; } From 4ac8af809687b343c42403eaa186a22d25f6d357 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 8 Nov 2018 20:12:17 +0100 Subject: [PATCH 0900/4053] use the new clone method in MFP view test --- test/views/multi-file-patch-view.test.js | 30 +++++++----------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 9b6e0e62fd..d9a408d5c9 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -38,24 +38,6 @@ describe('MultiFilePatchView', function() { lines: [' 0005', '+0006', '-0007', ' 0008'], }, ], - }, { - oldPath: 'path2.txt', - oldMode: '100644', - newPath: 'path2.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 4, oldLineCount: 3, newStartLine: 4, newLineCount: 4, - heading: 'zero', - lines: [' 0000', '+0001', '+0002', '-0003', ' 0004'], - }, - { - oldStartLine: 8, oldLineCount: 3, newStartLine: 9, newLineCount: 3, - heading: 'one', - lines: [' 0005', '+0006', '-0007', ' 0008'], - }, - ], }]); }); @@ -302,12 +284,16 @@ describe('MultiFilePatchView', function() { describe('executable mode changes', function() { it('does not render if the mode has not changed', function() { - const fp = filePatch.clone({ - oldFile: filePatch.getOldFile().clone({mode: '100644'}), - newFile: filePatch.getNewFile().clone({mode: '100644'}), + const [fp] = filePatches.getFilePatches(); + + const mfp = filePatches.clone({ + filePatches: fp.clone({ + oldFile: fp.getOldFile().clone({mode: '100644'}), + newFile: fp.getNewFile().clone({mode: '100644'}), + }), }); - const wrapper = shallow(buildApp({filePatch: fp})); + const wrapper = shallow(buildApp({multiFilePatch: mfp})); assert.isFalse(wrapper.find('FilePatchMetaView[title="Mode change"]').exists()); }); From 3908ffc9dc8467116c7077955f3d69f0fa7f598e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 8 Nov 2018 20:12:47 +0100 Subject: [PATCH 0901/4053] import the right thing --- test/views/multi-file-patch-view.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index d9a408d5c9..a4d8c73afd 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -6,6 +6,7 @@ import MultiFilePatchView from '../../lib/views/multi-file-patch-view'; import {buildFilePatch, buildMultiFilePatch} from '../../lib/models/patch'; import {nullFile} from '../../lib/models/patch/file'; import FilePatch from '../../lib/models/patch/file-patch'; +import MultiFilePatch from '../../lib/models/patch/multi-file-patch'; import RefHolder from '../../lib/models/ref-holder'; describe('MultiFilePatchView', function() { From 44bc9b767cd3f397a9c159b68827ac0e289155aa Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 8 Nov 2018 11:19:24 -0800 Subject: [PATCH 0902/4053] use this.constructor.name for metrics --- lib/controllers/multi-file-patch-controller.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index ebcd8171ab..7ffeb2b38f 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -87,10 +87,11 @@ export default class MultiFilePatchController extends React.Component { undoLastDiscard(filePatch, {eventSource} = {}) { addEvent('undo-last-discard', { package: 'github', - component: 'FilePatchController', + component: this.constructor.name, eventSource, }); + return this.props.undoLastDiscard(filePatch.getPath(), this.props.repository); } @@ -191,7 +192,7 @@ export default class MultiFilePatchController extends React.Component { addEvent('discard-unstaged-changes', { package: 'github', - component: 'FilePatchController', + component: this.constructor.name, lineCount: chosenRows.size, eventSource, }); From bc86c23f9627435d3adcdce9dc69556f4abebaba Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 8 Nov 2018 20:49:47 +0100 Subject: [PATCH 0903/4053] replace usages of old clone method with those of the new one --- test/views/multi-file-patch-view.test.js | 81 +++++++++++++++--------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index a4d8c73afd..da471fa3c2 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -6,7 +6,6 @@ import MultiFilePatchView from '../../lib/views/multi-file-patch-view'; import {buildFilePatch, buildMultiFilePatch} from '../../lib/models/patch'; import {nullFile} from '../../lib/models/patch/file'; import FilePatch from '../../lib/models/patch/file-patch'; -import MultiFilePatch from '../../lib/models/patch/multi-file-patch'; import RefHolder from '../../lib/models/ref-holder'; describe('MultiFilePatchView', function() { @@ -286,12 +285,11 @@ describe('MultiFilePatchView', function() { describe('executable mode changes', function() { it('does not render if the mode has not changed', function() { const [fp] = filePatches.getFilePatches(); - const mfp = filePatches.clone({ - filePatches: fp.clone({ + filePatches: [fp.clone({ oldFile: fp.getOldFile().clone({mode: '100644'}), newFile: fp.getNewFile().clone({mode: '100644'}), - }), + })], }); const wrapper = shallow(buildApp({multiFilePatch: mfp})); @@ -299,12 +297,15 @@ describe('MultiFilePatchView', function() { }); it('renders change details within a meta container', function() { - const fp = filePatch.clone({ - oldFile: filePatch.getOldFile().clone({mode: '100644'}), - newFile: filePatch.getNewFile().clone({mode: '100755'}), + const [fp] = filePatches.getFilePatches(); + const mfp = filePatches.clone({ + filePatches: [fp.clone({ + oldFile: fp.getOldFile().clone({mode: '100644'}), + newFile: fp.getNewFile().clone({mode: '100755'}), + })], }); - const wrapper = mount(buildApp({filePatch: fp, stagingStatus: 'unstaged'})); + const wrapper = mount(buildApp({multiFilePatch: mfp, stagingStatus: 'unstaged'})); const meta = wrapper.find('FilePatchMetaView[title="Mode change"]'); assert.strictEqual(meta.prop('actionIcon'), 'icon-move-down'); @@ -315,13 +316,16 @@ describe('MultiFilePatchView', function() { }); it("stages or unstages the mode change when the meta container's action is triggered", function() { - const fp = filePatch.clone({ - oldFile: filePatch.getOldFile().clone({mode: '100644'}), - newFile: filePatch.getNewFile().clone({mode: '100755'}), + const [fp] = filePatches.getFilePatches(); + const mfp = filePatches.clone({ + filePatches: fp.clone({ + oldFile: fp.getOldFile().clone({mode: '100644'}), + newFile: fp.getNewFile().clone({mode: '100755'}), + }), }); const toggleModeChange = sinon.stub(); - const wrapper = shallow(buildApp({filePatch: fp, stagingStatus: 'staged', toggleModeChange})); + const wrapper = mount(buildApp({multiFilePatch: mfp, stagingStatus: 'unstaged', toggleModeChange})); const meta = wrapper.find('FilePatchMetaView[title="Mode change"]'); assert.isTrue(meta.exists()); @@ -335,22 +339,28 @@ describe('MultiFilePatchView', function() { describe('symlink changes', function() { it('does not render if the symlink status is unchanged', function() { - const fp = filePatch.clone({ - oldFile: filePatch.getOldFile().clone({mode: '100644'}), - newFile: filePatch.getNewFile().clone({mode: '100755'}), + const [fp] = filePatches.getFilePatches(); + const mfp = filePatches.clone({ + filePatches: fp.clone({ + oldFile: fp.getOldFile().clone({mode: '100644'}), + newFile: fp.getNewFile().clone({mode: '100755'}), + }), }); - const wrapper = mount(buildApp({filePatch: fp})); + const wrapper = mount(buildApp({multiFilePatch: mfp})); assert.lengthOf(wrapper.find('FilePatchMetaView').filterWhere(v => v.prop('title').startsWith('Symlink')), 0); }); it('renders symlink change information within a meta container', function() { - const fp = filePatch.clone({ - oldFile: filePatch.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), - newFile: filePatch.getNewFile().clone({mode: '120000', symlink: '/new.txt'}), + const [fp] = filePatches.getFilePatches(); + const mfp = filePatches.clone({ + filePatches: fp.clone({ + oldFile: fp.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), + newFile: fp.getNewFile().clone({mode: '120000', symlink: '/new.txt'}), + }), }); - const wrapper = mount(buildApp({filePatch: fp, stagingStatus: 'unstaged'})); + const wrapper = mount(buildApp({multiFilePatch: mfp, stagingStatus: 'unstaged'})); const meta = wrapper.find('FilePatchMetaView[title="Symlink changed"]'); assert.isTrue(meta.exists()); assert.strictEqual(meta.prop('actionIcon'), 'icon-move-down'); @@ -363,12 +373,15 @@ describe('MultiFilePatchView', function() { it('stages or unstages the symlink change', function() { const toggleSymlinkChange = sinon.stub(); - const fp = filePatch.clone({ - oldFile: filePatch.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), - newFile: filePatch.getNewFile().clone({mode: '120000', symlink: '/new.txt'}), + const [fp] = filePatches.getFilePatches(); + const mfp = filePatches.clone({ + filePatches: fp.clone({ + oldFile: fp.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), + newFile: fp.getNewFile().clone({mode: '120000', symlink: '/new.txt'}), + }), }); - const wrapper = mount(buildApp({filePatch: fp, stagingStatus: 'staged', toggleSymlinkChange})); + const wrapper = mount(buildApp({multiFilePatch: mfp, stagingStatus: 'staged', toggleSymlinkChange})); const meta = wrapper.find('FilePatchMetaView[title="Symlink changed"]'); assert.isTrue(meta.exists()); assert.strictEqual(meta.prop('actionIcon'), 'icon-move-up'); @@ -379,12 +392,15 @@ describe('MultiFilePatchView', function() { }); it('renders details for a symlink deletion', function() { - const fp = filePatch.clone({ - oldFile: filePatch.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), - newFile: nullFile, + const [fp] = filePatches.getFilePatches(); + const mfp = filePatches.clone({ + filePatches: fp.clone({ + oldFile: fp.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), + newFile: nullFile, + }), }); - const wrapper = mount(buildApp({filePatch: fp})); + const wrapper = mount(buildApp({multiFilePatch: mfp})); const meta = wrapper.find('FilePatchMetaView[title="Symlink deleted"]'); assert.isTrue(meta.exists()); assert.strictEqual( @@ -394,9 +410,12 @@ describe('MultiFilePatchView', function() { }); it('renders details for a symlink creation', function() { - const fp = filePatch.clone({ - oldFile: nullFile, - newFile: filePatch.getOldFile().clone({mode: '120000', symlink: '/new.txt'}), + const [fp] = filePatches.getFilePatches(); + const mfp = filePatches.clone({ + filePatches: fp.clone({ + oldFile: nullFile, + newFile: fp.getOldFile().clone({mode: '120000', symlink: '/new.txt'}), + }), }); const wrapper = mount(buildApp({filePatch: fp})); From 634a3914d455e8465fb013c380467c3d3fe453d1 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 8 Nov 2018 20:59:51 +0100 Subject: [PATCH 0904/4053] One more thing... --- test/views/multi-file-patch-view.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index da471fa3c2..b09df3b557 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -418,7 +418,7 @@ describe('MultiFilePatchView', function() { }), }); - const wrapper = mount(buildApp({filePatch: fp})); + const wrapper = mount(buildApp({multiFilePatch: mfp})); const meta = wrapper.find('FilePatchMetaView[title="Symlink created"]'); assert.isTrue(meta.exists()); assert.strictEqual( From 973566d6421220c1af508e02a397ce268e8819aa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 15:00:54 -0500 Subject: [PATCH 0905/4053] Make the MultiFilePatch constructor like the others --- lib/models/patch/builder.js | 4 +- lib/models/patch/multi-file-patch.js | 18 ++++---- lib/models/repository-states/state.js | 4 +- test/models/patch/multi-file-patch.test.js | 48 +++++++++++----------- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 7e3c9c0e69..35070215af 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -21,7 +21,7 @@ export function buildFilePatch(diffs) { throw new Error(`Unexpected number of diffs: ${diffs.length}`); } - return new MultiFilePatch(layeredBuffer.buffer, layeredBuffer.layers, [filePatch]); + return new MultiFilePatch({filePatches: [filePatch], ...layeredBuffer}); } export function buildMultiFilePatch(diffs) { @@ -60,7 +60,7 @@ export function buildMultiFilePatch(diffs) { const filePatches = actions.map(action => action()); - return new MultiFilePatch(layeredBuffer.buffer, layeredBuffer.layers, filePatches); + return new MultiFilePatch({filePatches, ...layeredBuffer}); } function emptyDiffFilePatch() { diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 06a80953b5..736dbd8fa4 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,17 +1,17 @@ import {TextBuffer} from 'atom'; export default class MultiFilePatch { - constructor(buffer = null, layers = {}, filePatches = []) { - this.buffer = buffer; + constructor({buffer, layers, filePatches}) { + this.buffer = buffer || null; - this.patchLayer = layers.patch; - this.hunkLayer = layers.hunk; - this.unchangedLayer = layers.unchanged; - this.additionLayer = layers.addition; - this.deletionLayer = layers.deletion; - this.noNewlineLayer = layers.noNewline; + this.patchLayer = layers && layers.patch; + this.hunkLayer = layers && layers.hunk; + this.unchangedLayer = layers && layers.unchanged; + this.additionLayer = layers && layers.addition; + this.deletionLayer = layers && layers.deletion; + this.noNewlineLayer = layers && layers.noNewline; - this.filePatches = filePatches; + this.filePatches = filePatches || []; this.filePatchesByMarker = new Map(); this.hunksByMarker = new Map(); diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index c31af1bbfa..9cb953ee48 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -276,11 +276,11 @@ export default class State { } getFilePatchForPath(filePath, options = {}) { - return Promise.resolve(new MultiFilePatch()); + return Promise.resolve(new MultiFilePatch({})); } getStagedChangesPatch() { - return Promise.resolve(new MultiFilePatch()); + return Promise.resolve(new MultiFilePatch({})); } readFileFromIndex(filePath) { diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 41ad2b920a..1ea45887aa 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -25,63 +25,63 @@ describe('MultiFilePatch', function() { }); it('creates an empty patch when constructed with no arguments', function() { - const empty = new MultiFilePatch(); + const empty = new MultiFilePatch({}); assert.isFalse(empty.anyPresent()); assert.lengthOf(empty.getFilePatches(), 0); }); it('detects when it is not empty', function() { - const mp = new MultiFilePatch(buffer, layers, [buildFilePatchFixture(0)]); + const mp = new MultiFilePatch({buffer, layers, filePatches: [buildFilePatchFixture(0)]}); assert.isTrue(mp.anyPresent()); }); it('has an accessor for its file patches', function() { const filePatches = [buildFilePatchFixture(0), buildFilePatchFixture(1)]; - const mp = new MultiFilePatch(buffer, layers, filePatches); + const mp = new MultiFilePatch({buffer, layers, filePatches}); assert.strictEqual(mp.getFilePatches(), filePatches); }); describe('didAnyChangeExecutableMode()', function() { it('detects when at least one patch contains an executable mode change', function() { - const yes = new MultiFilePatch(buffer, layers, [ + const yes = new MultiFilePatch({buffer, layers, filePatches: [ buildFilePatchFixture(0, {oldFileMode: '100644', newFileMode: '100755'}), buildFilePatchFixture(1), - ]); + ]}); assert.isTrue(yes.didAnyChangeExecutableMode()); }); it('detects when none of the patches contain an executable mode change', function() { - const no = new MultiFilePatch(buffer, layers, [ + const no = new MultiFilePatch({buffer, layers, filePatches: [ buildFilePatchFixture(0), buildFilePatchFixture(1), - ]); + ]}); assert.isFalse(no.didAnyChangeExecutableMode()); }); }); describe('anyHaveTypechange()', function() { it('detects when at least one patch contains a symlink change', function() { - const yes = new MultiFilePatch(buffer, layers, [ + const yes = new MultiFilePatch({buffer, layers, filePatches: [ buildFilePatchFixture(0, {oldFileMode: '100644', newFileMode: '120000', newFileSymlink: 'destination'}), buildFilePatchFixture(1), - ]); + ]}); assert.isTrue(yes.anyHaveTypechange()); }); it('detects when none of its patches contain a symlink change', function() { - const no = new MultiFilePatch(buffer, layers, [ + const no = new MultiFilePatch({buffer, layers, filePatches: [ buildFilePatchFixture(0), buildFilePatchFixture(1), - ]); + ]}); assert.isFalse(no.anyHaveTypechange()); }); }); it('computes the maximum line number width of any hunk in any patch', function() { - const mp = new MultiFilePatch(buffer, layers, [ + const mp = new MultiFilePatch({buffer, layers, filePatches: [ buildFilePatchFixture(0), buildFilePatchFixture(1), - ]); + ]}); assert.strictEqual(mp.getMaxLineNumberWidth(), 2); }); @@ -90,7 +90,7 @@ describe('MultiFilePatch', function() { for (let i = 0; i < 10; i++) { filePatches.push(buildFilePatchFixture(i)); } - const mp = new MultiFilePatch(buffer, layers, filePatches); + const mp = new MultiFilePatch({buffer, layers, filePatches}); assert.strictEqual(mp.getFilePatchAt(0), filePatches[0]); assert.strictEqual(mp.getFilePatchAt(7), filePatches[0]); @@ -99,11 +99,11 @@ describe('MultiFilePatch', function() { }); it('creates a set of all unique paths referenced by patches', function() { - const mp = new MultiFilePatch(buffer, layers, [ + const mp = new MultiFilePatch({buffer, layers, filePatches: [ buildFilePatchFixture(0, {oldFilePath: 'file-0-before.txt', newFilePath: 'file-0-after.txt'}), buildFilePatchFixture(1, {status: 'added', newFilePath: 'file-1.txt'}), buildFilePatchFixture(2, {oldFilePath: 'file-2.txt', newFilePath: 'file-2.txt'}), - ]); + ]}); assert.sameMembers( Array.from(mp.getPathSet()), @@ -117,7 +117,7 @@ describe('MultiFilePatch', function() { buildFilePatchFixture(1), buildFilePatchFixture(2), ]; - const mp = new MultiFilePatch(buffer, layers, filePatches); + const mp = new MultiFilePatch({buffer, layers, filePatches}); assert.strictEqual(mp.getHunkAt(0), filePatches[0].getHunks()[0]); assert.strictEqual(mp.getHunkAt(3), filePatches[0].getHunks()[0]); @@ -130,10 +130,10 @@ describe('MultiFilePatch', function() { }); it('represents itself as an apply-ready string', function() { - const mp = new MultiFilePatch(buffer, layers, [ + const mp = new MultiFilePatch({buffer, layers, filePatches: [ buildFilePatchFixture(0), buildFilePatchFixture(1), - ]); + ]}); assert.strictEqual(mp.toString(), dedent` diff --git a/file-0.txt b/file-0.txt @@ -174,7 +174,7 @@ describe('MultiFilePatch', function() { buildFilePatchFixture(1), buildFilePatchFixture(2, {noNewline: true}), ]; - const lastPatch = new MultiFilePatch(lastBuffer, layers, lastFilePatches); + const lastPatch = new MultiFilePatch({buffer: lastBuffer, layers, filePatches: lastFilePatches}); buffer = new TextBuffer(); layers = { @@ -213,7 +213,7 @@ describe('MultiFilePatch', function() { buildFilePatchFixture(2), buildFilePatchFixture(3), ]; - const original = new MultiFilePatch(buffer, layers, filePatches); + const original = new MultiFilePatch({buffer, layers, filePatches}); const stagePatch = original.getStagePatchForLines(new Set([9, 14, 25, 26])); assert.strictEqual(stagePatch.getBuffer().getText(), dedent` @@ -275,7 +275,7 @@ describe('MultiFilePatch', function() { buildFilePatchFixture(0), buildFilePatchFixture(1), ]; - const original = new MultiFilePatch(buffer, layers, filePatches); + const original = new MultiFilePatch({buffer, layers, filePatches}); const hunk = original.getFilePatches()[0].getHunks()[1]; const stagePatch = original.getStagePatchForHunk(hunk); @@ -311,7 +311,7 @@ describe('MultiFilePatch', function() { buildFilePatchFixture(2), buildFilePatchFixture(3), ]; - const original = new MultiFilePatch(buffer, layers, filePatches); + const original = new MultiFilePatch({buffer, layers, filePatches}); const unstagePatch = original.getUnstagePatchForLines(new Set([1, 2, 21, 26, 29, 30])); @@ -392,7 +392,7 @@ describe('MultiFilePatch', function() { buildFilePatchFixture(0), buildFilePatchFixture(1), ]; - const original = new MultiFilePatch(buffer, layers, filePatches); + const original = new MultiFilePatch({buffer, layers, filePatches}); const hunk = original.getFilePatches()[1].getHunks()[0]; const unstagePatch = original.getUnstagePatchForHunk(hunk); From a3ef82ac7e2763bb8ab4de69ca5cfce0851770b3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 15:01:23 -0500 Subject: [PATCH 0906/4053] Return real Ranges --- lib/models/patch/multi-file-patch.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 736dbd8fa4..1dd87a169a 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,4 +1,4 @@ -import {TextBuffer} from 'atom'; +import {TextBuffer, Range} from 'atom'; export default class MultiFilePatch { constructor({buffer, layers, filePatches}) { @@ -183,7 +183,7 @@ export default class MultiFilePatch { } } - return [[newSelectionRow, 0], [newSelectionRow, Infinity]]; + return Range.fromObject([[newSelectionRow, 0], [newSelectionRow, Infinity]]); } adoptBufferFrom(lastMultiFilePatch) { From 37de91aa9e1ce9e6d4b9535412ece66399d33557 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 15:01:51 -0500 Subject: [PATCH 0907/4053] Use .clone() within MultiFilePatch --- lib/models/patch/multi-file-patch.js | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 1dd87a169a..d44d44c33c 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -97,12 +97,7 @@ export default class MultiFilePatch { const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { return fp.buildStagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); }); - - return new MultiFilePatch( - nextLayeredBuffer.buffer, - nextLayeredBuffer.layers, - nextFilePatches, - ); + return this.clone({...nextLayeredBuffer, filePatches: nextFilePatches}); } getStagePatchForHunk(hunk) { @@ -114,12 +109,7 @@ export default class MultiFilePatch { const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { return fp.buildUnstagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); }); - - return new MultiFilePatch( - nextLayeredBuffer.buffer, - nextLayeredBuffer.layers, - nextFilePatches, - ); + return this.clone({...nextLayeredBuffer, filePatches: nextFilePatches}); } getUnstagePatchForHunk(hunk) { @@ -130,7 +120,7 @@ export default class MultiFilePatch { if (lastSelectedRows.size === 0) { const [firstPatch] = this.getFilePatches(); if (!firstPatch) { - return [[0, 0], [0, 0]]; + return Range.fromObject([[0, 0], [0, 0]]); } return firstPatch.getFirstChangeRange(); @@ -146,12 +136,12 @@ export default class MultiFilePatch { changeLoop: for (const change of hunk.getChanges()) { for (const {intersection, gap} of change.intersectRows(lastSelectedRows, true)) { - // Only include a partial range if this intersection includes the last selected buffer row. + // Only include a partial range if this intersection includes the last selected buffer row. includesMax = intersection.intersectsRow(lastMax); const delta = includesMax ? lastMax - intersection.start.row + 1 : intersection.getRowCount(); if (gap) { - // Range of unselected changes. + // Range of unselected changes. hunkSelectionOffset += delta; } From ccced0b8343d1b89e413513f68b2669ea120a6d5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 16:20:02 -0500 Subject: [PATCH 0908/4053] Test fixture builders for the MultiFilePatch models --- test/builder/patch.js | 275 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 test/builder/patch.js diff --git a/test/builder/patch.js b/test/builder/patch.js new file mode 100644 index 0000000000..ea0efcf900 --- /dev/null +++ b/test/builder/patch.js @@ -0,0 +1,275 @@ +// Builders for classes related to MultiFilePatches. + +import {TextBuffer} from 'atom'; +import MultiFilePatch from '../../lib/models/patch/multi-file-patch'; +import FilePatch from '../../lib/models/patch/file-patch'; +import File from '../../lib/models/patch/file'; +import Patch from '../../lib/models/patch/patch'; +import Hunk from '../../lib/models/patch/hunk'; +import {Unchanged, Addition, Deletion, NoNewline} from '../../lib/models/patch/region'; + +class LayeredBuffer { + constructor() { + this.buffer = new TextBuffer(); + this.layers = ['patch', 'hunk', 'unchanged', 'addition', 'deletion', 'noNewline'].reduce((layers, name) => { + layers[name] = this.buffer.addMarkerLayer(); + return layers; + }, {}); + } + + getInsertionPoint() { + return this.buffer.getEndPosition(); + } + + getLayer(markerLayerName) { + const layer = this.layers[markerLayerName]; + if (!layer) { + throw new Error(`invalid marker layer name: ${markerLayerName}`); + } + return layer; + } + + appendMarked(markerLayerName, lines) { + const startPosition = this.buffer.getEndPosition(); + const layer = this.getLayer(markerLayerName); + this.buffer.append(lines.join('\n')); + const marker = layer.markRange([startPosition, this.buffer.getEndPosition()]); + this.buffer.append('\n'); + return marker; + } + + markFrom(markerLayerName, startPosition) { + const endPosition = this.buffer.getEndPosition(); + const layer = this.getLayer(markerLayerName); + return layer.markRange([startPosition, endPosition]); + } + + wrapReturn(object) { + return { + buffer: this.buffer, + layers: this.layers, + ...object, + }; + } +} + +class MultiFilePatchBuilder { + constructor(layeredBuffer = null) { + this.layeredBuffer = layeredBuffer; + + this.filePatches = []; + } + + addFilePatch(block) { + const filePatch = new FilePatchBuilder(this.layeredBuffer); + block(filePatch); + this.filePatches.push(filePatch.build().filePatch); + return this; + } + + build() { + return this.layeredBuffer.wrapReturn({ + multiFilePatch: new MultiFilePatch({ + buffer: this.layeredBuffer.buffer, + layers: this.layeredBuffer.layers, + filePatches: this.filePatches, + }), + }); + } +} + +class FilePatchBuilder { + constructor(layeredBuffer = null) { + this.layeredBuffer = layeredBuffer; + + this.oldFile = new File({path: 'file', mode: '100644'}); + this.newFile = new File({path: 'file', mode: '100644'}); + + this.patchBuilder = new PatchBuilder(this.layeredBuffer); + } + + setOldFile(block) { + const file = new FileBuilder(); + block(file); + this.oldFile = file.build().file; + return this; + } + + setNewFile(block) { + const file = new FileBuilder(); + block(file); + this.newFile = file.build().file; + return this; + } + + build() { + const {patch} = this.patchBuilder.build(); + + return this.layeredBuffer.wrapReturn({ + filePatch: new FilePatch(this.oldFile, this.newFile, patch), + }); + } +} + +class FileBuilder { + constructor() { + this.path = 'file.txt'; + this.mode = '100644'; + this.symlink = null; + } + + path(thePath) { + this.path = thePath; + return this; + } + + mode(theMode) { + this.mode = theMode; + return this; + } + + executable() { + return this.mode('100755'); + } + + symlinkTo(destinationPath) { + this.symlink = destinationPath; + return this.mode('120000'); + } + + build() { + return {file: new File({path: this.path, mode: this.mode, symlink: this.symlink})}; + } +} + +class PatchBuilder { + constructor(layeredBuffer = null) { + this.layeredBuffer = layeredBuffer; + + this.status = 'modified'; + this.hunks = []; + + this.patchStart = this.layeredBuffer.getInsertionPoint(); + } + + status(st) { + if (['modified', 'added', 'deleted'].indexOf(st) === -1) { + throw new Error(`Unrecognized status: ${st} (must be 'modified', 'added' or 'deleted')`); + } + + this.status = st; + return this; + } + + addHunk(block) { + const hunk = new HunkBuilder(this.layeredBuffer); + this.hunks.push(hunk.build().hunk); + return this; + } + + build() { + if (this.hunks.length === 0) { + this.addHunk(hunk => hunk.unchanged('0000').added('0001').deleted('0002').unchanged('0003')); + this.addHunk(hunk => hunk.startRow(10).unchanged('0004').added('0005').deleted('0006').unchanged('0007')); + } + + const marker = this.layeredBuffer.markFrom(this.patchStart()); + + return this.layeredBuffer.wrapReturn({ + patch: new Patch({status: this.status, hunks: this.hunks, marker}), + }); + } +} + +class HunkBuilder { + constructor(layeredBuffer = null) { + this.layeredBuffer = layeredBuffer; + + this.oldStartRow = 0; + this.oldRowCount = null; + this.newStartRow = 0; + this.newRowCount = null; + + this.sectionHeading = "don't care"; + + this.hunkStartPoint = this.layeredBuffer.getInsertionPoint(); + this.regions = []; + } + + oldRow(rowNumber) { + this.oldStartRow = rowNumber; + return this; + } + + unchanged(...lines) { + this.regions.push(new Unchanged(this.layeredBuffer.appendMarked(lines))); + return this; + } + + added(...lines) { + this.regions.push(new Addition(this.layeredBuffer.appendMarked(lines))); + return this; + } + + deleted(...lines) { + this.regions.push(new Deletion(this.layeredBuffer.appendMarked(lines))); + return this; + } + + noNewline() { + this.regions.push(new NoNewline(this.layeredBuffer.appendMarked(' No newline at end of file'))); + return this; + } + + build() { + if (this.oldRowCount === null) { + this.oldRowCount = this.regions.reduce((count, region) => region.when({ + unchanged: () => count + region.bufferRowCount(), + deletion: () => count + region.bufferRowCount(), + default: () => count, + }), 0); + } + + if (this.newRowCount === null) { + this.newRowCount = this.regions.reduce((count, region) => region.when({ + unchanged: () => count + region.bufferRowCount(), + addition: () => count + region.bufferRowCount(), + default: () => count, + }), 0); + } + + if (this.regions.length === 0) { + this.unchanged('0000').added('0001').deleted('0002').unchanged('0003'); + } + + const marker = this.layeredBuffer.markFrom('hunk', this.hunkStartPoint); + + return this.layeredBuffer.wrapReturn({ + hunk: new Hunk({ + oldStartRow: this.oldStartRow, + oldRowCount: this.oldRowCount, + newStartRow: this.newStartRow, + newRowCount: this.newRowCount, + sectionHeading: this.sectionHeading, + marker, + regions: this.regions, + }), + }); + } +} + +export function buildMultiFilePatch() { + return new MultiFilePatchBuilder(new LayeredBuffer()); +} + +export function buildFilePatch() { + return new FilePatchBuilder(new LayeredBuffer()); +} + +export function buildPatch() { + return new PatchBuilder(new LayeredBuffer()); +} + +export function buildHunk() { + return new HunkBuilder(new LayeredBuffer()); +} From 52945456ed63bde78bc3e57fa7763ed9acddc232 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 16:29:55 -0500 Subject: [PATCH 0909/4053] Default newFile to oldFile because they're almost always the same --- test/builder/patch.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index ea0efcf900..2ba04ccd0c 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -83,7 +83,7 @@ class FilePatchBuilder { this.layeredBuffer = layeredBuffer; this.oldFile = new File({path: 'file', mode: '100644'}); - this.newFile = new File({path: 'file', mode: '100644'}); + this.newFile = null; this.patchBuilder = new PatchBuilder(this.layeredBuffer); } @@ -105,6 +105,10 @@ class FilePatchBuilder { build() { const {patch} = this.patchBuilder.build(); + if (this.newFile === null) { + this.newFile = this.oldFile.clone(); + } + return this.layeredBuffer.wrapReturn({ filePatch: new FilePatch(this.oldFile, this.newFile, patch), }); From 8fc07fa57bc5a89f6435fa2a6036a1fd2e8d5889 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 16:37:48 -0500 Subject: [PATCH 0910/4053] Can't use the same names for properties and methods --- test/builder/patch.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index 2ba04ccd0c..e0e03dfef9 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -117,18 +117,18 @@ class FilePatchBuilder { class FileBuilder { constructor() { - this.path = 'file.txt'; - this.mode = '100644'; - this.symlink = null; + this._path = 'file.txt'; + this._mode = '100644'; + this._symlink = null; } path(thePath) { - this.path = thePath; + this._path = thePath; return this; } mode(theMode) { - this.mode = theMode; + this._mode = theMode; return this; } @@ -137,12 +137,12 @@ class FileBuilder { } symlinkTo(destinationPath) { - this.symlink = destinationPath; + this._symlink = destinationPath; return this.mode('120000'); } build() { - return {file: new File({path: this.path, mode: this.mode, symlink: this.symlink})}; + return {file: new File({path: this._path, mode: this._mode, symlink: this._symlink})}; } } From 483c69fd8d9c69f22f1533080634f1de1ccb08ed Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 16:38:00 -0500 Subject: [PATCH 0911/4053] Forgot the first argument to .appendMarked() --- test/builder/patch.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index e0e03dfef9..5add33f651 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -177,7 +177,7 @@ class PatchBuilder { this.addHunk(hunk => hunk.startRow(10).unchanged('0004').added('0005').deleted('0006').unchanged('0007')); } - const marker = this.layeredBuffer.markFrom(this.patchStart()); + const marker = this.layeredBuffer.markFrom('patch', this.patchStart); return this.layeredBuffer.wrapReturn({ patch: new Patch({status: this.status, hunks: this.hunks, marker}), @@ -206,22 +206,22 @@ class HunkBuilder { } unchanged(...lines) { - this.regions.push(new Unchanged(this.layeredBuffer.appendMarked(lines))); + this.regions.push(new Unchanged(this.layeredBuffer.appendMarked('unchanged', lines))); return this; } added(...lines) { - this.regions.push(new Addition(this.layeredBuffer.appendMarked(lines))); + this.regions.push(new Addition(this.layeredBuffer.appendMarked('addition', lines))); return this; } deleted(...lines) { - this.regions.push(new Deletion(this.layeredBuffer.appendMarked(lines))); + this.regions.push(new Deletion(this.layeredBuffer.appendMarked('deletion', lines))); return this; } noNewline() { - this.regions.push(new NoNewline(this.layeredBuffer.appendMarked(' No newline at end of file'))); + this.regions.push(new NoNewline(this.layeredBuffer.appendMarked('noNewline', ' No newline at end of file'))); return this; } From 85475053f51c76f947145f5832b8e5c119bc69d9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 16:39:08 -0500 Subject: [PATCH 0912/4053] First batch of examples that use the builder API --- test/models/patch/multi-file-patch.test.js | 62 ++++++++++++++-------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 1ea45887aa..8633e7d79b 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -1,6 +1,8 @@ import {TextBuffer} from 'atom'; import dedent from 'dedent-js'; +import {buildMultiFilePatch} from '../../builder/patch'; + import MultiFilePatch from '../../../lib/models/patch/multi-file-patch'; import FilePatch from '../../../lib/models/patch/file-patch'; import File, {nullFile} from '../../../lib/models/patch/file'; @@ -31,48 +33,66 @@ describe('MultiFilePatch', function() { }); it('detects when it is not empty', function() { - const mp = new MultiFilePatch({buffer, layers, filePatches: [buildFilePatchFixture(0)]}); - assert.isTrue(mp.anyPresent()); + const {multiFilePatch} = buildMultiFilePatch() + .addFilePatch(filePatch => { + filePatch + .setOldFile(file => file.path('file-0.txt')) + .setNewFile(file => file.path('file-0.txt')); + }) + .build(); + + assert.isTrue(multiFilePatch.anyPresent()); }); it('has an accessor for its file patches', function() { - const filePatches = [buildFilePatchFixture(0), buildFilePatchFixture(1)]; - const mp = new MultiFilePatch({buffer, layers, filePatches}); - assert.strictEqual(mp.getFilePatches(), filePatches); + const {multiFilePatch} = buildMultiFilePatch() + .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-0.txt'))) + .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-1.txt'))) + .build(); + + assert.lengthOf(multiFilePatch.getFilePatches(), 2); + const [fp0, fp1] = multiFilePatch.getFilePatches(); + assert.strictEqual(fp0.getOldPath(), 'file-0.txt'); + assert.strictEqual(fp1.getOldPath(), 'file-1.txt'); }); describe('didAnyChangeExecutableMode()', function() { it('detects when at least one patch contains an executable mode change', function() { - const yes = new MultiFilePatch({buffer, layers, filePatches: [ - buildFilePatchFixture(0, {oldFileMode: '100644', newFileMode: '100755'}), - buildFilePatchFixture(1), - ]}); + const {multiFilePatch: yes} = buildMultiFilePatch() + .addFilePatch(filePatch => { + filePatch.setOldFile(file => file.path('file-0.txt')); + filePatch.setNewFile(file => file.path('file-0.txt').executable()); + }) + .build(); assert.isTrue(yes.didAnyChangeExecutableMode()); }); it('detects when none of the patches contain an executable mode change', function() { - const no = new MultiFilePatch({buffer, layers, filePatches: [ - buildFilePatchFixture(0), - buildFilePatchFixture(1), - ]}); + const {multiFilePatch: no} = buildMultiFilePatch() + .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-0.txt'))) + .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-1.txt'))) + .build(); assert.isFalse(no.didAnyChangeExecutableMode()); }); }); describe('anyHaveTypechange()', function() { it('detects when at least one patch contains a symlink change', function() { - const yes = new MultiFilePatch({buffer, layers, filePatches: [ - buildFilePatchFixture(0, {oldFileMode: '100644', newFileMode: '120000', newFileSymlink: 'destination'}), - buildFilePatchFixture(1), - ]}); + const {multiFilePatch: yes} = buildMultiFilePatch() + .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-0.txt'))) + .addFilePatch(filePatch => { + filePatch.setOldFile(file => file.path('file-0.txt')); + filePatch.setNewFile(file => file.path('file-0.txt').symlink('somewhere.txt')); + }) + .build(); assert.isTrue(yes.anyHaveTypechange()); }); it('detects when none of its patches contain a symlink change', function() { - const no = new MultiFilePatch({buffer, layers, filePatches: [ - buildFilePatchFixture(0), - buildFilePatchFixture(1), - ]}); + const {multiFilePatch: no} = buildMultiFilePatch() + .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-0.txt'))) + .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-1.txt'))) + .build(); assert.isFalse(no.anyHaveTypechange()); }); }); From f3a8b1f964021700bd310f173cbac7f63754f1bd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 16:41:28 -0500 Subject: [PATCH 0913/4053] Build real Ranges in Patch methods --- lib/models/patch/patch.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 3d2ee1ea10..cc5cd9ef5f 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -246,16 +246,16 @@ export default class Patch { getFirstChangeRange() { const firstHunk = this.getHunks()[0]; if (!firstHunk) { - return [[0, 0], [0, 0]]; + return Range.fromObject([[0, 0], [0, 0]]); } const firstChange = firstHunk.getChanges()[0]; if (!firstChange) { - return [[0, 0], [0, 0]]; + return Range.fromObject([[0, 0], [0, 0]]); } const firstRow = firstChange.getStartBufferRow(); - return [[firstRow, 0], [firstRow, Infinity]]; + return Range.fromObject([[firstRow, 0], [firstRow, Infinity]]); } toStringIn(buffer) { From b6176ec38e18eb958ea1d543919ec2c825edb147 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 16:41:40 -0500 Subject: [PATCH 0914/4053] Delegate getFirstChangeRange() to the Patch --- lib/models/patch/file-patch.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 7f7ce899dc..2fa95ba2af 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -73,6 +73,10 @@ export default class FilePatch { return this.getPatch().getBuffer(); } + getFirstChangeRange() { + return this.getPatch().getFirstChangeRange(); + } + getMaxLineNumberWidth() { return this.getPatch().getMaxLineNumberWidth(); } From 566b9e9e09c6bb192011091ff15c6cf88fe5843b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 8 Nov 2018 16:45:50 -0500 Subject: [PATCH 0915/4053] Forward .status() and .addHunk() to PatchBuilder --- test/builder/patch.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/builder/patch.js b/test/builder/patch.js index 5add33f651..3d23862b3e 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -102,6 +102,16 @@ class FilePatchBuilder { return this; } + status(...args) { + this.patchBuilder.status(...args); + return this; + } + + addHunk(...args) { + this.patchBuilder.addHunk(...args); + return this; + } + build() { const {patch} = this.patchBuilder.build(); From 73d88c0bb0b1cb15bfd99e75fc159ff178dbcdfc Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 8 Nov 2018 16:24:47 -0800 Subject: [PATCH 0916/4053] fix MultiFilePatchController tests --- .../multi-file-patch-controller.test.js | 114 ++++++++++-------- 1 file changed, 61 insertions(+), 53 deletions(-) diff --git a/test/controllers/multi-file-patch-controller.test.js b/test/controllers/multi-file-patch-controller.test.js index f33be04ea2..6987f1e567 100644 --- a/test/controllers/multi-file-patch-controller.test.js +++ b/test/controllers/multi-file-patch-controller.test.js @@ -1,14 +1,14 @@ import path from 'path'; import fs from 'fs-extra'; import React from 'react'; -import {shallow} from 'enzyme'; +import {mount, shallow} from 'enzyme'; import MultiFilePatchController from '../../lib/controllers/multi-file-patch-controller'; import * as reporterProxy from '../../lib/reporter-proxy'; import {cloneRepository, buildRepository} from '../helpers'; describe('MultiFilePatchController', function() { - let atomEnv, repository, multiFilePatch; + let atomEnv, repository, multiFilePatch, filePatch; beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); @@ -17,9 +17,11 @@ describe('MultiFilePatchController', function() { repository = await buildRepository(workdirPath); // a.txt: unstaged changes - await fs.writeFile(path.join(workdirPath, 'a.txt'), '00\n01\n02\n03\n04\n05\n06'); + const filePath = 'a.txt'; + await fs.writeFile(path.join(workdirPath, filePath), '00\n01\n02\n03\n04\n05\n06'); - multiFilePatch = await repository.getStagedChangesPatch(); + multiFilePatch = await repository.getFilePatchForPath(filePath); + [filePatch] = multiFilePatch.getFilePatches(); }); afterEach(function() { @@ -30,8 +32,6 @@ describe('MultiFilePatchController', function() { const props = { repository, stagingStatus: 'unstaged', - relPath: 'a.txt', - isPartiallyStaged: false, multiFilePatch, hasUndoHistory: false, workspace: atomEnv.workspace, @@ -58,31 +58,32 @@ describe('MultiFilePatchController', function() { it('calls undoLastDiscard through with set arguments', function() { const undoLastDiscard = sinon.spy(); - const wrapper = shallow(buildApp({relPath: 'b.txt', undoLastDiscard})); - wrapper.find('MultiFilePatchView').prop('undoLastDiscard')(); + const wrapper = mount(buildApp({undoLastDiscard, stagingStatus: 'staged'})); - assert.isTrue(undoLastDiscard.calledWith('b.txt', repository)); + wrapper.find('MultiFilePatchView').prop('undoLastDiscard')(filePatch); + + assert.isTrue(undoLastDiscard.calledWith(filePatch.getPath(), repository)); }); it('calls surfaceFileAtPath with set arguments', function() { const surfaceFileAtPath = sinon.spy(); const wrapper = shallow(buildApp({relPath: 'c.txt', surfaceFileAtPath})); - wrapper.find('MultiFilePatchView').prop('surfaceFile')(); + wrapper.find('MultiFilePatchView').prop('surfaceFile')(filePatch); - assert.isTrue(surfaceFileAtPath.calledWith('c.txt', 'unstaged')); + assert.isTrue(surfaceFileAtPath.calledWith(filePatch.getPath(), 'unstaged')); }); describe('diveIntoMirrorPatch()', function() { it('destroys the current pane and opens the staged changes', async function() { const destroy = sinon.spy(); sinon.stub(atomEnv.workspace, 'open').resolves(); - const wrapper = shallow(buildApp({relPath: 'c.txt', stagingStatus: 'unstaged', destroy})); + const wrapper = shallow(buildApp({stagingStatus: 'unstaged', destroy})); - await wrapper.find('MultiFilePatchView').prop('diveIntoMirrorPatch')(); + await wrapper.find('MultiFilePatchView').prop('diveIntoMirrorPatch')(filePatch); assert.isTrue(destroy.called); assert.isTrue(atomEnv.workspace.open.calledWith( - 'atom-github://file-patch/c.txt' + + `atom-github://file-patch/${filePatch.getPath()}` + `?workdir=${encodeURIComponent(repository.getWorkingDirectoryPath())}&stagingStatus=staged`, )); }); @@ -90,13 +91,14 @@ describe('MultiFilePatchController', function() { it('destroys the current pane and opens the unstaged changes', async function() { const destroy = sinon.spy(); sinon.stub(atomEnv.workspace, 'open').resolves(); - const wrapper = shallow(buildApp({relPath: 'd.txt', stagingStatus: 'staged', destroy})); + const wrapper = shallow(buildApp({stagingStatus: 'staged', destroy})); + - await wrapper.find('MultiFilePatchView').prop('diveIntoMirrorPatch')(); + await wrapper.find('MultiFilePatchView').prop('diveIntoMirrorPatch')(filePatch); assert.isTrue(destroy.called); assert.isTrue(atomEnv.workspace.open.calledWith( - 'atom-github://file-patch/d.txt' + + `atom-github://file-patch/${filePatch.getPath()}` + `?workdir=${encodeURIComponent(repository.getWorkingDirectoryPath())}&stagingStatus=unstaged`, )); }); @@ -104,22 +106,22 @@ describe('MultiFilePatchController', function() { describe('openFile()', function() { it('opens an editor on the current file', async function() { - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - const editor = await wrapper.find('MultiFilePatchView').prop('openFile')([]); + const wrapper = shallow(buildApp({stagingStatus: 'unstaged'})); + const editor = await wrapper.find('MultiFilePatchView').prop('openFile')(filePatch, []); - assert.strictEqual(editor.getPath(), path.join(repository.getWorkingDirectoryPath(), 'a.txt')); + assert.strictEqual(editor.getPath(), path.join(repository.getWorkingDirectoryPath(), filePatch.getPath())); }); it('sets the cursor to a single position', async function() { const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - const editor = await wrapper.find('MultiFilePatchView').prop('openFile')([[1, 1]]); + const editor = await wrapper.find('MultiFilePatchView').prop('openFile')(filePatch, [[1, 1]]); assert.deepEqual(editor.getCursorBufferPositions().map(p => p.serialize()), [[1, 1]]); }); it('adds cursors at a set of positions', async function() { - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - const editor = await wrapper.find('MultiFilePatchView').prop('openFile')([[1, 1], [3, 1], [5, 0]]); + const wrapper = shallow(buildApp({stagingStatus: 'unstaged'})); + const editor = await wrapper.find('MultiFilePatchView').prop('openFile')(filePatch, [[1, 1], [3, 1], [5, 0]]); assert.deepEqual(editor.getCursorBufferPositions().map(p => p.serialize()), [[1, 1], [3, 1], [5, 0]]); }); @@ -128,37 +130,37 @@ describe('MultiFilePatchController', function() { describe('toggleFile()', function() { it('stages the current file if unstaged', async function() { sinon.spy(repository, 'stageFiles'); - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); + const wrapper = shallow(buildApp({stagingStatus: 'unstaged'})); - await wrapper.find('MultiFilePatchView').prop('toggleFile')(); + await wrapper.find('MultiFilePatchView').prop('toggleFile')(filePatch); - assert.isTrue(repository.stageFiles.calledWith(['a.txt'])); + assert.isTrue(repository.stageFiles.calledWith([filePatch.getPath()])); }); it('unstages the current file if staged', async function() { sinon.spy(repository, 'unstageFiles'); - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'staged'})); + const wrapper = shallow(buildApp({stagingStatus: 'staged'})); - await wrapper.find('MultiFilePatchView').prop('toggleFile')(); + await wrapper.find('MultiFilePatchView').prop('toggleFile')(filePatch); - assert.isTrue(repository.unstageFiles.calledWith(['a.txt'])); + assert.isTrue(repository.unstageFiles.calledWith([filePatch.getPath()])); }); it('is a no-op if a staging operation is already in progress', async function() { sinon.stub(repository, 'stageFiles').resolves('staged'); sinon.stub(repository, 'unstageFiles').resolves('unstaged'); - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - assert.strictEqual(await wrapper.find('MultiFilePatchView').prop('toggleFile')(), 'staged'); + const wrapper = shallow(buildApp({stagingStatus: 'unstaged'})); + assert.strictEqual(await wrapper.find('MultiFilePatchView').prop('toggleFile')(filePatch), 'staged'); wrapper.setProps({stagingStatus: 'staged'}); - assert.isNull(await wrapper.find('MultiFilePatchView').prop('toggleFile')()); + assert.isNull(await wrapper.find('MultiFilePatchView').prop('toggleFile')(filePatch)); const promise = wrapper.instance().patchChangePromise; wrapper.setProps({multiFilePatch: multiFilePatch.clone()}); await promise; - assert.strictEqual(await wrapper.find('MultiFilePatchView').prop('toggleFile')(), 'unstaged'); + assert.strictEqual(await wrapper.find('MultiFilePatchView').prop('toggleFile')(filePatch), 'unstaged'); }); }); @@ -219,7 +221,7 @@ describe('MultiFilePatchController', function() { it('records an event', function() { const wrapper = shallow(buildApp()); sinon.stub(reporterProxy, 'addEvent'); - wrapper.find('MultiFilePatchView').prop('undoLastDiscard')(); + wrapper.find('MultiFilePatchView').prop('undoLastDiscard')(filePatch); assert.isTrue(reporterProxy.addEvent.calledWith('undo-last-discard', { package: 'github', component: 'MultiFilePatchController', @@ -277,7 +279,7 @@ describe('MultiFilePatchController', function() { sinon.spy(otherPatch, 'getUnstagePatchForLines'); sinon.spy(repository, 'applyPatchToIndex'); - await wrapper.find('MultiFilePatchView').prop('toggleRows')(); + await wrapper.find('MultiFilePatchView').prop('toggleRows')(new Set([2]), 'hunk'); assert.sameMembers(Array.from(otherPatch.getUnstagePatchForLines.lastCall.args[0]), [2]); assert.isTrue(repository.applyPatchToIndex.calledWith(otherPatch.getUnstagePatchForLines.returnValues[0])); @@ -290,12 +292,13 @@ describe('MultiFilePatchController', function() { const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); await fs.chmod(p, 0o755); repository.refresh(); - const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); + const newMultiFilePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); - const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'unstaged'})); + const wrapper = shallow(buildApp({filePatch: newMultiFilePatch, stagingStatus: 'unstaged'})); + const [newFilePatch] = newMultiFilePatch.getFilePatches(); sinon.spy(repository, 'stageFileModeChange'); - await wrapper.find('MultiFilePatchView').prop('toggleModeChange')(); + await wrapper.find('MultiFilePatchView').prop('toggleModeChange')(newFilePatch); assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100755')); }); @@ -305,12 +308,13 @@ describe('MultiFilePatchController', function() { await fs.chmod(p, 0o755); await repository.stageFiles(['a.txt']); repository.refresh(); - const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: true}); + const newMultiFilePatch = await repository.getFilePatchForPath('a.txt', {staged: true}); + const [newFilePatch] = newMultiFilePatch.getFilePatches(); - const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'staged'})); + const wrapper = shallow(buildApp({filePatch: newMultiFilePatch, stagingStatus: 'staged'})); sinon.spy(repository, 'stageFileModeChange'); - await wrapper.find('MultiFilePatchView').prop('toggleModeChange')(); + await wrapper.find('MultiFilePatchView').prop('toggleModeChange')(newFilePatch); assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100644')); }); @@ -330,12 +334,13 @@ describe('MultiFilePatchController', function() { await fs.writeFile(p, 'fdsa\n', 'utf8'); repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); + const symlinkMultiPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); + const wrapper = shallow(buildApp({filePatch: symlinkMultiPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); + const [symlinkPatch] = symlinkMultiPatch.getFilePatches() sinon.spy(repository, 'stageFileSymlinkChange'); - await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(); + await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(symlinkPatch); assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); }); @@ -352,12 +357,13 @@ describe('MultiFilePatchController', function() { await fs.unlink(p); repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); + const symlinkMultiPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); + const wrapper = shallow(buildApp({filePatch: symlinkMultiPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); sinon.spy(repository, 'stageFiles'); - await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(); + const [symlinkPatch] = symlinkMultiPatch.getFilePatches() + await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(symlinkPatch); assert.isTrue(repository.stageFiles.calledWith(['waslink.txt'])); }); @@ -376,12 +382,13 @@ describe('MultiFilePatchController', function() { await repository.stageFiles(['waslink.txt']); repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); + const symlinkMultiPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); + const wrapper = shallow(buildApp({filePatch: symlinkMultiPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); sinon.spy(repository, 'stageFileSymlinkChange'); - await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(); + const [symlinkPatch] = symlinkMultiPatch.getFilePatches() + await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(symlinkPatch); assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); }); @@ -398,12 +405,13 @@ describe('MultiFilePatchController', function() { await fs.unlink(p); repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); + const symlinkMultiPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); + const wrapper = shallow(buildApp({filePatch: symlinkMultiPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); sinon.spy(repository, 'unstageFiles'); - await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(); + const [symlinkPatch] = symlinkMultiPatch.getFilePatches(); + await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(symlinkPatch); assert.isTrue(repository.unstageFiles.calledWith(['waslink.txt'])); }); From e62f508bfdcdf038dcfb99fac5d60ca65f21144d Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 8 Nov 2018 16:28:53 -0800 Subject: [PATCH 0917/4053] :fire: mount --- test/controllers/multi-file-patch-controller.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/controllers/multi-file-patch-controller.test.js b/test/controllers/multi-file-patch-controller.test.js index 6987f1e567..6273b04cec 100644 --- a/test/controllers/multi-file-patch-controller.test.js +++ b/test/controllers/multi-file-patch-controller.test.js @@ -2,6 +2,7 @@ import path from 'path'; import fs from 'fs-extra'; import React from 'react'; import {mount, shallow} from 'enzyme'; +import {shallow} from 'enzyme'; import MultiFilePatchController from '../../lib/controllers/multi-file-patch-controller'; import * as reporterProxy from '../../lib/reporter-proxy'; @@ -59,6 +60,7 @@ describe('MultiFilePatchController', function() { it('calls undoLastDiscard through with set arguments', function() { const undoLastDiscard = sinon.spy(); const wrapper = mount(buildApp({undoLastDiscard, stagingStatus: 'staged'})); + const wrapper = shallow(buildApp({undoLastDiscard, stagingStatus: 'staged'})); wrapper.find('MultiFilePatchView').prop('undoLastDiscard')(filePatch); From e6561aaea7895885ec1972eacfa34270f262c874 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 8 Nov 2018 16:30:25 -0800 Subject: [PATCH 0918/4053] I fuck up partial staging more often than not. --- test/controllers/multi-file-patch-controller.test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/controllers/multi-file-patch-controller.test.js b/test/controllers/multi-file-patch-controller.test.js index 6273b04cec..8669c1d6e9 100644 --- a/test/controllers/multi-file-patch-controller.test.js +++ b/test/controllers/multi-file-patch-controller.test.js @@ -1,7 +1,6 @@ import path from 'path'; import fs from 'fs-extra'; import React from 'react'; -import {mount, shallow} from 'enzyme'; import {shallow} from 'enzyme'; import MultiFilePatchController from '../../lib/controllers/multi-file-patch-controller'; @@ -59,7 +58,6 @@ describe('MultiFilePatchController', function() { it('calls undoLastDiscard through with set arguments', function() { const undoLastDiscard = sinon.spy(); - const wrapper = mount(buildApp({undoLastDiscard, stagingStatus: 'staged'})); const wrapper = shallow(buildApp({undoLastDiscard, stagingStatus: 'staged'})); wrapper.find('MultiFilePatchView').prop('undoLastDiscard')(filePatch); From 1ece7ebcb19f3ec74569a17ee215e7400bf2f31a Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 8 Nov 2018 16:40:19 -0800 Subject: [PATCH 0919/4053] :shirt: --- lib/models/repository-states/state.js | 1 - test/controllers/multi-file-patch-controller.test.js | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 9cb953ee48..3b1775ec3e 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -2,7 +2,6 @@ import {nullCommit} from '../commit'; import BranchSet from '../branch-set'; import RemoteSet from '../remote-set'; import {nullOperationStates} from '../operation-states'; -import FilePatch from '../patch/file-patch'; import MultiFilePatch from '../patch/multi-file-patch'; /** diff --git a/test/controllers/multi-file-patch-controller.test.js b/test/controllers/multi-file-patch-controller.test.js index 8669c1d6e9..88086e3ccb 100644 --- a/test/controllers/multi-file-patch-controller.test.js +++ b/test/controllers/multi-file-patch-controller.test.js @@ -336,7 +336,7 @@ describe('MultiFilePatchController', function() { repository.refresh(); const symlinkMultiPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); const wrapper = shallow(buildApp({filePatch: symlinkMultiPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); - const [symlinkPatch] = symlinkMultiPatch.getFilePatches() + const [symlinkPatch] = symlinkMultiPatch.getFilePatches(); sinon.spy(repository, 'stageFileSymlinkChange'); @@ -362,7 +362,7 @@ describe('MultiFilePatchController', function() { sinon.spy(repository, 'stageFiles'); - const [symlinkPatch] = symlinkMultiPatch.getFilePatches() + const [symlinkPatch] = symlinkMultiPatch.getFilePatches(); await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(symlinkPatch); assert.isTrue(repository.stageFiles.calledWith(['waslink.txt'])); @@ -387,7 +387,7 @@ describe('MultiFilePatchController', function() { sinon.spy(repository, 'stageFileSymlinkChange'); - const [symlinkPatch] = symlinkMultiPatch.getFilePatches() + const [symlinkPatch] = symlinkMultiPatch.getFilePatches(); await wrapper.find('MultiFilePatchView').prop('toggleSymlinkChange')(symlinkPatch); assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); From 513e83ce0d641fd3fd150775bef88e4a3da67071 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 8 Nov 2018 18:13:18 -0800 Subject: [PATCH 0920/4053] it was the missing brackets in the parlor with the lead pipe. --- test/views/multi-file-patch-view.test.js | 25 ++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index b09df3b557..362cd7f337 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -317,11 +317,12 @@ describe('MultiFilePatchView', function() { it("stages or unstages the mode change when the meta container's action is triggered", function() { const [fp] = filePatches.getFilePatches(); + const mfp = filePatches.clone({ - filePatches: fp.clone({ + filePatches: [fp.clone({ oldFile: fp.getOldFile().clone({mode: '100644'}), newFile: fp.getNewFile().clone({mode: '100755'}), - }), + })], }); const toggleModeChange = sinon.stub(); @@ -341,10 +342,10 @@ describe('MultiFilePatchView', function() { it('does not render if the symlink status is unchanged', function() { const [fp] = filePatches.getFilePatches(); const mfp = filePatches.clone({ - filePatches: fp.clone({ + filePatches: [fp.clone({ oldFile: fp.getOldFile().clone({mode: '100644'}), newFile: fp.getNewFile().clone({mode: '100755'}), - }), + })], }); const wrapper = mount(buildApp({multiFilePatch: mfp})); @@ -354,10 +355,10 @@ describe('MultiFilePatchView', function() { it('renders symlink change information within a meta container', function() { const [fp] = filePatches.getFilePatches(); const mfp = filePatches.clone({ - filePatches: fp.clone({ + filePatches: [fp.clone({ oldFile: fp.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), newFile: fp.getNewFile().clone({mode: '120000', symlink: '/new.txt'}), - }), + })], }); const wrapper = mount(buildApp({multiFilePatch: mfp, stagingStatus: 'unstaged'})); @@ -375,10 +376,10 @@ describe('MultiFilePatchView', function() { const toggleSymlinkChange = sinon.stub(); const [fp] = filePatches.getFilePatches(); const mfp = filePatches.clone({ - filePatches: fp.clone({ + filePatches: [fp.clone({ oldFile: fp.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), newFile: fp.getNewFile().clone({mode: '120000', symlink: '/new.txt'}), - }), + })], }); const wrapper = mount(buildApp({multiFilePatch: mfp, stagingStatus: 'staged', toggleSymlinkChange})); @@ -394,10 +395,10 @@ describe('MultiFilePatchView', function() { it('renders details for a symlink deletion', function() { const [fp] = filePatches.getFilePatches(); const mfp = filePatches.clone({ - filePatches: fp.clone({ + filePatches: [fp.clone({ oldFile: fp.getOldFile().clone({mode: '120000', symlink: '/old.txt'}), newFile: nullFile, - }), + })], }); const wrapper = mount(buildApp({multiFilePatch: mfp})); @@ -412,10 +413,10 @@ describe('MultiFilePatchView', function() { it('renders details for a symlink creation', function() { const [fp] = filePatches.getFilePatches(); const mfp = filePatches.clone({ - filePatches: fp.clone({ + filePatches: [fp.clone({ oldFile: nullFile, newFile: fp.getOldFile().clone({mode: '120000', symlink: '/new.txt'}), - }), + })], }); const wrapper = mount(buildApp({multiFilePatch: mfp})); From 4f80490c19559d2b30a1d1967e714f622a89e41f Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 8 Nov 2018 18:42:22 -0800 Subject: [PATCH 0921/4053] that's not how you get the hunks --- test/views/multi-file-patch-view.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 362cd7f337..642b0d2717 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -450,9 +450,10 @@ describe('MultiFilePatchView', function() { }, ], }]); - const hunks = fp.getHunks(); + const hunks = fp.getFilePatches()[0].patch.hunks; const wrapper = mount(buildApp({filePatch: fp})); + assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[0])); assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[1])); }); From f9930e54c287502ad73428dea478cf985e7ec26d Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 9 Nov 2018 11:57:28 +0900 Subject: [PATCH 0922/4053] Add more spacing between files --- styles/file-patch-view.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 2e3f73e6db..89f7255c6e 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -25,8 +25,7 @@ // TODO: Use better selector .react-atom-decoration { - padding: @component-padding; - padding-left: 0; + padding: @component-padding*2 @component-padding @component-padding 0; background-color: @syntax-background-color; & + .react-atom-decoration { @@ -38,6 +37,7 @@ display: flex; justify-content: space-between; align-items: center; + margin-top: @component-padding*2; padding: @component-padding/2; padding-left: @component-padding; border: 1px solid @base-border-color; From dfdbd477c1f5d20ef60a9fa3285185af2d84ac19 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 9 Nov 2018 12:57:43 +0900 Subject: [PATCH 0923/4053] Make text selections blue To match hunk selection --- styles/file-patch-view.less | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 89f7255c6e..629d573f93 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -33,6 +33,14 @@ } } + // Editor overrides + + atom-text-editor { + .selection .region { + background-color: mix(@button-background-color-selected, @syntax-background-color, 24%); + } + } + &-header { display: flex; justify-content: space-between; From 8da27e6667fa1a435cc8d863cee4187e3ee9a466 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 9 Nov 2018 12:58:23 +0900 Subject: [PATCH 0924/4053] Remove background color from hunks To differentiate them more from file headers --- styles/hunk-header-view.less | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/styles/hunk-header-view.less b/styles/hunk-header-view.less index 18e682fb87..64737bfa18 100644 --- a/styles/hunk-header-view.less +++ b/styles/hunk-header-view.less @@ -1,7 +1,9 @@ @import "variables"; @hunk-fg-color: @text-color-subtle; -@hunk-bg-color: mix(@syntax-text-color, @syntax-background-color, 4%); +@hunk-bg-color: mix(@syntax-text-color, @syntax-background-color, 0%); +@hunk-bg-color-hover: mix(@syntax-text-color, @syntax-background-color, 4%); +@hunk-bg-color-active: mix(@syntax-text-color, @syntax-background-color, 2%); .github-HunkHeaderView { font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace; @@ -23,8 +25,8 @@ white-space: nowrap; text-overflow: ellipsis; -webkit-font-smoothing: antialiased; - &:hover { background-color: mix(@syntax-text-color, @syntax-background-color, 8%); } - &:active { background-color: mix(@syntax-text-color, @syntax-background-color, 2%); } + &:hover { background-color: @hunk-bg-color-hover; } + &:active { background-color: @hunk-bg-color-active; } } &-stageButton, @@ -36,8 +38,8 @@ border: none; background-color: transparent; cursor: default; - &:hover { background-color: mix(@syntax-text-color, @syntax-background-color, 8%); } - &:active { background-color: mix(@syntax-text-color, @syntax-background-color, 2%); } + &:hover { background-color: @hunk-bg-color-hover; } + &:active { background-color: @hunk-bg-color-active; } .keystroke { margin-right: 1em; @@ -59,8 +61,8 @@ &-title, &-stageButton, &-discardButton { - &:hover { background-color: mix(@syntax-text-color, @syntax-background-color, 8%); } - &:active { background-color: mix(@syntax-text-color, @syntax-background-color, 2%); } + &:hover { background-color: @hunk-bg-color-hover; } + &:active { background-color: @hunk-bg-color-active; } } } From 70b4dee54475df563b10f47c12654ec11051db79 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 9 Nov 2018 13:10:15 +0900 Subject: [PATCH 0925/4053] Fix cursor line on diffs --- styles/file-patch-view.less | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 629d573f93..63a42c5ef1 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -182,10 +182,10 @@ &-line { // mixin .hunk-line-mixin(@bg;) { - background-color: fade(@bg, 18%); + background-color: fade(@bg, 16%); - .github-FilePatchView--active &.line.cursor-line { - background-color: fade(@bg, 28%); + &.line.cursor-line { + background-color: fade(@bg, 22%); } } From 4280a307b3a662d69440e68e4e3f5b15286aa166 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 9 Nov 2018 14:26:09 +0900 Subject: [PATCH 0926/4053] Add borders to hunk buttons --- styles/hunk-header-view.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/styles/hunk-header-view.less b/styles/hunk-header-view.less index 64737bfa18..7d6f3ccd51 100644 --- a/styles/hunk-header-view.less +++ b/styles/hunk-header-view.less @@ -36,6 +36,7 @@ padding-right: @component-padding; font-family: @font-family; border: none; + border-left: inherit; background-color: transparent; cursor: default; &:hover { background-color: @hunk-bg-color-hover; } @@ -50,6 +51,7 @@ &-discardButton:before { text-align: left; width: auto; + vertical-align: 2px; } } From c898ad64da6e21408f713c594b113951937367ae Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 9 Nov 2018 15:25:47 +0900 Subject: [PATCH 0927/4053] :fire: Remove gutter background It's currently broken anyways --- styles/hunk-header-view.less | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/styles/hunk-header-view.less b/styles/hunk-header-view.less index 7d6f3ccd51..11cffdec76 100644 --- a/styles/hunk-header-view.less +++ b/styles/hunk-header-view.less @@ -82,19 +82,3 @@ &:active { background-color: darken(@button-background-color-selected, 4%); } } } - - -// Hacks ----------------------- -// Please unhack (one day TM) - -// Make the gap in the gutter also use the same background as .github-HunkHeaderView -// Note: This only works with the default font-size -.github-FilePatchView .line-number[style="margin-top: 30px;"]:before { - content: ""; - position: absolute; - left: 0; - right: 0; - top: -30px; - height: 30px; - background-color: @hunk-bg-color; -} From 53177a8b806f7d09ba58f8802a1c97025968f56d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 08:15:07 -0500 Subject: [PATCH 0928/4053] Mark regions with exclusive: true to avoid stretching markers --- test/builder/patch.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index 3d23862b3e..10fe455c40 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -33,7 +33,7 @@ class LayeredBuffer { const startPosition = this.buffer.getEndPosition(); const layer = this.getLayer(markerLayerName); this.buffer.append(lines.join('\n')); - const marker = layer.markRange([startPosition, this.buffer.getEndPosition()]); + const marker = layer.markRange([startPosition, this.buffer.getEndPosition()], {exclusive: true}); this.buffer.append('\n'); return marker; } @@ -41,7 +41,7 @@ class LayeredBuffer { markFrom(markerLayerName, startPosition) { const endPosition = this.buffer.getEndPosition(); const layer = this.getLayer(markerLayerName); - return layer.markRange([startPosition, endPosition]); + return layer.markRange([startPosition, endPosition], {exclusive: true}); } wrapReturn(object) { From 5bebe03d3d098fc2a26b4b796e8ae4a777cb80bf Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 08:15:18 -0500 Subject: [PATCH 0929/4053] It helps if you actually call that block --- test/builder/patch.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/builder/patch.js b/test/builder/patch.js index 10fe455c40..d36bdf69d1 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -177,6 +177,7 @@ class PatchBuilder { addHunk(block) { const hunk = new HunkBuilder(this.layeredBuffer); + block(hunk); this.hunks.push(hunk.build().hunk); return this; } From 9b69f0ecfc0cf5a195b019cf7d97280707fbb26e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 08:20:51 -0500 Subject: [PATCH 0930/4053] Initialize default regions before computing hunk row counts --- test/builder/patch.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index d36bdf69d1..6c0b44cf61 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -237,6 +237,10 @@ class HunkBuilder { } build() { + if (this.regions.length === 0) { + this.unchanged('0000').added('0001').deleted('0002').unchanged('0003'); + } + if (this.oldRowCount === null) { this.oldRowCount = this.regions.reduce((count, region) => region.when({ unchanged: () => count + region.bufferRowCount(), @@ -253,10 +257,6 @@ class HunkBuilder { }), 0); } - if (this.regions.length === 0) { - this.unchanged('0000').added('0001').deleted('0002').unchanged('0003'); - } - const marker = this.layeredBuffer.markFrom('hunk', this.hunkStartPoint); return this.layeredBuffer.wrapReturn({ From e7feca77bef2e91b558dd67487299ad203d52ea0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 08:38:10 -0500 Subject: [PATCH 0931/4053] Compute valid hunk start rows --- test/builder/patch.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index 6c0b44cf61..70380fec0b 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -164,6 +164,7 @@ class PatchBuilder { this.hunks = []; this.patchStart = this.layeredBuffer.getInsertionPoint(); + this.drift = 0; } status(st) { @@ -176,9 +177,11 @@ class PatchBuilder { } addHunk(block) { - const hunk = new HunkBuilder(this.layeredBuffer); - block(hunk); - this.hunks.push(hunk.build().hunk); + const builder = new HunkBuilder(this.layeredBuffer, this.drift); + block(builder); + const {hunk, drift} = builder.build(); + this.hunks.push(hunk); + this.drift = drift; return this; } @@ -197,12 +200,13 @@ class PatchBuilder { } class HunkBuilder { - constructor(layeredBuffer = null) { + constructor(layeredBuffer = null, drift = 0) { this.layeredBuffer = layeredBuffer; + this.drift = drift; this.oldStartRow = 0; this.oldRowCount = null; - this.newStartRow = 0; + this.newStartRow = null; this.newRowCount = null; this.sectionHeading = "don't care"; @@ -249,6 +253,10 @@ class HunkBuilder { }), 0); } + if (this.newStartRow === null) { + this.newStartRow = this.oldStartRow + this.drift; + } + if (this.newRowCount === null) { this.newRowCount = this.regions.reduce((count, region) => region.when({ unchanged: () => count + region.bufferRowCount(), @@ -269,6 +277,7 @@ class HunkBuilder { marker, regions: this.regions, }), + drift: this.drift + this.newRowCount - this.oldRowCount, }); } } From 1475fce26bbb5b7ee7150e011c2eb8598420c8ab Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 08:44:07 -0500 Subject: [PATCH 0932/4053] It helps if you call methods that actually exist --- test/builder/patch.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index 70380fec0b..e4264ee003 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -187,8 +187,8 @@ class PatchBuilder { build() { if (this.hunks.length === 0) { - this.addHunk(hunk => hunk.unchanged('0000').added('0001').deleted('0002').unchanged('0003')); - this.addHunk(hunk => hunk.startRow(10).unchanged('0004').added('0005').deleted('0006').unchanged('0007')); + this.addHunk(hunk => hunk.oldRow(1).unchanged('0000').added('0001').deleted('0002').unchanged('0003')); + this.addHunk(hunk => hunk.oldRow(10).unchanged('0004').added('0005').deleted('0006').unchanged('0007')); } const marker = this.layeredBuffer.markFrom('patch', this.patchStart); From c51417079a5688466d92c3e3a2bdff943e5f2cb6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 08:59:43 -0500 Subject: [PATCH 0933/4053] Mark to the end of the previous line --- test/builder/patch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index e4264ee003..11b29eeb51 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -39,7 +39,7 @@ class LayeredBuffer { } markFrom(markerLayerName, startPosition) { - const endPosition = this.buffer.getEndPosition(); + const endPosition = this.buffer.getEndPosition().translate([-1, Infinity]); const layer = this.getLayer(markerLayerName); return layer.markRange([startPosition, endPosition], {exclusive: true}); } From d81a9cc4bb047246533b422802b9fa49b80f2c58 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 09:02:51 -0500 Subject: [PATCH 0934/4053] Allow nullFiles in FilePatches --- test/builder/patch.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index 11b29eeb51..116b249c0b 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -3,7 +3,7 @@ import {TextBuffer} from 'atom'; import MultiFilePatch from '../../lib/models/patch/multi-file-patch'; import FilePatch from '../../lib/models/patch/file-patch'; -import File from '../../lib/models/patch/file'; +import File, {nullFile} from '../../lib/models/patch/file'; import Patch from '../../lib/models/patch/patch'; import Hunk from '../../lib/models/patch/hunk'; import {Unchanged, Addition, Deletion, NoNewline} from '../../lib/models/patch/region'; @@ -95,6 +95,11 @@ class FilePatchBuilder { return this; } + nullOldFile() { + this.oldFile = nullFile; + return this; + } + setNewFile(block) { const file = new FileBuilder(); block(file); @@ -102,6 +107,11 @@ class FilePatchBuilder { return this; } + nullNewFile() { + this.newFile = nullFile; + return this; + } + status(...args) { this.patchBuilder.status(...args); return this; From e5d0b95b99b5bd7b7ed2bf1134a7caed41ebb653 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 09:06:12 -0500 Subject: [PATCH 0935/4053] Another method/property name collision --- test/builder/patch.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index 116b249c0b..cfc78b20ed 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -170,7 +170,7 @@ class PatchBuilder { constructor(layeredBuffer = null) { this.layeredBuffer = layeredBuffer; - this.status = 'modified'; + this._status = 'modified'; this.hunks = []; this.patchStart = this.layeredBuffer.getInsertionPoint(); @@ -182,7 +182,7 @@ class PatchBuilder { throw new Error(`Unrecognized status: ${st} (must be 'modified', 'added' or 'deleted')`); } - this.status = st; + this._status = st; return this; } @@ -197,14 +197,20 @@ class PatchBuilder { build() { if (this.hunks.length === 0) { - this.addHunk(hunk => hunk.oldRow(1).unchanged('0000').added('0001').deleted('0002').unchanged('0003')); - this.addHunk(hunk => hunk.oldRow(10).unchanged('0004').added('0005').deleted('0006').unchanged('0007')); + if (this._status === 'modified') { + this.addHunk(hunk => hunk.oldRow(1).unchanged('0000').added('0001').deleted('0002').unchanged('0003')); + this.addHunk(hunk => hunk.oldRow(10).unchanged('0004').added('0005').deleted('0006').unchanged('0007')); + } else if (this._status === 'added') { + this.addHunk(hunk => hunk.oldRow(1).added('0000', '0001', '0002', '0003')); + } else if (this._status === 'deleted') { + this.addHunk(hunk => hunk.oldRow(1).deleted('0000', '0001', '0002', '0003')); + } } const marker = this.layeredBuffer.markFrom('patch', this.patchStart); return this.layeredBuffer.wrapReturn({ - patch: new Patch({status: this.status, hunks: this.hunks, marker}), + patch: new Patch({status: this._status, hunks: this.hunks, marker}), }); } } From b7a4dc758293546e9c23c2856001f312768dfec9 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 9 Nov 2018 15:33:23 +0100 Subject: [PATCH 0936/4053] fix exec mode change tests --- test/views/multi-file-patch-view.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 642b0d2717..b1fc26d8b5 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -326,7 +326,7 @@ describe('MultiFilePatchView', function() { }); const toggleModeChange = sinon.stub(); - const wrapper = mount(buildApp({multiFilePatch: mfp, stagingStatus: 'unstaged', toggleModeChange})); + const wrapper = mount(buildApp({multiFilePatch: mfp, stagingStatus: 'staged', toggleModeChange})); const meta = wrapper.find('FilePatchMetaView[title="Mode change"]'); assert.isTrue(meta.exists()); From ec70e50394571f0acf3c29daf7f742d819479ab7 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 9 Nov 2018 15:33:46 +0100 Subject: [PATCH 0937/4053] fix hunk headers test --- test/views/multi-file-patch-view.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index b1fc26d8b5..1f1931f82e 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -431,7 +431,7 @@ describe('MultiFilePatchView', function() { describe('hunk headers', function() { it('renders one for each hunk', function() { - const fp = buildFilePatch([{ + const mfp = buildMultiFilePatch([{ oldPath: 'path.txt', oldMode: '100644', newPath: 'path.txt', @@ -451,8 +451,8 @@ describe('MultiFilePatchView', function() { ], }]); - const hunks = fp.getFilePatches()[0].patch.hunks; - const wrapper = mount(buildApp({filePatch: fp})); + const hunks = mfp.getFilePatches()[0].getHunks(); + const wrapper = mount(buildApp({multiFilePatch: mfp})); assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[0])); assert.isTrue(wrapper.find('HunkHeaderView').someWhere(h => h.prop('hunk') === hunks[1])); @@ -515,7 +515,7 @@ describe('MultiFilePatchView', function() { }); it('handles mousedown as a selection event', function() { - const fp = buildFilePatch([{ + const mfp = buildMultiFilePatch([{ oldPath: 'path.txt', oldMode: '100644', newPath: 'path.txt', @@ -536,9 +536,9 @@ describe('MultiFilePatchView', function() { }]); const selectedRowsChanged = sinon.spy(); - const wrapper = mount(buildApp({filePatch: fp, selectedRowsChanged, selectionMode: 'line'})); + const wrapper = mount(buildApp({multiFilePatch: mfp, selectedRowsChanged, selectionMode: 'line'})); - wrapper.find('HunkHeaderView').at(1).prop('mouseDown')({button: 0}, fp.getHunks()[1]); + wrapper.find('HunkHeaderView').at(1).prop('mouseDown')({button: 0}, mfp.getFilePatches()[0].getHunks()[1]); assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [4]); assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'hunk'); @@ -576,8 +576,8 @@ describe('MultiFilePatchView', function() { const wrapper = mount(buildApp({selectedRows: new Set([2]), discardRows, selectionMode: 'line'})); wrapper.find('HunkHeaderView').at(1).prop('discardSelection')(); - assert.sameMembers(Array.from(discardRows.lastCall.args[0]), [6, 7]); - assert.strictEqual(discardRows.lastCall.args[1], 'hunk'); + assert.sameMembers(Array.from(discardRows.lastCall.args[1]), [6, 7]); + assert.strictEqual(discardRows.lastCall.args[2], 'hunk'); }); }); From e0855f694e64264a2955b32d37f845277c0a774a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 09:40:41 -0500 Subject: [PATCH 0938/4053] Allow empty blocks for addHunk() and addFilePatch() to accept defaults --- test/builder/patch.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index cfc78b20ed..fa66f6486f 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -60,7 +60,7 @@ class MultiFilePatchBuilder { this.filePatches = []; } - addFilePatch(block) { + addFilePatch(block = () => {}) { const filePatch = new FilePatchBuilder(this.layeredBuffer); block(filePatch); this.filePatches.push(filePatch.build().filePatch); @@ -186,7 +186,7 @@ class PatchBuilder { return this; } - addHunk(block) { + addHunk(block = () => {}) { const builder = new HunkBuilder(this.layeredBuffer, this.drift); block(builder); const {hunk, drift} = builder.build(); From 1fd0b0d38c9afe9807b74e232056ed6074cc59d6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 09:40:49 -0500 Subject: [PATCH 0939/4053] Right right that expects an Array --- test/builder/patch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index fa66f6486f..70a25cd89d 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -252,7 +252,7 @@ class HunkBuilder { } noNewline() { - this.regions.push(new NoNewline(this.layeredBuffer.appendMarked('noNewline', ' No newline at end of file'))); + this.regions.push(new NoNewline(this.layeredBuffer.appendMarked('noNewline', [' No newline at end of file']))); return this; } From cd4ec3776b9f394d20ff8a82a1c35d285b80d4e9 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 9 Nov 2018 15:44:25 +0100 Subject: [PATCH 0940/4053] fix hunk lines tests --- test/views/multi-file-patch-view.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 1f1931f82e..de0be90163 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -17,7 +17,6 @@ describe('MultiFilePatchView', function() { const workdirPath = await cloneRepository(); repository = await buildRepository(workdirPath); - // filePatches = repository.getStagedChangesPatch(); // path.txt: unstaged changes filePatches = buildMultiFilePatch([{ @@ -624,8 +623,9 @@ describe('MultiFilePatchView', function() { const decorations = layerWrapper.find('Decoration[type="line-number"][gutterName="diff-icons"]'); assert.isTrue(decorations.exists()); }; - assertLayerDecorated(filePatch.getAdditionLayer()); - assertLayerDecorated(filePatch.getDeletionLayer()); + + assertLayerDecorated(filePatches.getAdditionLayer()); + assertLayerDecorated(filePatches.getDeletionLayer()); atomEnv.config.set('github.showDiffIconGutter', false); wrapper.update(); @@ -826,7 +826,7 @@ describe('MultiFilePatchView', function() { let linesPatch; beforeEach(function() { - linesPatch = buildFilePatch([{ + linesPatch = buildMultiFilePatch([{ oldPath: 'file.txt', oldMode: '100644', newPath: 'file.txt', @@ -851,7 +851,7 @@ describe('MultiFilePatchView', function() { }); it('decorates added lines', function() { - const wrapper = mount(buildApp({filePatch: linesPatch})); + const wrapper = mount(buildApp({multiFilePatch: linesPatch})); const decorationSelector = 'Decoration[type="line"][className="github-FilePatchView-line--added"]'; const decoration = wrapper.find(decorationSelector); @@ -862,7 +862,7 @@ describe('MultiFilePatchView', function() { }); it('decorates deleted lines', function() { - const wrapper = mount(buildApp({filePatch: linesPatch})); + const wrapper = mount(buildApp({multiFilePatch: linesPatch})); const decorationSelector = 'Decoration[type="line"][className="github-FilePatchView-line--deleted"]'; const decoration = wrapper.find(decorationSelector); @@ -873,7 +873,7 @@ describe('MultiFilePatchView', function() { }); it('decorates the nonewline line', function() { - const wrapper = mount(buildApp({filePatch: linesPatch})); + const wrapper = mount(buildApp({multiFilePatch: linesPatch})); const decorationSelector = 'Decoration[type="line"][className="github-FilePatchView-line--nonewline"]'; const decoration = wrapper.find(decorationSelector); @@ -1004,7 +1004,7 @@ describe('MultiFilePatchView', function() { assert.isTrue(surfaceFile.called); }); - describe('hunk mode navigation', function() { + describe.only('hunk mode navigation', function() { beforeEach(function() { filePatch = buildFilePatch([{ oldPath: 'path.txt', From 13ad6487dd8843ad100bafe201e7e33d71384eb3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 10:06:41 -0500 Subject: [PATCH 0941/4053] That rename we were just talking about --- test/builder/patch.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index 70a25cd89d..828250ea73 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -298,18 +298,18 @@ class HunkBuilder { } } -export function buildMultiFilePatch() { +export function multiFilePatchBuilder() { return new MultiFilePatchBuilder(new LayeredBuffer()); } -export function buildFilePatch() { +export function filePatchBuilder() { return new FilePatchBuilder(new LayeredBuffer()); } -export function buildPatch() { +export function patchBuilder() { return new PatchBuilder(new LayeredBuffer()); } -export function buildHunk() { +export function hunkBuilder() { return new HunkBuilder(new LayeredBuffer()); } From cca116e84679cc57c2f9878831fde45eb2810903 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 12:33:48 -0500 Subject: [PATCH 0942/4053] MultiFilePatch tests so far --- test/models/patch/multi-file-patch.test.js | 934 +++++++++------------ 1 file changed, 395 insertions(+), 539 deletions(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 8633e7d79b..e3aa99513c 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -1,7 +1,7 @@ import {TextBuffer} from 'atom'; import dedent from 'dedent-js'; -import {buildMultiFilePatch} from '../../builder/patch'; +import {multiFilePatchBuilder} from '../../builder/patch'; import MultiFilePatch from '../../../lib/models/patch/multi-file-patch'; import FilePatch from '../../../lib/models/patch/file-patch'; @@ -33,7 +33,7 @@ describe('MultiFilePatch', function() { }); it('detects when it is not empty', function() { - const {multiFilePatch} = buildMultiFilePatch() + const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(filePatch => { filePatch .setOldFile(file => file.path('file-0.txt')) @@ -45,7 +45,7 @@ describe('MultiFilePatch', function() { }); it('has an accessor for its file patches', function() { - const {multiFilePatch} = buildMultiFilePatch() + const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-0.txt'))) .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-1.txt'))) .build(); @@ -58,7 +58,7 @@ describe('MultiFilePatch', function() { describe('didAnyChangeExecutableMode()', function() { it('detects when at least one patch contains an executable mode change', function() { - const {multiFilePatch: yes} = buildMultiFilePatch() + const {multiFilePatch: yes} = multiFilePatchBuilder() .addFilePatch(filePatch => { filePatch.setOldFile(file => file.path('file-0.txt')); filePatch.setNewFile(file => file.path('file-0.txt').executable()); @@ -68,7 +68,7 @@ describe('MultiFilePatch', function() { }); it('detects when none of the patches contain an executable mode change', function() { - const {multiFilePatch: no} = buildMultiFilePatch() + const {multiFilePatch: no} = multiFilePatchBuilder() .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-0.txt'))) .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-1.txt'))) .build(); @@ -78,18 +78,18 @@ describe('MultiFilePatch', function() { describe('anyHaveTypechange()', function() { it('detects when at least one patch contains a symlink change', function() { - const {multiFilePatch: yes} = buildMultiFilePatch() + const {multiFilePatch: yes} = multiFilePatchBuilder() .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-0.txt'))) .addFilePatch(filePatch => { filePatch.setOldFile(file => file.path('file-0.txt')); - filePatch.setNewFile(file => file.path('file-0.txt').symlink('somewhere.txt')); + filePatch.setNewFile(file => file.path('file-0.txt').symlinkTo('somewhere.txt')); }) .build(); assert.isTrue(yes.anyHaveTypechange()); }); it('detects when none of its patches contain a symlink change', function() { - const {multiFilePatch: no} = buildMultiFilePatch() + const {multiFilePatch: no} = multiFilePatchBuilder() .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-0.txt'))) .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-1.txt'))) .build(); @@ -98,586 +98,442 @@ describe('MultiFilePatch', function() { }); it('computes the maximum line number width of any hunk in any patch', function() { - const mp = new MultiFilePatch({buffer, layers, filePatches: [ - buildFilePatchFixture(0), - buildFilePatchFixture(1), - ]}); - assert.strictEqual(mp.getMaxLineNumberWidth(), 2); + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-0.txt')); + fp.addHunk(h => h.oldRow(10)); + fp.addHunk(h => h.oldRow(99)); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-1.txt')); + fp.addHunk(h => h.oldRow(5)); + fp.addHunk(h => h.oldRow(15)); + }) + .build(); + + assert.strictEqual(multiFilePatch.getMaxLineNumberWidth(), 3); }); it('locates an individual FilePatch by marker lookup', function() { - const filePatches = []; + const builder = multiFilePatchBuilder(); for (let i = 0; i < 10; i++) { - filePatches.push(buildFilePatchFixture(i)); + builder.addFilePatch(fp => { + fp.setOldFile(f => f.path(`file-${i}.txt`)); + fp.addHunk(h => { + h.oldRow(1).unchanged('a', 'b').added('c').deleted('d').unchanged('e'); + }); + fp.addHunk(h => { + h.oldRow(10).unchanged('f').deleted('g', 'h', 'i').unchanged('j'); + }); + }); } - const mp = new MultiFilePatch({buffer, layers, filePatches}); + const {multiFilePatch} = builder.build(); + const fps = multiFilePatch.getFilePatches(); - assert.strictEqual(mp.getFilePatchAt(0), filePatches[0]); - assert.strictEqual(mp.getFilePatchAt(7), filePatches[0]); - assert.strictEqual(mp.getFilePatchAt(8), filePatches[1]); - assert.strictEqual(mp.getFilePatchAt(79), filePatches[9]); + assert.strictEqual(multiFilePatch.getFilePatchAt(0), fps[0]); + assert.strictEqual(multiFilePatch.getFilePatchAt(9), fps[0]); + assert.strictEqual(multiFilePatch.getFilePatchAt(10), fps[1]); + assert.strictEqual(multiFilePatch.getFilePatchAt(99), fps[9]); }); it('creates a set of all unique paths referenced by patches', function() { - const mp = new MultiFilePatch({buffer, layers, filePatches: [ - buildFilePatchFixture(0, {oldFilePath: 'file-0-before.txt', newFilePath: 'file-0-after.txt'}), - buildFilePatchFixture(1, {status: 'added', newFilePath: 'file-1.txt'}), - buildFilePatchFixture(2, {oldFilePath: 'file-2.txt', newFilePath: 'file-2.txt'}), - ]}); + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-0-before.txt')); + fp.setNewFile(f => f.path('file-0-after.txt')); + }) + .addFilePatch(fp => { + fp.status('added'); + fp.nullOldFile(); + fp.setNewFile(f => f.path('file-1.txt')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-2.txt')); + fp.setNewFile(f => f.path('file-2.txt')); + }) + .build(); assert.sameMembers( - Array.from(mp.getPathSet()), + Array.from(multiFilePatch.getPathSet()), ['file-0-before.txt', 'file-0-after.txt', 'file-1.txt', 'file-2.txt'], ); }); it('locates a Hunk by marker lookup', function() { - const filePatches = [ - buildFilePatchFixture(0), - buildFilePatchFixture(1), - buildFilePatchFixture(2), - ]; - const mp = new MultiFilePatch({buffer, layers, filePatches}); - - assert.strictEqual(mp.getHunkAt(0), filePatches[0].getHunks()[0]); - assert.strictEqual(mp.getHunkAt(3), filePatches[0].getHunks()[0]); - assert.strictEqual(mp.getHunkAt(4), filePatches[0].getHunks()[1]); - assert.strictEqual(mp.getHunkAt(7), filePatches[0].getHunks()[1]); - assert.strictEqual(mp.getHunkAt(8), filePatches[1].getHunks()[0]); - assert.strictEqual(mp.getHunkAt(15), filePatches[1].getHunks()[1]); - assert.strictEqual(mp.getHunkAt(16), filePatches[2].getHunks()[0]); - assert.strictEqual(mp.getHunkAt(23), filePatches[2].getHunks()[1]); + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.addHunk(h => h.oldRow(1).added('0', '1', '2', '3', '4')); + fp.addHunk(h => h.oldRow(10).deleted('5', '6', '7', '8', '9')); + }) + .addFilePatch(fp => { + fp.addHunk(h => h.oldRow(5).unchanged('10', '11').added('12').deleted('13')); + fp.addHunk(h => h.oldRow(20).unchanged('14').deleted('15')); + }) + .addFilePatch(fp => { + fp.status('deleted'); + fp.addHunk(h => h.oldRow(4).deleted('16', '17', '18', '19')); + }) + .build(); + + const [fp0, fp1, fp2] = multiFilePatch.getFilePatches(); + + assert.strictEqual(multiFilePatch.getHunkAt(0), fp0.getHunks()[0]); + assert.strictEqual(multiFilePatch.getHunkAt(4), fp0.getHunks()[0]); + assert.strictEqual(multiFilePatch.getHunkAt(5), fp0.getHunks()[1]); + assert.strictEqual(multiFilePatch.getHunkAt(9), fp0.getHunks()[1]); + assert.strictEqual(multiFilePatch.getHunkAt(10), fp1.getHunks()[0]); + assert.strictEqual(multiFilePatch.getHunkAt(15), fp1.getHunks()[1]); + assert.strictEqual(multiFilePatch.getHunkAt(16), fp2.getHunks()[0]); + assert.strictEqual(multiFilePatch.getHunkAt(19), fp2.getHunks()[0]); }); it('represents itself as an apply-ready string', function() { - const mp = new MultiFilePatch({buffer, layers, filePatches: [ - buildFilePatchFixture(0), - buildFilePatchFixture(1), - ]}); + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-0.txt')); + fp.addHunk(h => h.oldRow(1).unchanged('0;0;0').added('0;0;1').deleted('0;0;2').unchanged('0;0;3')); + fp.addHunk(h => h.oldRow(10).unchanged('0;1;0').added('0;1;1').deleted('0;1;2').unchanged('0;1;3')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-1.txt')); + fp.addHunk(h => h.oldRow(1).unchanged('1;0;0').added('1;0;1').deleted('1;0;2').unchanged('1;0;3')); + fp.addHunk(h => h.oldRow(10).unchanged('1;1;0').added('1;1;1').deleted('1;1;2').unchanged('1;1;3')); + }) + .build(); - assert.strictEqual(mp.toString(), dedent` + assert.strictEqual(multiFilePatch.toString(), dedent` diff --git a/file-0.txt b/file-0.txt --- a/file-0.txt +++ b/file-0.txt - @@ -0,3 +0,3 @@ - file-0 line-0 - +file-0 line-1 - -file-0 line-2 - file-0 line-3 + @@ -1,3 +1,3 @@ + 0;0;0 + +0;0;1 + -0;0;2 + 0;0;3 @@ -10,3 +10,3 @@ - file-0 line-4 - +file-0 line-5 - -file-0 line-6 - file-0 line-7 + 0;1;0 + +0;1;1 + -0;1;2 + 0;1;3 diff --git a/file-1.txt b/file-1.txt --- a/file-1.txt +++ b/file-1.txt - @@ -0,3 +0,3 @@ - file-1 line-0 - +file-1 line-1 - -file-1 line-2 - file-1 line-3 + @@ -1,3 +1,3 @@ + 1;0;0 + +1;0;1 + -1;0;2 + 1;0;3 @@ -10,3 +10,3 @@ - file-1 line-4 - +file-1 line-5 - -file-1 line-6 - file-1 line-7 + 1;1;0 + +1;1;1 + -1;1;2 + 1;1;3 `); }); it('adopts a buffer from a previous patch', function() { - const lastBuffer = buffer; - const lastLayers = layers; - const lastFilePatches = [ - buildFilePatchFixture(0), - buildFilePatchFixture(1), - buildFilePatchFixture(2, {noNewline: true}), - ]; - const lastPatch = new MultiFilePatch({buffer: lastBuffer, layers, filePatches: lastFilePatches}); - - buffer = new TextBuffer(); - layers = { - patch: buffer.addMarkerLayer(), - hunk: buffer.addMarkerLayer(), - unchanged: buffer.addMarkerLayer(), - addition: buffer.addMarkerLayer(), - deletion: buffer.addMarkerLayer(), - noNewline: buffer.addMarkerLayer(), - }; - const nextFilePatches = [ - buildFilePatchFixture(0), - buildFilePatchFixture(1), - buildFilePatchFixture(2), - buildFilePatchFixture(3, {noNewline: true}), - ]; - const nextPatch = new MultiFilePatch(buffer, layers, nextFilePatches); - - nextPatch.adoptBufferFrom(lastPatch); - - assert.strictEqual(nextPatch.getBuffer(), lastBuffer); - assert.strictEqual(nextPatch.getPatchLayer(), lastLayers.patch); - assert.strictEqual(nextPatch.getHunkLayer(), lastLayers.hunk); - assert.strictEqual(nextPatch.getUnchangedLayer(), lastLayers.unchanged); - assert.strictEqual(nextPatch.getAdditionLayer(), lastLayers.addition); - assert.strictEqual(nextPatch.getDeletionLayer(), lastLayers.deletion); - assert.strictEqual(nextPatch.getNoNewlineLayer(), lastLayers.noNewline); - - assert.lengthOf(nextPatch.getHunkLayer().getMarkers(), 8); - }); - - it('generates a stage patch for arbitrary buffer rows', function() { - const filePatches = [ - buildFilePatchFixture(0), - buildFilePatchFixture(1), - buildFilePatchFixture(2), - buildFilePatchFixture(3), - ]; - const original = new MultiFilePatch({buffer, layers, filePatches}); - const stagePatch = original.getStagePatchForLines(new Set([9, 14, 25, 26])); - - assert.strictEqual(stagePatch.getBuffer().getText(), dedent` - file-1 line-0 - file-1 line-1 - file-1 line-2 - file-1 line-3 - file-1 line-4 - file-1 line-6 - file-1 line-7 - file-3 line-0 - file-3 line-1 - file-3 line-2 - file-3 line-3 - - `); - - assert.lengthOf(stagePatch.getFilePatches(), 2); - const [fp0, fp1] = stagePatch.getFilePatches(); - assert.strictEqual(fp0.getOldPath(), 'file-1.txt'); - assertInFilePatch(fp0, stagePatch.getBuffer()).hunks( - { - startRow: 0, endRow: 3, - header: '@@ -0,3 +0,4 @@', - regions: [ - {kind: 'unchanged', string: ' file-1 line-0\n', range: [[0, 0], [0, 13]]}, - {kind: 'addition', string: '+file-1 line-1\n', range: [[1, 0], [1, 13]]}, - {kind: 'unchanged', string: ' file-1 line-2\n file-1 line-3\n', range: [[2, 0], [3, 13]]}, - ], - }, - { - startRow: 4, endRow: 6, - header: '@@ -10,3 +11,2 @@', - regions: [ - {kind: 'unchanged', string: ' file-1 line-4\n', range: [[4, 0], [4, 13]]}, - {kind: 'deletion', string: '-file-1 line-6\n', range: [[5, 0], [5, 13]]}, - {kind: 'unchanged', string: ' file-1 line-7\n', range: [[6, 0], [6, 13]]}, - ], - }, - ); - - assert.strictEqual(fp1.getOldPath(), 'file-3.txt'); - assertInFilePatch(fp1, stagePatch.getBuffer()).hunks( - { - startRow: 7, endRow: 10, - header: '@@ -0,3 +0,3 @@', - regions: [ - {kind: 'unchanged', string: ' file-3 line-0\n', range: [[7, 0], [7, 13]]}, - {kind: 'addition', string: '+file-3 line-1\n', range: [[8, 0], [8, 13]]}, - {kind: 'deletion', string: '-file-3 line-2\n', range: [[9, 0], [9, 13]]}, - {kind: 'unchanged', string: ' file-3 line-3\n', range: [[10, 0], [10, 13]]}, - ], - }, - ); - }); - - it('generates a stage patch from an arbitrary hunk', function() { - const filePatches = [ - buildFilePatchFixture(0), - buildFilePatchFixture(1), - ]; - const original = new MultiFilePatch({buffer, layers, filePatches}); - const hunk = original.getFilePatches()[0].getHunks()[1]; - const stagePatch = original.getStagePatchForHunk(hunk); - - assert.strictEqual(stagePatch.getBuffer().getText(), dedent` - file-0 line-4 - file-0 line-5 - file-0 line-6 - file-0 line-7 + const {multiFilePatch: lastMultiPatch, buffer: lastBuffer, layers: lastLayers} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('a0').added('a1').deleted('a2').unchanged('a3')); + }) + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('a4').deleted('a5').unchanged('a6')); + fp.addHunk(h => h.unchanged('a7').added('a8').unchanged('a9')); + }) + .addFilePatch(fp => { + fp.addHunk(h => h.oldRow(99).deleted('7').noNewline()); + }) + .build(); - `); - assert.lengthOf(stagePatch.getFilePatches(), 1); - const [fp0] = stagePatch.getFilePatches(); - assert.strictEqual(fp0.getOldPath(), 'file-0.txt'); - assert.strictEqual(fp0.getNewPath(), 'file-0.txt'); - assertInFilePatch(fp0, stagePatch.getBuffer()).hunks( - { - startRow: 0, endRow: 3, - header: '@@ -10,3 +10,3 @@', - regions: [ - {kind: 'unchanged', string: ' file-0 line-4\n', range: [[0, 0], [0, 13]]}, - {kind: 'addition', string: '+file-0 line-5\n', range: [[1, 0], [1, 13]]}, - {kind: 'deletion', string: '-file-0 line-6\n', range: [[2, 0], [2, 13]]}, - {kind: 'unchanged', string: ' file-0 line-7\n', range: [[3, 0], [3, 13]]}, - ], - }, - ); - }); + const {multiFilePatch: nextMultiPatch, buffer: nextBuffer, layers: nextLayers} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('b0', 'b1').added('b2').unchanged('b3', 'b4')); + }) + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('b5', 'b6').added('b7')); + }) + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('b8', 'b9').deleted('b10').unchanged('b11')); + fp.addHunk(h => h.oldRow(99).deleted('b12').noNewline()); + }) + .build(); - it('generates an unstage patch for arbitrary buffer rows', function() { - const filePatches = [ - buildFilePatchFixture(0), - buildFilePatchFixture(1), - buildFilePatchFixture(2), - buildFilePatchFixture(3), - ]; - const original = new MultiFilePatch({buffer, layers, filePatches}); - - const unstagePatch = original.getUnstagePatchForLines(new Set([1, 2, 21, 26, 29, 30])); - - assert.strictEqual(unstagePatch.getBuffer().getText(), dedent` - file-0 line-0 - file-0 line-1 - file-0 line-2 - file-0 line-3 - file-2 line-4 - file-2 line-5 - file-2 line-7 - file-3 line-0 - file-3 line-1 - file-3 line-2 - file-3 line-3 - file-3 line-4 - file-3 line-5 - file-3 line-6 - file-3 line-7 + assert.notStrictEqual(nextBuffer, lastBuffer); + assert.notStrictEqual(nextLayers, lastLayers); + + nextMultiPatch.adoptBufferFrom(lastMultiPatch); + + assert.strictEqual(nextMultiPatch.getBuffer(), lastBuffer); + assert.strictEqual(nextMultiPatch.getPatchLayer(), lastLayers.patch); + assert.strictEqual(nextMultiPatch.getHunkLayer(), lastLayers.hunk); + assert.strictEqual(nextMultiPatch.getUnchangedLayer(), lastLayers.unchanged); + assert.strictEqual(nextMultiPatch.getAdditionLayer(), lastLayers.addition); + assert.strictEqual(nextMultiPatch.getDeletionLayer(), lastLayers.deletion); + assert.strictEqual(nextMultiPatch.getNoNewlineLayer(), lastLayers.noNewline); + + assert.deepEqual(lastBuffer.getText(), dedent` + b0 + b1 + b2 + b3 + b4 + b5 + b6 + b7 + b8 + b9 + b10 + b11 + b12 + No newline at end of file `); - assert.lengthOf(unstagePatch.getFilePatches(), 3); - const [fp0, fp1, fp2] = unstagePatch.getFilePatches(); - assert.strictEqual(fp0.getOldPath(), 'file-0.txt'); - assertInFilePatch(fp0, unstagePatch.getBuffer()).hunks( - { - startRow: 0, endRow: 3, - header: '@@ -0,3 +0,3 @@', - regions: [ - {kind: 'unchanged', string: ' file-0 line-0\n', range: [[0, 0], [0, 13]]}, - {kind: 'deletion', string: '-file-0 line-1\n', range: [[1, 0], [1, 13]]}, - {kind: 'addition', string: '+file-0 line-2\n', range: [[2, 0], [2, 13]]}, - {kind: 'unchanged', string: ' file-0 line-3\n', range: [[3, 0], [3, 13]]}, - ], - }, - ); - - assert.strictEqual(fp1.getOldPath(), 'file-2.txt'); - assertInFilePatch(fp1, unstagePatch.getBuffer()).hunks( - { - startRow: 4, endRow: 6, - header: '@@ -10,3 +10,2 @@', - regions: [ - {kind: 'unchanged', string: ' file-2 line-4\n', range: [[4, 0], [4, 13]]}, - {kind: 'deletion', string: '-file-2 line-5\n', range: [[5, 0], [5, 13]]}, - {kind: 'unchanged', string: ' file-2 line-7\n', range: [[6, 0], [6, 13]]}, - ], - }, - ); + const assertMarkedLayerRanges = (layer, ranges) => { + assert.deepEqual(layer.getMarkers().map(m => m.getRange().serialize()), ranges); + }; - assert.strictEqual(fp2.getOldPath(), 'file-3.txt'); - assertInFilePatch(fp2, unstagePatch.getBuffer()).hunks( - { - startRow: 7, endRow: 10, - header: '@@ -0,3 +0,4 @@', - regions: [ - {kind: 'unchanged', string: ' file-3 line-0\n file-3 line-1\n', range: [[7, 0], [8, 13]]}, - {kind: 'addition', string: '+file-3 line-2\n', range: [[9, 0], [9, 13]]}, - {kind: 'unchanged', string: ' file-3 line-3\n', range: [[10, 0], [10, 13]]}, - ], - }, - { - startRow: 11, endRow: 14, - header: '@@ -10,3 +11,3 @@', - regions: [ - {kind: 'unchanged', string: ' file-3 line-4\n', range: [[11, 0], [11, 13]]}, - {kind: 'deletion', string: '-file-3 line-5\n', range: [[12, 0], [12, 13]]}, - {kind: 'addition', string: '+file-3 line-6\n', range: [[13, 0], [13, 13]]}, - {kind: 'unchanged', string: ' file-3 line-7\n', range: [[14, 0], [14, 13]]}, - ], - }, - ); + assertMarkedLayerRanges(lastLayers.patch, [ + [[0, 0], [4, 2]], [[5, 0], [7, 2]], [[8, 0], [13, 26]], + ]); + assertMarkedLayerRanges(lastLayers.hunk, [ + [[0, 0], [4, 2]], [[5, 0], [7, 2]], [[8, 0], [11, 3]], [[12, 0], [13, 26]], + ]); + assertMarkedLayerRanges(lastLayers.unchanged, [ + [[0, 0], [1, 2]], [[3, 0], [4, 2]], [[5, 0], [6, 2]], [[8, 0], [9, 2]], [[11, 0], [11, 3]], + ]); + assertMarkedLayerRanges(lastLayers.addition, [ + [[2, 0], [2, 2]], [[7, 0], [7, 2]], + ]); + assertMarkedLayerRanges(lastLayers.deletion, [ + [[10, 0], [10, 3]], [[12, 0], [12, 3]], + ]); + assertMarkedLayerRanges(lastLayers.noNewline, [ + [[13, 0], [13, 26]], + ]); }); - it('generates an unstaged patch for an arbitrary hunk', function() { - const filePatches = [ - buildFilePatchFixture(0), - buildFilePatchFixture(1), - ]; - const original = new MultiFilePatch({buffer, layers, filePatches}); - const hunk = original.getFilePatches()[1].getHunks()[0]; - const unstagePatch = original.getUnstagePatchForHunk(hunk); - - assert.strictEqual(unstagePatch.getBuffer().getText(), dedent` - file-1 line-0 - file-1 line-1 - file-1 line-2 - file-1 line-3 + describe('derived patch generation', function() { + let multiFilePatch, rowSet; - `); - assert.lengthOf(unstagePatch.getFilePatches(), 1); - const [fp0] = unstagePatch.getFilePatches(); - assert.strictEqual(fp0.getOldPath(), 'file-1.txt'); - assert.strictEqual(fp0.getNewPath(), 'file-1.txt'); - assertInFilePatch(fp0, unstagePatch.getBuffer()).hunks( - { - startRow: 0, endRow: 3, - header: '@@ -0,3 +0,3 @@', - regions: [ - {kind: 'unchanged', string: ' file-1 line-0\n', range: [[0, 0], [0, 13]]}, - {kind: 'deletion', string: '-file-1 line-1\n', range: [[1, 0], [1, 13]]}, - {kind: 'addition', string: '+file-1 line-2\n', range: [[2, 0], [2, 13]]}, - {kind: 'unchanged', string: ' file-1 line-3\n', range: [[3, 0], [3, 13]]}, - ], - }, - ); - }); - - // FIXME adapt these to the lifted method. - // describe('next selection range derivation', function() { - // it('selects the first change region after the highest buffer row', function() { - // const lastPatch = buildPatchFixture(); - // // Selected: - // // deletions (1-2) and partial addition (4 from 3-5) from hunk 0 - // // one deletion row (13 from 12-16) from the middle of hunk 1; - // // nothing in hunks 2 or 3 - // const lastSelectedRows = new Set([1, 2, 4, 5, 13]); - // - // const nBuffer = new TextBuffer({text: - // // 0 1 2 3 4 - // '0000\n0003\n0004\n0005\n0006\n' + - // // 5 6 7 8 9 10 11 12 13 14 15 - // '0007\n0008\n0009\n0010\n0011\n0012\n0014\n0015\n0016\n0017\n0018\n' + - // // 16 17 18 19 20 - // '0019\n0020\n0021\n0022\n0023\n' + - // // 21 22 23 - // '0024\n0025\n No newline at end of file\n', - // }); - // const nLayers = buildLayers(nBuffer); - // const nHunks = [ - // new Hunk({ - // oldStartRow: 3, oldRowCount: 3, newStartRow: 3, newRowCount: 5, // next row drift = +2 - // marker: markRange(nLayers.hunk, 0, 4), - // regions: [ - // new Unchanged(markRange(nLayers.unchanged, 0)), // 0 - // new Addition(markRange(nLayers.addition, 1)), // + 1 - // new Unchanged(markRange(nLayers.unchanged, 2)), // 2 - // new Addition(markRange(nLayers.addition, 3)), // + 3 - // new Unchanged(markRange(nLayers.unchanged, 4)), // 4 - // ], - // }), - // new Hunk({ - // oldStartRow: 12, oldRowCount: 9, newStartRow: 14, newRowCount: 7, // next row drift = +2 -2 = 0 - // marker: markRange(nLayers.hunk, 5, 15), - // regions: [ - // new Unchanged(markRange(nLayers.unchanged, 5)), // 5 - // new Addition(markRange(nLayers.addition, 6)), // +6 - // new Unchanged(markRange(nLayers.unchanged, 7, 9)), // 7 8 9 - // new Deletion(markRange(nLayers.deletion, 10, 13)), // -10 -11 -12 -13 - // new Addition(markRange(nLayers.addition, 14)), // +14 - // new Unchanged(markRange(nLayers.unchanged, 15)), // 15 - // ], - // }), - // new Hunk({ - // oldStartRow: 26, oldRowCount: 4, newStartRow: 26, newRowCount: 3, // next row drift = 0 -1 = -1 - // marker: markRange(nLayers.hunk, 16, 20), - // regions: [ - // new Unchanged(markRange(nLayers.unchanged, 16)), // 16 - // new Addition(markRange(nLayers.addition, 17)), // +17 - // new Deletion(markRange(nLayers.deletion, 18, 19)), // -18 -19 - // new Unchanged(markRange(nLayers.unchanged, 20)), // 20 - // ], - // }), - // new Hunk({ - // oldStartRow: 32, oldRowCount: 1, newStartRow: 31, newRowCount: 2, - // marker: markRange(nLayers.hunk, 22, 24), - // regions: [ - // new Unchanged(markRange(nLayers.unchanged, 22)), // 22 - // new Addition(markRange(nLayers.addition, 23)), // +23 - // new NoNewline(markRange(nLayers.noNewline, 24)), - // ], - // }), - // ]; - // const nextPatch = new Patch({status: 'modified', hunks: nHunks, buffer: nBuffer, layers: nLayers}); - // - // const nextRange = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); - // // Original buffer row 14 = the next changed row = new buffer row 11 - // assert.deepEqual(nextRange, [[11, 0], [11, Infinity]]); - // }); - // - // it('offsets the chosen selection index by hunks that were completely selected', function() { - // const buffer = buildBuffer(11); - // const layers = buildLayers(buffer); - // const lastPatch = new Patch({ - // status: 'modified', - // hunks: [ - // new Hunk({ - // oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 3, - // marker: markRange(layers.hunk, 0, 5), - // regions: [ - // new Unchanged(markRange(layers.unchanged, 0)), - // new Addition(markRange(layers.addition, 1, 2)), - // new Deletion(markRange(layers.deletion, 3, 4)), - // new Unchanged(markRange(layers.unchanged, 5)), - // ], - // }), - // new Hunk({ - // oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, - // marker: markRange(layers.hunk, 6, 11), - // regions: [ - // new Unchanged(markRange(layers.unchanged, 6)), - // new Addition(markRange(layers.addition, 7, 8)), - // new Deletion(markRange(layers.deletion, 9, 10)), - // new Unchanged(markRange(layers.unchanged, 11)), - // ], - // }), - // ], - // buffer, - // layers, - // }); - // // Select: - // // * all changes from hunk 0 - // // * partial addition (8 of 7-8) from hunk 1 - // const lastSelectedRows = new Set([1, 2, 3, 4, 8]); - // - // const nextBuffer = new TextBuffer({text: '0006\n0007\n0008\n0009\n0010\n0011\n'}); - // const nextLayers = buildLayers(nextBuffer); - // const nextPatch = new Patch({ - // status: 'modified', - // hunks: [ - // new Hunk({ - // oldStartRow: 5, oldRowCount: 4, newStartRow: 5, newRowCount: 4, - // marker: markRange(nextLayers.hunk, 0, 5), - // regions: [ - // new Unchanged(markRange(nextLayers.unchanged, 0)), - // new Addition(markRange(nextLayers.addition, 1)), - // new Deletion(markRange(nextLayers.deletion, 3, 4)), - // new Unchanged(markRange(nextLayers.unchanged, 5)), - // ], - // }), - // ], - // buffer: nextBuffer, - // layers: nextLayers, - // }); - // - // const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); - // assert.deepEqual(range, [[3, 0], [3, Infinity]]); - // }); - // - // it('selects the first row of the first change of the patch if no rows were selected before', function() { - // const lastPatch = buildPatchFixture(); - // const lastSelectedRows = new Set(); - // - // const buffer = lastPatch.getBuffer(); - // const layers = buildLayers(buffer); - // const nextPatch = new Patch({ - // status: 'modified', - // hunks: [ - // new Hunk({ - // oldStartRow: 1, oldRowCount: 3, newStartRow: 1, newRowCount: 4, - // marker: markRange(layers.hunk, 0, 4), - // regions: [ - // new Unchanged(markRange(layers.unchanged, 0)), - // new Addition(markRange(layers.addition, 1, 2)), - // new Deletion(markRange(layers.deletion, 3)), - // new Unchanged(markRange(layers.unchanged, 4)), - // ], - // }), - // ], - // buffer, - // layers, - // }); - // - // const range = nextPatch.getNextSelectionRange(lastPatch, lastSelectedRows); - // assert.deepEqual(range, [[1, 0], [1, Infinity]]); - // }); - // }); - - function buildFilePatchFixture(index, options = {}) { - const opts = { - oldFilePath: `file-${index}.txt`, - oldFileMode: '100644', - oldFileSymlink: null, - newFilePath: `file-${index}.txt`, - newFileMode: '100644', - newFileSymlink: null, - status: 'modified', - noNewline: false, - ...options, - }; + beforeEach(function() { + // The row content pattern here is: ${fileno};${hunkno};${lineno}, with a (**) if it's selected + multiFilePatch = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-0.txt')); + fp.addHunk(h => h.oldRow(1).unchanged('0;0;0').added('0;0;1').deleted('0;0;2').unchanged('0;0;3')); + fp.addHunk(h => h.oldRow(10).unchanged('0;1;0').added('0;1;1').deleted('0;1;2').unchanged('0;1;3')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-1.txt')); + fp.addHunk(h => h.oldRow(1).unchanged('1;0;0').added('1;0;1 (**)').deleted('1;0;2').unchanged('1;0;3')); + fp.addHunk(h => h.oldRow(10).unchanged('1;1;0').added('1;1;1').deleted('1;1;2 (**)').unchanged('1;1;3')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-2.txt')); + fp.addHunk(h => h.oldRow(1).unchanged('2;0;0').added('2;0;1').deleted('2;0;2').unchanged('2;0;3')); + fp.addHunk(h => h.oldRow(10).unchanged('2;1;0').added('2;1;1').deleted('2;2;2').unchanged('2;1;3')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-3.txt')); + fp.addHunk(h => h.oldRow(1).unchanged('3;0;0').added('3;0;1 (**)').deleted('3;0;2 (**)').unchanged('3;0;3')); + fp.addHunk(h => h.oldRow(10).unchanged('3;1;0').added('3;1;1').deleted('3;2;2').unchanged('3;1;3')); + }) + .build() + .multiFilePatch; - const rowOffset = buffer.getLastRow(); - for (let i = 0; i < 8; i++) { - buffer.append(`file-${index} line-${i}\n`); - } - if (opts.noNewline) { - buffer.append(' No newline at end of file\n'); - } + // Buffer rows corresponding to the rows marked with (**) above + rowSet = new Set([9, 14, 25, 26]); + }); - let oldFile = new File({path: opts.oldFilePath, mode: opts.oldFileMode, symlink: opts.oldFileSymlink}); - const newFile = new File({path: opts.newFilePath, mode: opts.newFileMode, symlink: opts.newFileSymlink}); + it('generates a stage patch for arbitrary buffer rows', function() { + const stagePatch = multiFilePatch.getStagePatchForLines(rowSet); + + assert.strictEqual(stagePatch.getBuffer().getText(), dedent` + 1;0;0 + 1;0;1 (**) + 1;0;2 + 1;0;3 + 1;1;0 + 1;1;2 (**) + 1;1;3 + 3;0;0 + 3;0;1 (**) + 3;0;2 (**) + 3;0;3 + + `); + + assert.lengthOf(stagePatch.getFilePatches(), 2); + const [fp0, fp1] = stagePatch.getFilePatches(); + assert.strictEqual(fp0.getOldPath(), 'file-1.txt'); + assertInFilePatch(fp0, stagePatch.getBuffer()).hunks( + { + startRow: 0, endRow: 3, + header: '@@ -1,3 +1,4 @@', + regions: [ + {kind: 'unchanged', string: ' 1;0;0\n', range: [[0, 0], [0, 5]]}, + {kind: 'addition', string: '+1;0;1 (**)\n', range: [[1, 0], [1, 10]]}, + {kind: 'unchanged', string: ' 1;0;2\n 1;0;3\n', range: [[2, 0], [3, 5]]}, + ], + }, + { + startRow: 4, endRow: 6, + header: '@@ -10,3 +11,2 @@', + regions: [ + {kind: 'unchanged', string: ' 1;1;0\n', range: [[4, 0], [4, 5]]}, + {kind: 'deletion', string: '-1;1;2 (**)\n', range: [[5, 0], [5, 10]]}, + {kind: 'unchanged', string: ' 1;1;3\n', range: [[6, 0], [6, 5]]}, + ], + }, + ); + + assert.strictEqual(fp1.getOldPath(), 'file-3.txt'); + assertInFilePatch(fp1, stagePatch.getBuffer()).hunks( + { + startRow: 7, endRow: 10, + header: '@@ -1,3 +1,3 @@', + regions: [ + {kind: 'unchanged', string: ' 3;0;0\n', range: [[7, 0], [7, 5]]}, + {kind: 'addition', string: '+3;0;1 (**)\n', range: [[8, 0], [8, 10]]}, + {kind: 'deletion', string: '-3;0;2 (**)\n', range: [[9, 0], [9, 10]]}, + {kind: 'unchanged', string: ' 3;0;3\n', range: [[10, 0], [10, 5]]}, + ], + }, + ); + }); - const mark = (layer, start, end = start) => layer.markRange([[rowOffset + start, 0], [rowOffset + end, Infinity]]); + it('generates a stage patch from an arbitrary hunk', function() { + const hunk = multiFilePatch.getFilePatches()[0].getHunks()[1]; + const stagePatch = multiFilePatch.getStagePatchForHunk(hunk); + + assert.strictEqual(stagePatch.getBuffer().getText(), dedent` + 0;1;0 + 0;1;1 + 0;1;2 + 0;1;3 + + `); + assert.lengthOf(stagePatch.getFilePatches(), 1); + const [fp0] = stagePatch.getFilePatches(); + assert.strictEqual(fp0.getOldPath(), 'file-0.txt'); + assert.strictEqual(fp0.getNewPath(), 'file-0.txt'); + assertInFilePatch(fp0, stagePatch.getBuffer()).hunks( + { + startRow: 0, endRow: 3, + header: '@@ -10,3 +10,3 @@', + regions: [ + {kind: 'unchanged', string: ' 0;1;0\n', range: [[0, 0], [0, 5]]}, + {kind: 'addition', string: '+0;1;1\n', range: [[1, 0], [1, 5]]}, + {kind: 'deletion', string: '-0;1;2\n', range: [[2, 0], [2, 5]]}, + {kind: 'unchanged', string: ' 0;1;3\n', range: [[3, 0], [3, 5]]}, + ], + }, + ); + }); - const withNoNewlineRegion = regions => { - if (opts.noNewline) { - regions.push(new NoNewline(mark(layers.noNewline, 8))); - } - return regions; - }; + it('generates an unstage patch for arbitrary buffer rows', function() { + const unstagePatch = multiFilePatch.getUnstagePatchForLines(rowSet); + + assert.strictEqual(unstagePatch.getBuffer().getText(), dedent` + 1;0;0 + 1;0;1 (**) + 1;0;3 + 1;1;0 + 1;1;1 + 1;1;2 (**) + 1;1;3 + 3;0;0 + 3;0;1 (**) + 3;0;2 (**) + 3;0;3 + + `); + + assert.lengthOf(unstagePatch.getFilePatches(), 2); + const [fp0, fp1] = unstagePatch.getFilePatches(); + assert.strictEqual(fp0.getOldPath(), 'file-1.txt'); + assertInFilePatch(fp0, unstagePatch.getBuffer()).hunks( + { + startRow: 0, endRow: 2, + header: '@@ -1,3 +1,2 @@', + regions: [ + {kind: 'unchanged', string: ' 1;0;0\n', range: [[0, 0], [0, 5]]}, + {kind: 'deletion', string: '-1;0;1 (**)\n', range: [[1, 0], [1, 10]]}, + {kind: 'unchanged', string: ' 1;0;3\n', range: [[2, 0], [2, 5]]}, + ], + }, + { + startRow: 3, endRow: 6, + header: '@@ -10,3 +9,4 @@', + regions: [ + {kind: 'unchanged', string: ' 1;1;0\n 1;1;1\n', range: [[3, 0], [4, 5]]}, + {kind: 'addition', string: '+1;1;2 (**)\n', range: [[5, 0], [5, 10]]}, + {kind: 'unchanged', string: ' 1;1;3\n', range: [[6, 0], [6, 5]]}, + ], + }, + ); + + assert.strictEqual(fp1.getOldPath(), 'file-3.txt'); + assertInFilePatch(fp1, unstagePatch.getBuffer()).hunks( + { + startRow: 7, endRow: 10, + header: '@@ -1,3 +1,3 @@', + regions: [ + {kind: 'unchanged', string: ' 3;0;0\n', range: [[7, 0], [7, 5]]}, + {kind: 'deletion', string: '-3;0;1 (**)\n', range: [[8, 0], [8, 10]]}, + {kind: 'addition', string: '+3;0;2 (**)\n', range: [[9, 0], [9, 10]]}, + {kind: 'unchanged', string: ' 3;0;3\n', range: [[10, 0], [10, 5]]}, + ], + }, + ); + }); - let hunks = []; - if (opts.status === 'modified') { - hunks = [ - new Hunk({ - oldStartRow: 0, newStartRow: 0, oldRowCount: 3, newRowCount: 3, - sectionHeading: `file-${index} hunk-0`, - marker: mark(layers.hunk, 0, 3), + it('generates an unstage patch for an arbitrary hunk', function() { + const hunk = multiFilePatch.getFilePatches()[1].getHunks()[0]; + const unstagePatch = multiFilePatch.getUnstagePatchForHunk(hunk); + + assert.strictEqual(unstagePatch.getBuffer().getText(), dedent` + 1;0;0 + 1;0;1 (**) + 1;0;2 + 1;0;3 + + `); + assert.lengthOf(unstagePatch.getFilePatches(), 1); + const [fp0] = unstagePatch.getFilePatches(); + assert.strictEqual(fp0.getOldPath(), 'file-1.txt'); + assert.strictEqual(fp0.getNewPath(), 'file-1.txt'); + assertInFilePatch(fp0, unstagePatch.getBuffer()).hunks( + { + startRow: 0, endRow: 3, + header: '@@ -1,3 +1,3 @@', regions: [ - new Unchanged(mark(layers.unchanged, 0)), - new Addition(mark(layers.addition, 1)), - new Deletion(mark(layers.deletion, 2)), - new Unchanged(mark(layers.unchanged, 3)), + {kind: 'unchanged', string: ' 1;0;0\n', range: [[0, 0], [0, 5]]}, + {kind: 'deletion', string: '-1;0;1 (**)\n', range: [[1, 0], [1, 10]]}, + {kind: 'addition', string: '+1;0;2\n', range: [[2, 0], [2, 5]]}, + {kind: 'unchanged', string: ' 1;0;3\n', range: [[3, 0], [3, 5]]}, ], - }), - new Hunk({ - oldStartRow: 10, newStartRow: 10, oldRowCount: 3, newRowCount: 3, - sectionHeading: `file-${index} hunk-1`, - marker: mark(layers.hunk, 4, opts.noNewline ? 8 : 7), - regions: withNoNewlineRegion([ - new Unchanged(mark(layers.unchanged, 4)), - new Addition(mark(layers.addition, 5)), - new Deletion(mark(layers.deletion, 6)), - new Unchanged(mark(layers.unchanged, 7)), - ]), - }), - ]; - } else if (opts.status === 'added') { - hunks = [ - new Hunk({ - oldStartRow: 0, newStartRow: 0, oldRowCount: 8, newRowCount: 8, - sectionHeading: `file-${index} hunk-0`, - marker: mark(layers.hunk, 0, opts.noNewline ? 8 : 7), - regions: withNoNewlineRegion([ - new Addition(mark(layers.addition, 0, 7)), - ]), - }), - ]; - - oldFile = nullFile; - } + }, + ); + }); + }); + + describe('next selection range derivation', function() { + it('selects the origin if the new patch is empty', function() { + const {multiFilePatch: lastMultiPatch} = multiFilePatchBuilder().addFilePatch().build(); + const {multiFilePatch: nextMultiPatch} = multiFilePatchBuilder().build(); - const marker = mark(layers.patch, 0, 7); - const patch = new Patch({status: opts.status, hunks, marker}); + const nextSelectionRange = nextMultiPatch.getNextSelectionRange(lastMultiPatch, new Set()); + assert.deepEqual(nextSelectionRange.serialize(), [[0, 0], [0, 0]]); + }); - return new FilePatch(oldFile, newFile, patch); - } + it('selects the first change row if there was no prior selection', function() { + const {multiFilePatch: lastMultiPatch} = multiFilePatchBuilder().build(); + const {multiFilePatch: nextMultiPatch} = multiFilePatchBuilder().addFilePatch().build(); + const nextSelectionRange = nextMultiPatch.getNextSelectionRange(lastMultiPatch, new Set()); + assert.deepEqual(nextSelectionRange.serialize(), [[1, 0], [1, Infinity]]); + }); + }); }); From 3c3d8ba62b0c59c8beef1fdf38e4b8f00cf16639 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 9 Nov 2018 17:29:34 +0100 Subject: [PATCH 0943/4053] =?UTF-8?q?use=20the=20new=20shiny=20`multiFileP?= =?UTF-8?q?atchBuilder`=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/views/multi-file-patch-view.test.js | 52 +++++++++++------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index de0be90163..e5678d278f 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -4,11 +4,12 @@ import {shallow, mount} from 'enzyme'; import {cloneRepository, buildRepository} from '../helpers'; import MultiFilePatchView from '../../lib/views/multi-file-patch-view'; import {buildFilePatch, buildMultiFilePatch} from '../../lib/models/patch'; +import {multiFilePatchBuilder} from '../builder/patch'; import {nullFile} from '../../lib/models/patch/file'; import FilePatch from '../../lib/models/patch/file-patch'; import RefHolder from '../../lib/models/ref-holder'; -describe('MultiFilePatchView', function() { +describe.only('MultiFilePatchView', function() { let atomEnv, workspace, repository, filePatches; beforeEach(async function() { @@ -18,26 +19,20 @@ describe('MultiFilePatchView', function() { const workdirPath = await cloneRepository(); repository = await buildRepository(workdirPath); - // path.txt: unstaged changes - filePatches = buildMultiFilePatch([{ - oldPath: 'path.txt', - oldMode: '100644', - newPath: 'path.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 4, oldLineCount: 3, newStartLine: 4, newLineCount: 4, - heading: 'zero', - lines: [' 0000', '+0001', '+0002', '-0003', ' 0004'], - }, - { - oldStartLine: 8, oldLineCount: 3, newStartLine: 9, newLineCount: 3, - heading: 'one', - lines: [' 0005', '+0006', '-0007', ' 0008'], - }, - ], - }]); + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('path.txt')); + fp.addHunk(h => { + h.oldRow(4); + h.unchanged('0000').added('0001', '0002').deleted('0003').unchanged('0004'); + }); + fp.addHunk(h => { + h.oldRow(8); + h.unchanged('0005').added('0006').deleted('0007').unchanged('0008'); + }); + }).build(); + + filePatches = multiFilePatch; }); afterEach(function() { @@ -899,7 +894,8 @@ describe('MultiFilePatchView', function() { describe('when viewing an empty patch', function() { it('renders an empty patch message', function() { - const wrapper = shallow(buildApp({filePatch: FilePatch.createNull()})); + const {multiFilePatch: emptyMfp} = multiFilePatchBuilder().build(); + const wrapper = shallow(buildApp({multiFilePatch: emptyMfp})); assert.isTrue(wrapper.find('.github-FilePatchView').hasClass('github-FilePatchView--blank')); assert.isTrue(wrapper.find('.github-FilePatchView-message').exists()); }); @@ -1004,9 +1000,9 @@ describe('MultiFilePatchView', function() { assert.isTrue(surfaceFile.called); }); - describe.only('hunk mode navigation', function() { + describe('hunk mode navigation', function() { beforeEach(function() { - filePatch = buildFilePatch([{ + filePatches = buildFilePatch([{ oldPath: 'path.txt', oldMode: '100644', newPath: 'path.txt', @@ -1045,7 +1041,7 @@ describe('MultiFilePatchView', function() { it('advances the selection to the next hunks', function() { const selectedRowsChanged = sinon.spy(); const selectedRows = new Set([1, 7, 10]); - const wrapper = mount(buildApp({filePatch, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); + const wrapper = mount(buildApp({multiFilePatch: filePatches, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setSelectedBufferRanges([ [[0, 0], [2, 4]], // hunk 0 @@ -1069,7 +1065,7 @@ describe('MultiFilePatchView', function() { it('does not advance a selected hunk at the end of the patch', function() { const selectedRowsChanged = sinon.spy(); const selectedRows = new Set([4, 13, 14]); - const wrapper = mount(buildApp({filePatch, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); + const wrapper = mount(buildApp({multiFilePatch: filePatches, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setSelectedBufferRanges([ [[3, 0], [5, 4]], // hunk 1 @@ -1091,7 +1087,7 @@ describe('MultiFilePatchView', function() { it('retreats the selection to the previous hunks', function() { const selectedRowsChanged = sinon.spy(); const selectedRows = new Set([4, 10, 13, 14]); - const wrapper = mount(buildApp({filePatch, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); + const wrapper = mount(buildApp({multiFilePatch: filePatches, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setSelectedBufferRanges([ [[3, 0], [5, 4]], // hunk 1 @@ -1115,7 +1111,7 @@ describe('MultiFilePatchView', function() { it('does not retreat a selected hunk at the beginning of the patch', function() { const selectedRowsChanged = sinon.spy(); const selectedRows = new Set([4, 10, 13, 14]); - const wrapper = mount(buildApp({filePatch, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); + const wrapper = mount(buildApp({multiFilePatch: filePatches, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setSelectedBufferRanges([ [[0, 0], [2, 4]], // hunk 0 From 5a16641c044b9fcc38e608142148b544481a8374 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 9 Nov 2018 17:49:48 +0100 Subject: [PATCH 0944/4053] replace old buildMultiPatch method with multiFilePatchBuilder --- test/views/multi-file-patch-view.test.js | 220 +++++++++-------------- 1 file changed, 87 insertions(+), 133 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index e5678d278f..5b9948d59d 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -120,22 +120,16 @@ describe.only('MultiFilePatchView', function() { selectedRowsChanged, })); - const nextPatch = buildFilePatch([{ - oldPath: 'path.txt', - oldMode: '100644', - newPath: 'path.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 5, oldLineCount: 4, newStartLine: 5, newLineCount: 3, - heading: 'heading', - lines: [' 0000', '+0001', ' 0002', '-0003', ' 0004'], - }, - ], - }]); - - wrapper.setProps({filePatch: nextPatch}); + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('path.txt')); + fp.addHunk(h => { + h.oldRow(5); + h.unchanged('0000').added('0001').unchanged('0002').deleted('0003').unchanged('0004'); + }); + }).build(); + + wrapper.setProps({multiFilePatch}); assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [3]); assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'line'); @@ -150,71 +144,53 @@ describe.only('MultiFilePatchView', function() { }); it('selects the next full hunk when a new file patch arrives in hunk selection mode', function() { - const multiHunkPatch = buildFilePatch([{ - oldPath: 'path.txt', - oldMode: '100644', - newPath: 'path.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 10, oldLineCount: 4, newStartLine: 10, newLineCount: 4, - heading: '0', - lines: [' 0000', '+0001', ' 0002', '-0003', ' 0004'], - }, - { - oldStartLine: 20, oldLineCount: 3, newStartLine: 20, newLineCount: 4, - heading: '1', - lines: [' 0005', '+0006', '+0007', '-0008', ' 0009'], - }, - { - oldStartLine: 30, oldLineCount: 3, newStartLine: 31, newLineCount: 3, - heading: '2', - lines: [' 0010', '+0011', '-0012', ' 0013'], - }, - { - oldStartLine: 40, oldLineCount: 4, newStartLine: 41, newLineCount: 4, - heading: '3', - lines: [' 0014', '-0015', ' 0016', '+0017', ' 0018'], - }, - ], - }]); + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('path.txt')); + fp.addHunk(h => { + h.oldRow(10); + h.unchanged('0000').added('0001').unchanged('0002').deleted('0003').unchanged('0004'); + }); + fp.addHunk(h => { + h.oldRow(20); + h.unchanged('0005').added('0006').added('0007').deleted('0008').unchanged('0009'); + }); + fp.addHunk(h => { + h.oldRow(30); + h.unchanged('0010').added('0011').deleted('0012').unchanged('0013'); + }); + fp.addHunk(h => { + h.oldRow(40); + h.unchanged('0014').deleted('0015').unchanged('0016').added('0017').unchanged('0018'); + }); + }).build(); const selectedRowsChanged = sinon.spy(); const wrapper = mount(buildApp({ - filePatch: multiHunkPatch, + multiFilePatch, selectedRows: new Set([6, 7, 8]), selectionMode: 'hunk', selectedRowsChanged, })); - const nextPatch = buildFilePatch([{ - oldPath: 'path.txt', - oldMode: '100644', - newPath: 'path.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 10, oldLineCount: 4, newStartLine: 10, newLineCount: 4, - heading: '0', - lines: [' 0000', '+0001', ' 0002', '-0003', ' 0004'], - }, - { - oldStartLine: 30, oldLineCount: 3, newStartLine: 30, newLineCount: 3, - heading: '2', - // 5 6 7 8 - lines: [' 0010', '+0011', '-0012', ' 0013'], - }, - { - oldStartLine: 40, oldLineCount: 4, newStartLine: 40, newLineCount: 4, - heading: '3', - lines: [' 0014', '-0015', ' 0016', '+0017', ' 0018'], - }, - ], - }]); - - wrapper.setProps({filePatch: nextPatch}); + const {multiFilePatch: nextMfp} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('path.txt')); + fp.addHunk(h => { + h.oldRow(10); + h.unchanged('0000').added('0001').unchanged('0002').deleted('0003').unchanged('0004'); + }); + fp.addHunk(h => { + h.oldRow(30); + h.unchanged('0010').added('0011').deleted('0012').unchanged('0013'); + }); + fp.addHunk(h => { + h.oldRow(40); + h.unchanged('0014').deleted('0015').unchanged('0016').added('0017').unchanged('0018'); + }); + }).build(); + + wrapper.setProps({multiFilePatch: nextMfp}); assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [6, 7]); assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'hunk'); @@ -425,25 +401,18 @@ describe.only('MultiFilePatchView', function() { describe('hunk headers', function() { it('renders one for each hunk', function() { - const mfp = buildMultiFilePatch([{ - oldPath: 'path.txt', - oldMode: '100644', - newPath: 'path.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 1, oldLineCount: 2, newStartLine: 1, newLineCount: 3, - heading: 'first hunk', - lines: [' 0000', '+0001', ' 0002'], - }, - { - oldStartLine: 10, oldLineCount: 3, newStartLine: 11, newLineCount: 2, - heading: 'second hunk', - lines: [' 0003', '-0004', ' 0005'], - }, - ], - }]); + const {multiFilePatch: mfp} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('path.txt')); + fp.addHunk(h => { + h.oldRow(1); + h.unchanged('0000').added('0001').unchanged('0002'); + }); + fp.addHunk(h => { + h.oldRow(10); + h.unchanged('0003').deleted('0004').unchanged('0005'); + }); + }).build(); const hunks = mfp.getFilePatches()[0].getHunks(); const wrapper = mount(buildApp({multiFilePatch: mfp})); @@ -509,25 +478,18 @@ describe.only('MultiFilePatchView', function() { }); it('handles mousedown as a selection event', function() { - const mfp = buildMultiFilePatch([{ - oldPath: 'path.txt', - oldMode: '100644', - newPath: 'path.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 1, oldLineCount: 2, newStartLine: 1, newLineCount: 3, - heading: 'first hunk', - lines: [' 0000', '+0001', ' 0002'], - }, - { - oldStartLine: 10, oldLineCount: 3, newStartLine: 11, newLineCount: 2, - heading: 'second hunk', - lines: [' 0003', '-0004', ' 0005'], - }, - ], - }]); + const {multiFilePatch: mfp} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('path.txt')); + fp.addHunk(h => { + h.oldRow(1); + h.unchanged('0000').added('0001').unchanged('0002'); + }); + fp.addHunk(h => { + h.oldRow(10); + h.unchanged('0003').deleted('0004').unchanged('0005'); + }); + }).build(); const selectedRowsChanged = sinon.spy(); const wrapper = mount(buildApp({multiFilePatch: mfp, selectedRowsChanged, selectionMode: 'line'})); @@ -821,28 +783,20 @@ describe.only('MultiFilePatchView', function() { let linesPatch; beforeEach(function() { - linesPatch = buildMultiFilePatch([{ - oldPath: 'file.txt', - oldMode: '100644', - newPath: 'file.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 6, - heading: 'first hunk', - lines: [' 0000', '+0001', '+0002', '-0003', '+0004', '+0005', ' 0006'], - }, - { - oldStartLine: 10, oldLineCount: 0, newStartLine: 13, newLineCount: 0, - heading: 'second hunk', - lines: [ - ' 0007', '-0008', '-0009', '-0010', ' 0011', '+0012', '+0013', '+0014', '-0015', ' 0016', - '\\ No newline at end of file', - ], - }, - ], - }]); + + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('path.txt')); + fp.addHunk(h => { + h.oldRow(1); + h.unchanged('0000').added('0001', '0002').deleted('0003').added('0004').added('0005').unchanged('0006'); + }); + fp.addHunk(h => { + h.oldRow(10); + h.unchanged('0007').deleted('0008', '0009', '0010').unchanged('0011').added('0012', '0013', '0014').deleted('0015').unchanged('0016').added('\\ No newline at end of file'); + }); + }).build(); + linesPatch = multiFilePatch; }); it('decorates added lines', function() { From 6679c9c37d46bd51c3e10ec0eac1b29ee8d2e5ce Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 9 Nov 2018 18:53:01 +0100 Subject: [PATCH 0945/4053] nonewline is its own special ting --- test/views/multi-file-patch-view.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 5b9948d59d..00a7676242 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -793,7 +793,7 @@ describe.only('MultiFilePatchView', function() { }); fp.addHunk(h => { h.oldRow(10); - h.unchanged('0007').deleted('0008', '0009', '0010').unchanged('0011').added('0012', '0013', '0014').deleted('0015').unchanged('0016').added('\\ No newline at end of file'); + h.unchanged('0007').deleted('0008', '0009', '0010').unchanged('0011').added('0012', '0013', '0014').deleted('0015').unchanged('0016').noNewline(); }); }).build(); linesPatch = multiFilePatch; From fc1faa73d3c8afcfc340416dd1390d67a66c4950 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 9 Nov 2018 18:53:26 +0100 Subject: [PATCH 0946/4053] greenify open file tests --- test/views/multi-file-patch-view.test.js | 45 ++++++++++-------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 00a7676242..ba6a328e71 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -1086,47 +1086,38 @@ describe.only('MultiFilePatchView', function() { }); describe('opening the file', function() { - let fp; + let mfp; beforeEach(function() { - fp = buildFilePatch([{ - oldPath: 'path.txt', - oldMode: '100644', - newPath: 'path.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 2, oldLineCount: 2, newStartLine: 2, newLineCount: 3, - heading: 'first hunk', - // 2 3 4 - lines: [' 0000', '+0001', ' 0002'], - }, - { - oldStartLine: 10, oldLineCount: 5, newStartLine: 11, newLineCount: 6, - heading: 'second hunk', - // 11 12 13 14 15 16 - lines: [' 0003', '+0004', '+0005', '-0006', ' 0007', '+0008', '-0009', ' 0010'], - }, - ], - }]); + const {multiFilePatch} = multiFilePatchBuilder().addFilePatch(fp => { + fp.setOldFile(f => f.path('path.txt')); + fp.addHunk(h => { + h.oldRow(1); + h.unchanged('0000').added('0001').unchanged('0002'); + }); + fp.addHunk(h => { + h.oldRow(10); + h.unchanged('0003').added('0004', '0005').deleted('0006').unchanged('0007').added('0008').deleted('0009').unchanged('0010'); + }); + }).build(); + + mfp = multiFilePatch; }); it('opens the file at the current unchanged row', function() { const openFile = sinon.spy(); - const wrapper = mount(buildApp({filePatch: fp, openFile})); + const wrapper = mount(buildApp({multiFilePatch: mfp, openFile})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setCursorBufferPosition([7, 2]); atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); - assert.isTrue(openFile.calledWith([[14, 2]])); }); it('opens the file at a current added row', function() { const openFile = sinon.spy(); - const wrapper = mount(buildApp({filePatch: fp, openFile})); + const wrapper = mount(buildApp({multiFilePatch: mfp, openFile})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setCursorBufferPosition([8, 3]); @@ -1138,7 +1129,7 @@ describe.only('MultiFilePatchView', function() { it('opens the file at the beginning of the previous added or unchanged row', function() { const openFile = sinon.spy(); - const wrapper = mount(buildApp({filePatch: fp, openFile})); + const wrapper = mount(buildApp({multiFilePatch: mfp, openFile})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setCursorBufferPosition([9, 2]); @@ -1150,7 +1141,7 @@ describe.only('MultiFilePatchView', function() { it('preserves multiple cursors', function() { const openFile = sinon.spy(); - const wrapper = mount(buildApp({filePatch: fp, openFile})); + const wrapper = mount(buildApp({multiFilePatch: mfp, openFile})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setCursorBufferPosition([3, 2]); From a142e524172cff27b021fbf91e2175bbe511898a Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 9 Nov 2018 18:53:41 +0100 Subject: [PATCH 0947/4053] remove old builder methods from mfp view test suite --- test/views/multi-file-patch-view.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index ba6a328e71..c5facd9bf9 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -3,7 +3,6 @@ import {shallow, mount} from 'enzyme'; import {cloneRepository, buildRepository} from '../helpers'; import MultiFilePatchView from '../../lib/views/multi-file-patch-view'; -import {buildFilePatch, buildMultiFilePatch} from '../../lib/models/patch'; import {multiFilePatchBuilder} from '../builder/patch'; import {nullFile} from '../../lib/models/patch/file'; import FilePatch from '../../lib/models/patch/file-patch'; From c0a296e3af9378ddadb67ddb4e17f95e271444f7 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 9 Nov 2018 19:19:01 +0100 Subject: [PATCH 0948/4053] fix hunk navigation tests Co-Authored-By: Tilde Ann Thurium --- test/views/multi-file-patch-view.test.js | 68 +++++++++++------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index c5facd9bf9..f14dd56cec 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -954,47 +954,39 @@ describe.only('MultiFilePatchView', function() { }); describe('hunk mode navigation', function() { + let mfp; + beforeEach(function() { - filePatches = buildFilePatch([{ - oldPath: 'path.txt', - oldMode: '100644', - newPath: 'path.txt', - newMode: '100644', - status: 'modified', - hunks: [ - { - oldStartLine: 4, oldLineCount: 2, newStartLine: 4, newLineCount: 3, - heading: 'zero', - lines: [' 0000', '+0001', ' 0002'], - }, - { - oldStartLine: 10, oldLineCount: 3, newStartLine: 11, newLineCount: 2, - heading: 'one', - lines: [' 0003', '-0004', ' 0005'], - }, - { - oldStartLine: 20, oldLineCount: 2, newStartLine: 20, newLineCount: 3, - heading: 'two', - lines: [' 0006', '+0007', ' 0008'], - }, - { - oldStartLine: 30, oldLineCount: 2, newStartLine: 31, newLineCount: 3, - heading: 'three', - lines: [' 0009', '+0010', ' 0011'], - }, - { - oldStartLine: 40, oldLineCount: 4, newStartLine: 42, newLineCount: 2, - heading: 'four', - lines: [' 0012', '-0013', '-0014', ' 0015'], - }, - ], - }]); + const {multiFilePatch} = multiFilePatchBuilder().addFilePatch(fp => { + fp.setOldFile(f => f.path('path.txt')); + fp.addHunk(h => { + h.oldRow(4); + h.unchanged('0000').added('0001').unchanged('0002'); + }); + fp.addHunk(h => { + h.oldRow(10); + h.unchanged('0003').deleted('0004').unchanged('0005'); + }); + fp.addHunk(h => { + h.oldRow(20); + h.unchanged('0006').added('0007').unchanged('0008'); + }); + fp.addHunk(h => { + h.oldRow(30); + h.unchanged('0009').added('0010').unchanged('0011'); + }); + fp.addHunk(h => { + h.oldRow(40); + h.unchanged('0012').deleted('0013', '0014').unchanged('0015'); + }); + }).build(); + mfp = multiFilePatch; }); it('advances the selection to the next hunks', function() { const selectedRowsChanged = sinon.spy(); const selectedRows = new Set([1, 7, 10]); - const wrapper = mount(buildApp({multiFilePatch: filePatches, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); + const wrapper = mount(buildApp({multiFilePatch: mfp, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setSelectedBufferRanges([ [[0, 0], [2, 4]], // hunk 0 @@ -1018,7 +1010,7 @@ describe.only('MultiFilePatchView', function() { it('does not advance a selected hunk at the end of the patch', function() { const selectedRowsChanged = sinon.spy(); const selectedRows = new Set([4, 13, 14]); - const wrapper = mount(buildApp({multiFilePatch: filePatches, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); + const wrapper = mount(buildApp({multiFilePatch: mfp, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setSelectedBufferRanges([ [[3, 0], [5, 4]], // hunk 1 @@ -1040,7 +1032,7 @@ describe.only('MultiFilePatchView', function() { it('retreats the selection to the previous hunks', function() { const selectedRowsChanged = sinon.spy(); const selectedRows = new Set([4, 10, 13, 14]); - const wrapper = mount(buildApp({multiFilePatch: filePatches, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); + const wrapper = mount(buildApp({multiFilePatch: mfp, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setSelectedBufferRanges([ [[3, 0], [5, 4]], // hunk 1 @@ -1064,7 +1056,7 @@ describe.only('MultiFilePatchView', function() { it('does not retreat a selected hunk at the beginning of the patch', function() { const selectedRowsChanged = sinon.spy(); const selectedRows = new Set([4, 10, 13, 14]); - const wrapper = mount(buildApp({multiFilePatch: filePatches, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); + const wrapper = mount(buildApp({multiFilePatch: mfp, selectedRowsChanged, selectedRows, selectionMode: 'hunk'})); const editor = wrapper.find('AtomTextEditor').instance().getModel(); editor.setSelectedBufferRanges([ [[0, 0], [2, 4]], // hunk 0 From 2bb78b0fe654bdc0244fbe1abe49ad39aa51b231 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 9 Nov 2018 11:06:15 -0800 Subject: [PATCH 0949/4053] fix typo in patch building for mfp file opening tests --- test/views/multi-file-patch-view.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index f14dd56cec..776f3ebd78 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -8,7 +8,7 @@ import {nullFile} from '../../lib/models/patch/file'; import FilePatch from '../../lib/models/patch/file-patch'; import RefHolder from '../../lib/models/ref-holder'; -describe.only('MultiFilePatchView', function() { +describe('MultiFilePatchView', function() { let atomEnv, workspace, repository, filePatches; beforeEach(async function() { @@ -1083,7 +1083,7 @@ describe.only('MultiFilePatchView', function() { const {multiFilePatch} = multiFilePatchBuilder().addFilePatch(fp => { fp.setOldFile(f => f.path('path.txt')); fp.addHunk(h => { - h.oldRow(1); + h.oldRow(2); h.unchanged('0000').added('0001').unchanged('0002'); }); fp.addHunk(h => { From 1fc0d85762bf5e979d8a4b6734f7fdc0bd51a5df Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 14:06:09 -0500 Subject: [PATCH 0950/4053] Break out of the correct loop --- lib/models/patch/multi-file-patch.js | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index d44d44c33c..a44860af83 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -129,12 +129,11 @@ export default class MultiFilePatch { const lastMax = Math.max(...lastSelectedRows); let lastSelectionIndex = 0; - for (const lastFilePatch of lastMultiFilePatch.getFilePatches()) { + patchLoop: for (const lastFilePatch of lastMultiFilePatch.getFilePatches()) { for (const hunk of lastFilePatch.getHunks()) { let includesMax = false; - let hunkSelectionOffset = 0; - changeLoop: for (const change of hunk.getChanges()) { + for (const change of hunk.getChanges()) { for (const {intersection, gap} of change.intersectRows(lastSelectedRows, true)) { // Only include a partial range if this intersection includes the last selected buffer row. includesMax = intersection.intersectsRow(lastMax); @@ -142,20 +141,14 @@ export default class MultiFilePatch { if (gap) { // Range of unselected changes. - hunkSelectionOffset += delta; + lastSelectionIndex += delta; } if (includesMax) { - break changeLoop; + break patchLoop; } } } - - lastSelectionIndex += hunkSelectionOffset; - - if (includesMax) { - break; - } } } From 3603a7650116e8f90c5597cffe1d266098992937 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 14:06:42 -0500 Subject: [PATCH 0951/4053] getNextSelectionRange() tests :tada: --- test/models/patch/multi-file-patch.test.js | 62 ++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index e3aa99513c..31a0a6b72f 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -535,5 +535,67 @@ describe('MultiFilePatch', function() { const nextSelectionRange = nextMultiPatch.getNextSelectionRange(lastMultiPatch, new Set()); assert.deepEqual(nextSelectionRange.serialize(), [[1, 0], [1, Infinity]]); }); + + it('preserves the numeric index of the highest selected change row', function() { + const {multiFilePatch: lastMultiPatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('.').added('0', '1', 'x *').unchanged('.')); + fp.addHunk(h => h.unchanged('.').deleted('2').added('3').unchanged('.')); + }) + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('.').deleted('4', '5 *', '6').unchanged('.')); + fp.addHunk(h => h.unchanged('.').added('7').unchanged('.')); + }) + .build(); + + const {multiFilePatch: nextMultiPatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('.').added('0', '1').unchanged('x', '.')); + fp.addHunk(h => h.unchanged('.').deleted('2').added('3').unchanged('.')); + }) + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('.').deleted('4', '6 *').unchanged('.')); + fp.addHunk(h => h.unchanged('.').added('7').unchanged('.')); + }) + .build(); + + const nextSelectionRange = nextMultiPatch.getNextSelectionRange(lastMultiPatch, new Set([3, 11])); + assert.deepEqual(nextSelectionRange.serialize(), [[11, 0], [11, Infinity]]); + }); + + it('skips hunks that were completely selected', function() { + const {multiFilePatch: lastMultiPatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('.').added('0').unchanged('.')); + fp.addHunk(h => h.unchanged('.').added('x *', 'x *').unchanged('.')); + }) + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('.').deleted('x *').unchanged('.')); + }) + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('.').added('x *', '1').deleted('2').unchanged('.')); + fp.addHunk(h => h.unchanged('.').deleted('x *').unchanged('.')); + fp.addHunk(h => h.unchanged('.', '.').deleted('4', '5 *', '6').unchanged('.')); + fp.addHunk(h => h.unchanged('.').deleted('7', '8').unchanged('.', '.')); + }) + .build(); + + const {multiFilePatch: nextMultiPatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('.').added('0').unchanged('.')); + }) + .addFilePatch(fp => { + fp.addHunk(h => h.unchanged('.', 'x').added('1').deleted('2').unchanged('.')); + fp.addHunk(h => h.unchanged('.', '.').deleted('4', '6 +').unchanged('.')); + fp.addHunk(h => h.unchanged('.').deleted('7', '8').unchanged('.', '.')); + }) + .build(); + + const nextSelectionRange = nextMultiPatch.getNextSelectionRange( + lastMultiPatch, + new Set([4, 5, 8, 11, 16, 21]), + ); + assert.deepEqual(nextSelectionRange.serialize(), [[11, 0], [11, Infinity]]); + }); }); }); From 201033e506ba0fc60f10fd69fc4ee439877efd7b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 14:08:26 -0500 Subject: [PATCH 0952/4053] :fire: unused imports, beforeEach, and lets --- test/models/patch/multi-file-patch.test.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 31a0a6b72f..110decc1c7 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -1,31 +1,11 @@ -import {TextBuffer} from 'atom'; import dedent from 'dedent-js'; import {multiFilePatchBuilder} from '../../builder/patch'; import MultiFilePatch from '../../../lib/models/patch/multi-file-patch'; -import FilePatch from '../../../lib/models/patch/file-patch'; -import File, {nullFile} from '../../../lib/models/patch/file'; -import Patch from '../../../lib/models/patch/patch'; -import Hunk from '../../../lib/models/patch/hunk'; -import {Unchanged, Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; import {assertInFilePatch} from '../../helpers'; describe('MultiFilePatch', function() { - let buffer, layers; - - beforeEach(function() { - buffer = new TextBuffer(); - layers = { - patch: buffer.addMarkerLayer(), - hunk: buffer.addMarkerLayer(), - unchanged: buffer.addMarkerLayer(), - addition: buffer.addMarkerLayer(), - deletion: buffer.addMarkerLayer(), - noNewline: buffer.addMarkerLayer(), - }; - }); - it('creates an empty patch when constructed with no arguments', function() { const empty = new MultiFilePatch({}); assert.isFalse(empty.anyPresent()); From 02407d38c4c4d941d184f5060dac39c9c7cb975a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 11:13:11 -0800 Subject: [PATCH 0953/4053] Fix MultiFilePatchController tests --- test/controllers/multi-file-patch-controller.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/controllers/multi-file-patch-controller.test.js b/test/controllers/multi-file-patch-controller.test.js index 88086e3ccb..8d6ab9ad95 100644 --- a/test/controllers/multi-file-patch-controller.test.js +++ b/test/controllers/multi-file-patch-controller.test.js @@ -273,7 +273,7 @@ describe('MultiFilePatchController', function() { it('applies an unstage patch to the index', async function() { await repository.stageFiles(['a.txt']); const otherPatch = await repository.getFilePatchForPath('a.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: otherPatch, stagingStatus: 'staged'})); + const wrapper = shallow(buildApp({multiFilePatch: otherPatch, stagingStatus: 'staged'})); wrapper.find('MultiFilePatchView').prop('selectedRowsChanged')(new Set([2])); sinon.spy(otherPatch, 'getUnstagePatchForLines'); @@ -404,9 +404,11 @@ describe('MultiFilePatchController', function() { await fs.unlink(p); + await repository.stageFiles(['waslink.txt']); + repository.refresh(); const symlinkMultiPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: symlinkMultiPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); + const wrapper = shallow(buildApp({multiFilePatch: symlinkMultiPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); sinon.spy(repository, 'unstageFiles'); From 11e78f464cd746a4b55c90a51ed52d04c24c22bb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 14:21:00 -0500 Subject: [PATCH 0954/4053] Test coverage for .clone() --- test/models/patch/multi-file-patch.test.js | 58 +++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 110decc1c7..e23bc01c98 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -1,6 +1,6 @@ import dedent from 'dedent-js'; -import {multiFilePatchBuilder} from '../../builder/patch'; +import {multiFilePatchBuilder, filePatchBuilder} from '../../builder/patch'; import MultiFilePatch from '../../../lib/models/patch/multi-file-patch'; import {assertInFilePatch} from '../../helpers'; @@ -24,6 +24,62 @@ describe('MultiFilePatch', function() { assert.isTrue(multiFilePatch.anyPresent()); }); + describe('clone', function() { + let original; + + beforeEach(function() { + original = multiFilePatchBuilder() + .addFilePatch() + .addFilePatch() + .build() + .multiFilePatch; + }); + + it('defaults to creating an exact copy', function() { + const dup = original.clone(); + + assert.strictEqual(dup.getBuffer(), original.getBuffer()); + assert.strictEqual(dup.getPatchLayer(), original.getPatchLayer()); + assert.strictEqual(dup.getHunkLayer(), original.getHunkLayer()); + assert.strictEqual(dup.getUnchangedLayer(), original.getUnchangedLayer()); + assert.strictEqual(dup.getAdditionLayer(), original.getAdditionLayer()); + assert.strictEqual(dup.getDeletionLayer(), original.getDeletionLayer()); + assert.strictEqual(dup.getNoNewlineLayer(), original.getNoNewlineLayer()); + assert.strictEqual(dup.getFilePatches(), original.getFilePatches()); + }); + + it('creates a copy with a new buffer and layer set', function() { + const {buffer, layers} = multiFilePatchBuilder().build(); + const dup = original.clone({buffer, layers}); + + assert.strictEqual(dup.getBuffer(), buffer); + assert.strictEqual(dup.getPatchLayer(), layers.patch); + assert.strictEqual(dup.getHunkLayer(), layers.hunk); + assert.strictEqual(dup.getUnchangedLayer(), layers.unchanged); + assert.strictEqual(dup.getAdditionLayer(), layers.addition); + assert.strictEqual(dup.getDeletionLayer(), layers.deletion); + assert.strictEqual(dup.getNoNewlineLayer(), layers.noNewline); + assert.strictEqual(dup.getFilePatches(), original.getFilePatches()); + }); + + it('creates a copy with a new set of file patches', function() { + const nfp = [ + filePatchBuilder().build().filePatch, + filePatchBuilder().build().filePatch, + ]; + + const dup = original.clone({filePatches: nfp}); + assert.strictEqual(dup.getBuffer(), original.getBuffer()); + assert.strictEqual(dup.getPatchLayer(), original.getPatchLayer()); + assert.strictEqual(dup.getHunkLayer(), original.getHunkLayer()); + assert.strictEqual(dup.getUnchangedLayer(), original.getUnchangedLayer()); + assert.strictEqual(dup.getAdditionLayer(), original.getAdditionLayer()); + assert.strictEqual(dup.getDeletionLayer(), original.getDeletionLayer()); + assert.strictEqual(dup.getNoNewlineLayer(), original.getNoNewlineLayer()); + assert.strictEqual(dup.getFilePatches(), nfp); + }); + }); + it('has an accessor for its file patches', function() { const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(filePatch => filePatch.setOldFile(file => file.path('file-0.txt'))) From c667d3ede4e87d87cd5178568549a25345597c5d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 14:24:15 -0500 Subject: [PATCH 0955/4053] getFirstChangeRange() returns a real Range object --- test/models/patch/patch.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 2f7738eab7..b5989ab3fe 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -592,7 +592,7 @@ describe('Patch', function() { describe('getFirstChangeRange', function() { it('accesses the range of the first change from the first hunk', function() { const {patch} = buildPatchFixture(); - assert.deepEqual(patch.getFirstChangeRange(), [[1, 0], [1, Infinity]]); + assert.deepEqual(patch.getFirstChangeRange().serialize(), [[1, 0], [1, Infinity]]); }); it('returns the origin if the first hunk is empty', function() { @@ -607,7 +607,7 @@ describe('Patch', function() { ]; const marker = markRange(layers.patch, 0); const patch = new Patch({status: 'modified', hunks, marker}); - assert.deepEqual(patch.getFirstChangeRange(), [[0, 0], [0, 0]]); + assert.deepEqual(patch.getFirstChangeRange().serialize(), [[0, 0], [0, 0]]); }); it('returns the origin if the patch is empty', function() { @@ -615,7 +615,7 @@ describe('Patch', function() { const layers = buildLayers(buffer); const marker = markRange(layers.patch, 0); const patch = new Patch({status: 'modified', hunks: [], marker}); - assert.deepEqual(patch.getFirstChangeRange(), [[0, 0], [0, 0]]); + assert.deepEqual(patch.getFirstChangeRange().serialize(), [[0, 0], [0, 0]]); }); }); From aa1e8bff933e59aa76e459694e94beb9ec2238fe Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 14:30:45 -0500 Subject: [PATCH 0956/4053] Implement .isEqual() on MultiFilePatch the dumbest possible way --- lib/models/patch/multi-file-patch.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index a44860af83..6d731db651 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -283,4 +283,8 @@ export default class MultiFilePatch { toString() { return this.filePatches.map(fp => fp.toStringIn(this.buffer)).join(''); } + + isEqual(other) { + return this.toString() === other.toString(); + } } From e686a1f2fc87b37e3f8cbe872bf9bfd6e9bd1312 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 14:34:27 -0500 Subject: [PATCH 0957/4053] The method is called .anyPresent() on a MultiFilePatch --- test/models/repository.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 993c5aa02b..90a9e9c58c 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -434,7 +434,7 @@ describe('Repository', function() { await repo.getLoadPromise(); const patch = await repo.getFilePatchForPath('no.txt'); - assert.isFalse(patch.isPresent()); + assert.isFalse(patch.anyPresent()); }); }); From ce8363b1bc5055bc7faa4bc397632a65b924d923 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 14:34:54 -0500 Subject: [PATCH 0958/4053] Reword spec names to reflect MultiFilePatches being returned --- test/models/repository.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 90a9e9c58c..d6cd353dcb 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -398,7 +398,7 @@ describe('Repository', function() { }); describe('getFilePatchForPath', function() { - it('returns cached FilePatch objects if they exist', async function() { + it('returns cached MultiFilePatch objects if they exist', async function() { const workingDirPath = await cloneRepository('multiple-commits'); const repo = new Repository(workingDirPath); await repo.getLoadPromise(); @@ -413,7 +413,7 @@ describe('Repository', function() { assert.equal(await repo.getFilePatchForPath('file.txt', {staged: true}), stagedFilePatch); }); - it('returns new FilePatch object after repository refresh', async function() { + it('returns new MultiFilePatch object after repository refresh', async function() { const workingDirPath = await cloneRepository('three-files'); const repo = new Repository(workingDirPath); await repo.getLoadPromise(); @@ -428,7 +428,7 @@ describe('Repository', function() { assert.isTrue((await repo.getFilePatchForPath('a.txt')).isEqual(filePatchA)); }); - it('returns a nullFilePatch for unknown paths', async function() { + it('returns an empty MultiFilePatch for unknown paths', async function() { const workingDirPath = await cloneRepository('multiple-commits'); const repo = new Repository(workingDirPath); await repo.getLoadPromise(); From d12e3fdf71e466e6c5639d104c518ea818c9f026 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 9 Nov 2018 11:37:49 -0800 Subject: [PATCH 0959/4053] fix buildFilePatch null patch test --- test/models/patch/builder.test.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index c4e2385e72..ae11008366 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -3,10 +3,12 @@ import {assertInPatch, assertInFilePatch} from '../../helpers'; describe('buildFilePatch', function() { it('returns a null patch for an empty diff list', function() { - const p = buildFilePatch([]); - assert.isFalse(p.getOldFile().isPresent()); - assert.isFalse(p.getNewFile().isPresent()); - assert.isFalse(p.getPatch().isPresent()); + const multiFilePatch = buildFilePatch([]); + const [filePatch] = multiFilePatch.getFilePatches(); + + assert.isFalse(filePatch.getOldFile().isPresent()); + assert.isFalse(filePatch.getNewFile().isPresent()); + assert.isFalse(filePatch.getPatch().isPresent()); }); describe('with a single diff', function() { From 47aeebee685574cf93b506ce2d396a522d659b3a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 14:59:00 -0500 Subject: [PATCH 0960/4053] watchWorkspaceItem tests :white_check_mark: + :100: --- lib/watch-workspace-item.js | 97 ++---------------------------- test/watch-workspace-item.test.js | 98 ++++++++++++------------------- 2 files changed, 41 insertions(+), 154 deletions(-) diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js index dc2fcd1077..fafe8028a8 100644 --- a/lib/watch-workspace-item.js +++ b/lib/watch-workspace-item.js @@ -2,85 +2,6 @@ import {CompositeDisposable} from 'atom'; import URIPattern from './atom/uri-pattern'; -class ItemWatcher { - constructor(workspace, pattern, component, stateKey) { - this.workspace = workspace; - this.pattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); - this.component = component; - this.stateKey = stateKey; - - this.itemCount = this.getItemCount(); - this.subs = new CompositeDisposable(); - } - - setInitialState() { - if (!this.component.state) { - this.component.state = {}; - } - this.component.state[this.stateKey] = this.itemCount > 0; - return this; - } - - subscribeToWorkspace() { - this.subs.dispose(); - this.subs = new CompositeDisposable( - this.workspace.onDidAddPaneItem(this.itemAdded), - this.workspace.onDidDestroyPaneItem(this.itemDestroyed), - ); - return this; - } - - setPattern(pattern) { - const wasTrue = this.itemCount > 0; - - this.pattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); - - // Update the item count to match the new pattern - this.itemCount = this.getItemCount(); - - // Update the component's state if it's changed as a result - if (wasTrue && this.itemCount <= 0) { - return new Promise(resolve => this.component.setState({[this.stateKey]: false}, resolve)); - } else if (!wasTrue && this.itemCount > 0) { - return new Promise(resolve => this.component.setState({[this.stateKey]: true}, resolve)); - } else { - return Promise.resolve(); - } - } - - itemMatches = item => item && item.getURI && this.pattern.matches(item.getURI()).ok() - - getItemCount() { - return this.workspace.getPaneItems().filter(this.itemMatches).length; - } - - itemAdded = ({item}) => { - const hadOpen = this.itemCount > 0; - if (this.itemMatches(item)) { - this.itemCount++; - - if (this.itemCount > 0 && !hadOpen) { - this.component.setState({[this.stateKey]: true}); - } - } - } - - itemDestroyed = ({item}) => { - const hadOpen = this.itemCount > 0; - if (this.itemMatches(item)) { - this.itemCount--; - - if (this.itemCount <= 0 && hadOpen) { - this.component.setState({[this.stateKey]: false}); - } - } - } - - dispose() { - this.subs.dispose(); - } -} - class ActiveItemWatcher { constructor(workspace, pattern, component, stateKey, opts) { this.workspace = workspace; @@ -145,18 +66,8 @@ class ActiveItemWatcher { } } -export function watchWorkspaceItem(workspace, pattern, component, stateKey, options = {}) { - if (options.active) { - // I implemented this as a separate class because the logic differs enough - // and I suspect we can replace `ItemWatcher` with this. I don't see a clear use case for the `ItemWatcher` class - return new ActiveItemWatcher(workspace, pattern, component, stateKey, options) - .setInitialState() - .subscribeToWorkspace(); - } else { - // TODO: would we ever actually use this? If not, clean it up, along with tests - return new ItemWatcher(workspace, pattern, component, stateKey, options) - .setInitialState() - .subscribeToWorkspace(); - } - +export function watchWorkspaceItem(workspace, pattern, component, stateKey) { + return new ActiveItemWatcher(workspace, pattern, component, stateKey) + .setInitialState() + .subscribeToWorkspace(); } diff --git a/test/watch-workspace-item.test.js b/test/watch-workspace-item.test.js index 672ae15dee..f233f0ca40 100644 --- a/test/watch-workspace-item.test.js +++ b/test/watch-workspace-item.test.js @@ -17,6 +17,13 @@ describe('watchWorkspaceItem', function() { if (uri.startsWith('atom-github://')) { return { getURI() { return uri; }, + + getElement() { + if (!this.element) { + this.element = document.createElement('div'); + } + return this.element; + }, }; } else { return undefined; @@ -44,23 +51,31 @@ describe('watchWorkspaceItem', function() { assert.isFalse(component.state.someKey); }); - it('is true when the pane is already open', async function() { + it('is false when the pane is open but not active', async function() { await workspace.open('atom-github://item/one'); await workspace.open('atom-github://item/two'); sub = watchWorkspaceItem(workspace, 'atom-github://item/one', component, 'theKey'); + assert.isFalse(component.state.theKey); + }); + + it('is true when the pane is already open and active', async function() { + await workspace.open('atom-github://item/two'); + await workspace.open('atom-github://item/one'); + sub = watchWorkspaceItem(workspace, 'atom-github://item/one', component, 'theKey'); assert.isTrue(component.state.theKey); }); - it('is true when multiple panes matching the URI pattern are open', async function() { - await workspace.open('atom-github://item/one'); - await workspace.open('atom-github://item/two'); - await workspace.open('atom-github://nonmatch'); + it('is true when the pane is open and active in any pane', async function() { + await workspace.open('atom-github://some-item', {location: 'right'}); + await workspace.open('atom-github://nonmatching'); - sub = watchWorkspaceItem(workspace, 'atom-github://item/{pattern}', component, 'theKey'); + assert.strictEqual(workspace.getRightDock().getActivePaneItem().getURI(), 'atom-github://some-item'); + assert.strictEqual(workspace.getActivePaneItem().getURI(), 'atom-github://nonmatching'); - assert.isTrue(component.state.theKey); + sub = watchWorkspaceItem(workspace, 'atom-github://some-item', component, 'someKey', {active: true}); + assert.isTrue(component.state.someKey); }); it('accepts a preconstructed URIPattern', async function() { @@ -70,49 +85,11 @@ describe('watchWorkspaceItem', function() { sub = watchWorkspaceItem(workspace, u, component, 'theKey'); assert.isTrue(component.state.theKey); }); - - describe('{active: true}', function() { - it('is false when the pane is not open', async function() { - await workspace.open('atom-github://nonmatching'); - - sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey', {active: true}); - assert.isFalse(component.state.someKey); - }); - - it('is false when the pane is open, but not active', async function() { - // TODO: fix this test suite so that 'atom-github://item' works - await workspace.open('atom-github://item'); - await workspace.open('atom-github://nonmatching'); - - sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey', {active: true}); - assert.isFalse(component.state.someKey); - }); - - it('is true when the pane is open and active in the workspace', async function() { - await workspace.open('atom-github://nonmatching'); - await workspace.open('atom-github://item'); - - sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'someKey', {active: true}); - assert.isTrue(component.state.someKey); - }); - - it('is true when the pane is open and active in any pane', async function() { - await workspace.open('atom-github://some-item', {location: 'right'}); - await workspace.open('atom-github://nonmatching'); - - assert.strictEqual(workspace.getRightDock().getActivePaneItem().getURI(), 'atom-github://some-item'); - assert.strictEqual(workspace.getActivePaneItem().getURI(), 'atom-github://nonmatching'); - - sub = watchWorkspaceItem(workspace, 'atom-github://some-item', component, 'someKey', {active: true}); - assert.isTrue(component.state.someKey); - }); - }); }); describe('workspace events', function() { it('becomes true when the pane is opened', async function() { sub = watchWorkspaceItem(workspace, 'atom-github://item/{pattern}', component, 'theKey'); - assert.isFalse(component.state.theKey); await workspace.open('atom-github://item/match'); @@ -123,24 +100,19 @@ describe('watchWorkspaceItem', function() { it('remains true if another matching pane is opened', async function() { await workspace.open('atom-github://item/match0'); sub = watchWorkspaceItem(workspace, 'atom-github://item/{pattern}', component, 'theKey'); - assert.isTrue(component.state.theKey); await workspace.open('atom-github://item/match1'); - assert.isFalse(component.setState.called); }); - it('remains true if a matching pane is closed but another remains open', async function() { + it('becomes false if a nonmatching pane is opened', async function() { await workspace.open('atom-github://item/match0'); - await workspace.open('atom-github://item/match1'); - sub = watchWorkspaceItem(workspace, 'atom-github://item/{pattern}', component, 'theKey'); assert.isTrue(component.state.theKey); - assert.isTrue(workspace.hide('atom-github://item/match1')); - - assert.isFalse(component.setState.called); + await workspace.open('atom-github://other-item/match1'); + assert.isTrue(component.setState.calledWith({theKey: false})); }); it('becomes false if the last matching pane is closed', async function() { @@ -151,18 +123,14 @@ describe('watchWorkspaceItem', function() { assert.isTrue(component.state.theKey); assert.isTrue(workspace.hide('atom-github://item/match1')); - assert.isTrue(workspace.hide('atom-github://item/match0')); + assert.isFalse(component.setState.called); + assert.isTrue(workspace.hide('atom-github://item/match0')); assert.isTrue(component.setState.calledWith({theKey: false})); }); - - describe('{active: true}', function() { - // - }); }); it('stops updating when disposed', async function() { - // TODO: fix this test suite so that 'atom-github://item' works sub = watchWorkspaceItem(workspace, 'atom-github://item', component, 'theKey'); assert.isFalse(component.state.theKey); @@ -198,8 +166,16 @@ describe('watchWorkspaceItem', function() { assert.isTrue(component.setState.calledWith({theKey: true})); }); - describe('{active: true}', function() { - // + it('accepts a preconstructed URIPattern', async function() { + sub = watchWorkspaceItem(workspace, 'atom-github://item0/{pattern}', component, 'theKey'); + assert.isFalse(component.state.theKey); + + await workspace.open('atom-github://item1/match'); + assert.isFalse(component.setState.called); + + await sub.setPattern(new URIPattern('atom-github://item1/{pattern}')); + assert.isFalse(component.state.theKey); + assert.isTrue(component.setState.calledWith({theKey: true})); }); }); }); From a7a345b29db5e6c42eaff9134519c9fc21b2b736 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 11:48:25 -0800 Subject: [PATCH 0961/4053] Use `anyPresent` instead of `isPresent` --- test/models/repository.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/repository.test.js b/test/models/repository.test.js index d6cd353dcb..f784d3da28 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -110,7 +110,7 @@ describe('Repository', function() { assert.strictEqual(await repository.getHeadDescription(), '(no repository)'); assert.strictEqual(await repository.getOperationStates(), nullOperationStates); assert.strictEqual(await repository.getCommitMessage(), ''); - assert.isFalse((await repository.getFilePatchForPath('anything.txt')).isPresent()); + assert.isFalse((await repository.getFilePatchForPath('anything.txt')).anyPresent()); }); it('returns a rejecting promise', async function() { From 0c8f000eec6cf330765f54b15d0ee917e075f996 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 11:48:48 -0800 Subject: [PATCH 0962/4053] Check status on individual filePatch instead of MFP --- test/models/repository.test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/models/repository.test.js b/test/models/repository.test.js index f784d3da28..c713ad845b 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -338,7 +338,9 @@ describe('Repository', function() { await repo.stageFileSymlinkChange(deletedSymlinkAddedFilePath); assert.isNull(await indexModeAndOid(deletedSymlinkAddedFilePath)); const unstagedFilePatch = await repo.getFilePatchForPath(deletedSymlinkAddedFilePath, {staged: false}); - assert.equal(unstagedFilePatch.getStatus(), 'added'); + assert.lengthOf(unstagedFilePatch.getFilePatches(), 1); + const [uFilePatch] = unstagedFilePatch.getFilePatches(); + assert.equal(uFilePatch.getStatus(), 'added'); assert.equal(unstagedFilePatch.toString(), dedent` diff --git a/symlink.txt b/symlink.txt new file mode 100644 @@ -357,7 +359,9 @@ describe('Repository', function() { await repo.stageFileSymlinkChange(deletedFileAddedSymlinkPath); assert.isNull(await indexModeAndOid(deletedFileAddedSymlinkPath)); const stagedFilePatch = await repo.getFilePatchForPath(deletedFileAddedSymlinkPath, {staged: true}); - assert.equal(stagedFilePatch.getStatus(), 'deleted'); + assert.lengthOf(stagedFilePatch.getFilePatches(), 1); + const [sFilePatch] = stagedFilePatch.getFilePatches(); + assert.equal(sFilePatch.getStatus(), 'deleted'); assert.equal(stagedFilePatch.toString(), dedent` diff --git a/a.txt b/a.txt deleted file mode 100644 From 7cd5cfd3cb49cd0f8c974edc7707ad678b69aa27 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 12:10:40 -0800 Subject: [PATCH 0963/4053] Set pull.rebase config to false in `setUpLocalAndRemoteRepositories` This was causing the `only performs a fast-forward merge with ffOnly` test to fail if the system's global config had this set to `true` --- test/helpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/helpers.js b/test/helpers.js index 274f85c013..4733eb2476 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -99,6 +99,7 @@ export async function setUpLocalAndRemoteRepositories(repoName = 'multiple-commi await localGit.exec(['config', '--local', 'commit.gpgsign', 'false']); await localGit.exec(['config', '--local', 'user.email', FAKE_USER.email]); await localGit.exec(['config', '--local', 'user.name', FAKE_USER.name]); + await localGit.exec(['config', '--local', 'pull.rebase', false]); return {baseRepoPath, remoteRepoPath, localRepoPath}; } From 488612948122becd3b615d318610f58d04ddf901 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 15:38:02 -0500 Subject: [PATCH 0964/4053] Move coveralls report to after_script --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 821983cbf5..d5718814fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,5 +61,5 @@ before_script: script: - ./script/cibuild -after_success: +after_script: - npm run coveralls From 7b4078f3e6f1b8f0cf2e6e67757ddd00239b4d4a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 12:44:32 -0800 Subject: [PATCH 0965/4053] Only call `discardLines` if MFP has a single file patch --- lib/controllers/multi-file-patch-controller.js | 8 ++++++++ lib/controllers/root-controller.js | 16 ++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 7ffeb2b38f..6b7a19c6e0 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -183,6 +183,14 @@ export default class MultiFilePatchController extends React.Component { } async discardRows(rowSet, nextSelectionMode, {eventSource} = {}) { + // (kuychaco) For now we only support discarding rows for MultiFilePatches that contain a single file patch + // The only way to access this method from the UI is to be in a ChangedFileItem, which only has a single file patch + // This check is duplicated in RootController#discardLines. We also want it here to prevent us from sending metrics + // unnecessarily + if (this.props.multiFilePatch.getFilePatches().length !== 1) { + return Promise.resolve(null); + } + let chosenRows = rowSet; if (chosenRows) { await this.selectedRowsChanged(chosenRows, nextSelectionMode); diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index db41139e7b..4ade80eaf6 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -678,18 +678,22 @@ export default class RootController extends React.Component { } async discardLines(multiFilePatch, lines, repository = this.props.repository) { - const filePaths = multiFilePatch.getFilePatches().map(fp => fp.getPath()); + // (kuychaco) For now we only support discarding rows for MultiFilePatches that contain a single file patch + // The only way to access this method from the UI is to be in a ChangedFileItem, which only has a single file patch + if (multiFilePatch.getFilePatches().length !== 1) { + return Promise.resolve(null); + } + + const filePath = multiFilePatch.getFilePatches()[0].getPath(); const destructiveAction = async () => { const discardFilePatch = multiFilePatch.getUnstagePatchForLines(lines); await repository.applyPatchToWorkdir(discardFilePatch); }; return await repository.storeBeforeAndAfterBlobs( - [filePaths], - () => this.ensureNoUnsavedFiles(filePaths, 'Cannot discard lines.', repository.getWorkingDirectoryPath()), + [filePath], + () => this.ensureNoUnsavedFiles([filePath], 'Cannot discard lines.', repository.getWorkingDirectoryPath()), destructiveAction, - // FIXME: Present::storeBeforeAndAfterBlobs() and DiscardHistory::storeBeforeAndAfterBlobs() need a way to store - // multiple partial paths - filePaths[0], + filePath, ); } From abcb4824e19c95d93a8705ae781d40105bdf3a40 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 16:03:24 -0500 Subject: [PATCH 0966/4053] Job parameters do not work that way --- script/azure-pipelines/linux-install.yml | 8 ++++++-- script/azure-pipelines/macos-install.yml | 8 ++++++-- script/azure-pipelines/windows-install.yml | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/script/azure-pipelines/linux-install.yml b/script/azure-pipelines/linux-install.yml index 5dd0abe7a8..cb3a93d7d0 100644 --- a/script/azure-pipelines/linux-install.yml +++ b/script/azure-pipelines/linux-install.yml @@ -4,13 +4,17 @@ parameters: steps: - bash: | - curl -s -L "https://atom.io/download/deb?channel=${{ parameters.atom_channel }}" \ + curl -s -L "https://atom.io/download/deb?channel=${ATOM_CHANNEL}" \ -H 'Accept: application/octet-stream' \ -o 'atom-amd64.deb' /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 sudo apt-get update && sudo apt-get install -yyq libgconf-2-4 build-essential git libsecret-1-dev dpkg-deb -x atom-amd64.deb /tmp/atom displayName: install Atom + env: + ATOM_CHANNEL: ${{ parameters.atom_channel }} - bash: | - "/tmp/atom/usr/share/${{ parameters.atom_name }}/resources/app/apm/bin/apm" ci + "/tmp/atom/usr/share/${ATOM_NAME}/resources/app/apm/bin/apm" ci displayName: install dependencies + env: + ATOM_NAME: ${{ parameters.atom_name }} diff --git a/script/azure-pipelines/macos-install.yml b/script/azure-pipelines/macos-install.yml index 2f2f3e16e1..31e181cfc8 100644 --- a/script/azure-pipelines/macos-install.yml +++ b/script/azure-pipelines/macos-install.yml @@ -5,12 +5,16 @@ parameters: steps: - bash: | set -x - curl -s -L "https://atom.io/download/mac?channel=${{ parameters.atom_channel }}" \ + curl -s -L "https://atom.io/download/mac?channel=${ATOM_CHANNEL}" \ -H 'Accept: application/octet-stream' \ -o "atom.zip" mkdir -p /tmp/atom unzip -q atom.zip -d /tmp/atom displayName: install Atom + env: + ATOM_CHANNEL: ${{ parameters.atom_channel }} - bash: | - "/tmp/atom/${{ parameters.atom_app }}/Contents/Resources/app/apm/bin/apm" ci + "/tmp/atom/${ATOM_APP}/Contents/Resources/app/apm/bin/apm" ci displayName: install dependencies + env: + ATOM_APP: ${{ parameters.atom_app }} diff --git a/script/azure-pipelines/windows-install.yml b/script/azure-pipelines/windows-install.yml index 16b7dd72b1..bf8a68392a 100644 --- a/script/azure-pipelines/windows-install.yml +++ b/script/azure-pipelines/windows-install.yml @@ -8,16 +8,18 @@ steps: $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" Write-Host "Downloading latest Atom release" - $source = "https://atom.io/download/windows_zip?channel=${{ parameters.atom_channel }}" + $source = "https://atom.io/download/windows_zip?channel=$env:ATOM_CHANNEL" $destination = "atom.zip" (New-Object System.Net.WebClient).DownloadFile($source, $destination) Expand-Archive -Path "atom.zip" -DestinationPath $script:ATOMROOT displayName: install Atom + env: + ATOM_CHANNEL: ${{ parameters.atom_channel }} - powershell: | Set-StrictMode -Version Latest $script:ATOMROOT = "$env:AGENT_HOMEDIRECTORY/atom" - $script:APM_SCRIPT_PATH = "$script:ATOMROOT\${{ parameters.atom_directory }}\resources\app\apm\bin\apm.cmd" + $script:APM_SCRIPT_PATH = "$script:ATOMROOT\${env:ATOM_DIRECTORY}\resources\app\apm\bin\apm.cmd" & "$script:APM_SCRIPT_PATH" ci if ($LASTEXITCODE -ne 0) { @@ -26,3 +28,5 @@ steps: exit } displayName: install dependencies + env: + ATOM_DIRECTORY: ${{ parameters.atom_directory }} From 3908db1d09baabe0eac8b2df043df1771abc3209 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 14:19:10 -0800 Subject: [PATCH 0967/4053] Fix `buildFilePatch` --- test/models/patch/builder.test.js | 87 +++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index ae11008366..2933f218be 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -13,7 +13,7 @@ describe('buildFilePatch', function() { describe('with a single diff', function() { it('assembles a patch from non-symlink sides', function() { - const p = buildFilePatch([{ + const multiFilePatch = buildFilePatch([{ oldPath: 'old/path', oldMode: '100644', newPath: 'new/path', @@ -66,18 +66,22 @@ describe('buildFilePatch', function() { ], }]); + assert.lengthOf(multiFilePatch.getFilePatches(), 1); + const [p] = multiFilePatch.getFilePatches(); + const buffer = multiFilePatch.getBuffer(); + assert.strictEqual(p.getOldPath(), 'old/path'); assert.strictEqual(p.getOldMode(), '100644'); assert.strictEqual(p.getNewPath(), 'new/path'); assert.strictEqual(p.getNewMode(), '100755'); assert.strictEqual(p.getPatch().getStatus(), 'modified'); - const buffer = + const bufferText = 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\nline-6\nline-7\nline-8\nline-9\nline-10\n' + 'line-11\nline-12\nline-13\nline-14\nline-15\nline-16\nline-17\nline-18\n'; - assert.strictEqual(p.getBuffer().getText(), buffer); + assert.strictEqual(buffer.getText(), bufferText); - assertInPatch(p).hunks( + assertInPatch(p, buffer).hunks( { startRow: 0, endRow: 8, @@ -115,7 +119,7 @@ describe('buildFilePatch', function() { }); it("sets the old file's symlink destination", function() { - const p = buildFilePatch([{ + const multiFilePatch = buildFilePatch([{ oldPath: 'old/path', oldMode: '120000', newPath: 'new/path', @@ -132,12 +136,14 @@ describe('buildFilePatch', function() { ], }]); + assert.lengthOf(multiFilePatch.getFilePatches(), 1); + const [p] = multiFilePatch.getFilePatches(); assert.strictEqual(p.getOldSymlink(), 'old/destination'); assert.isNull(p.getNewSymlink()); }); it("sets the new file's symlink destination", function() { - const p = buildFilePatch([{ + const multiFilePatch = buildFilePatch([{ oldPath: 'old/path', oldMode: '100644', newPath: 'new/path', @@ -154,12 +160,14 @@ describe('buildFilePatch', function() { ], }]); + assert.lengthOf(multiFilePatch.getFilePatches(), 1); + const [p] = multiFilePatch.getFilePatches(); assert.isNull(p.getOldSymlink()); assert.strictEqual(p.getNewSymlink(), 'new/destination'); }); it("sets both files' symlink destinations", function() { - const p = buildFilePatch([{ + const multiFilePatch = buildFilePatch([{ oldPath: 'old/path', oldMode: '120000', newPath: 'new/path', @@ -180,12 +188,14 @@ describe('buildFilePatch', function() { ], }]); + assert.lengthOf(multiFilePatch.getFilePatches(), 1); + const [p] = multiFilePatch.getFilePatches(); assert.strictEqual(p.getOldSymlink(), 'old/destination'); assert.strictEqual(p.getNewSymlink(), 'new/destination'); }); it('assembles a patch from a file deletion', function() { - const p = buildFilePatch([{ + const multiFilePatch = buildFilePatch([{ oldPath: 'old/path', oldMode: '100644', newPath: null, @@ -208,16 +218,20 @@ describe('buildFilePatch', function() { ], }]); + assert.lengthOf(multiFilePatch.getFilePatches(), 1); + const [p] = multiFilePatch.getFilePatches(); + const buffer = multiFilePatch.getBuffer(); + assert.isTrue(p.getOldFile().isPresent()); assert.strictEqual(p.getOldPath(), 'old/path'); assert.strictEqual(p.getOldMode(), '100644'); assert.isFalse(p.getNewFile().isPresent()); assert.strictEqual(p.getPatch().getStatus(), 'deleted'); - const buffer = 'line-0\nline-1\nline-2\nline-3\n\n'; - assert.strictEqual(p.getBuffer().getText(), buffer); + const bufferText = 'line-0\nline-1\nline-2\nline-3\n\n'; + assert.strictEqual(buffer.getText(), bufferText); - assertInPatch(p).hunks( + assertInPatch(p, buffer).hunks( { startRow: 0, endRow: 4, @@ -230,7 +244,7 @@ describe('buildFilePatch', function() { }); it('assembles a patch from a file addition', function() { - const p = buildFilePatch([{ + const multiFilePatch = buildFilePatch([{ oldPath: null, oldMode: null, newPath: 'new/path', @@ -251,16 +265,20 @@ describe('buildFilePatch', function() { ], }]); + assert.lengthOf(multiFilePatch.getFilePatches(), 1); + const [p] = multiFilePatch.getFilePatches(); + const buffer = multiFilePatch.getBuffer(); + assert.isFalse(p.getOldFile().isPresent()); assert.isTrue(p.getNewFile().isPresent()); assert.strictEqual(p.getNewPath(), 'new/path'); assert.strictEqual(p.getNewMode(), '100755'); assert.strictEqual(p.getPatch().getStatus(), 'added'); - const buffer = 'line-0\nline-1\nline-2\n'; - assert.strictEqual(p.getBuffer().getText(), buffer); + const bufferText = 'line-0\nline-1\nline-2\n'; + assert.strictEqual(buffer.getText(), bufferText); - assertInPatch(p).hunks( + assertInPatch(p, buffer).hunks( { startRow: 0, endRow: 2, @@ -286,7 +304,7 @@ describe('buildFilePatch', function() { }); it('parses a no-newline marker', function() { - const p = buildFilePatch([{ + const multiFilePatch = buildFilePatch([{ oldPath: 'old/path', oldMode: '100644', newPath: 'new/path', @@ -297,9 +315,12 @@ describe('buildFilePatch', function() { ]}], }]); - assert.strictEqual(p.getBuffer().getText(), 'line-0\nline-1\n No newline at end of file\n'); + assert.lengthOf(multiFilePatch.getFilePatches(), 1); + const [p] = multiFilePatch.getFilePatches(); + const buffer = multiFilePatch.getBuffer(); + assert.strictEqual(buffer.getText(), 'line-0\nline-1\n No newline at end of file\n'); - assertInPatch(p).hunks({ + assertInPatch(p, buffer).hunks({ startRow: 0, endRow: 2, header: '@@ -0,1 +0,1 @@', @@ -314,7 +335,7 @@ describe('buildFilePatch', function() { describe('with a mode change and a content diff', function() { it('identifies a file that was deleted and replaced by a symlink', function() { - const p = buildFilePatch([ + const multiFilePatch = buildFilePatch([ { oldPath: 'the-path', oldMode: '000000', @@ -349,6 +370,10 @@ describe('buildFilePatch', function() { }, ]); + assert.lengthOf(multiFilePatch.getFilePatches(), 1); + const [p] = multiFilePatch.getFilePatches(); + const buffer = multiFilePatch.getBuffer(); + assert.strictEqual(p.getOldPath(), 'the-path'); assert.strictEqual(p.getOldMode(), '100644'); assert.isNull(p.getOldSymlink()); @@ -357,8 +382,8 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getNewSymlink(), 'the-destination'); assert.strictEqual(p.getStatus(), 'deleted'); - assert.strictEqual(p.getBuffer().getText(), 'line-0\nline-1\n'); - assertInPatch(p).hunks({ + assert.strictEqual(buffer.getText(), 'line-0\nline-1\n'); + assertInPatch(p, buffer).hunks({ startRow: 0, endRow: 1, header: '@@ -0,0 +0,2 @@', @@ -369,7 +394,7 @@ describe('buildFilePatch', function() { }); it('identifies a symlink that was deleted and replaced by a file', function() { - const p = buildFilePatch([ + const multiFilePatch = buildFilePatch([ { oldPath: 'the-path', oldMode: '120000', @@ -404,6 +429,10 @@ describe('buildFilePatch', function() { }, ]); + assert.lengthOf(multiFilePatch.getFilePatches(), 1); + const [p] = multiFilePatch.getFilePatches(); + const buffer = multiFilePatch.getBuffer(); + assert.strictEqual(p.getOldPath(), 'the-path'); assert.strictEqual(p.getOldMode(), '120000'); assert.strictEqual(p.getOldSymlink(), 'the-destination'); @@ -412,8 +441,8 @@ describe('buildFilePatch', function() { assert.isNull(p.getNewSymlink()); assert.strictEqual(p.getStatus(), 'added'); - assert.strictEqual(p.getBuffer().getText(), 'line-0\nline-1\n'); - assertInPatch(p).hunks({ + assert.strictEqual(buffer.getText(), 'line-0\nline-1\n'); + assertInPatch(p, buffer).hunks({ startRow: 0, endRow: 1, header: '@@ -0,2 +0,0 @@', @@ -424,7 +453,7 @@ describe('buildFilePatch', function() { }); it('is indifferent to the order of the diffs', function() { - const p = buildFilePatch([ + const multiFilePatch = buildFilePatch([ { oldMode: '100644', newPath: 'the-path', @@ -458,6 +487,10 @@ describe('buildFilePatch', function() { }, ]); + assert.lengthOf(multiFilePatch.getFilePatches(), 1); + const [p] = multiFilePatch.getFilePatches(); + const buffer = multiFilePatch.getBuffer(); + assert.strictEqual(p.getOldPath(), 'the-path'); assert.strictEqual(p.getOldMode(), '100644'); assert.isNull(p.getOldSymlink()); @@ -466,8 +499,8 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getNewSymlink(), 'the-destination'); assert.strictEqual(p.getStatus(), 'deleted'); - assert.strictEqual(p.getBuffer().getText(), 'line-0\nline-1\n'); - assertInPatch(p).hunks({ + assert.strictEqual(buffer.getText(), 'line-0\nline-1\n'); + assertInPatch(p, buffer).hunks({ startRow: 0, endRow: 1, header: '@@ -0,0 +0,2 @@', From 37967eefe3b75b1b4b5bc48b31fcf3958ae0d7ab Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 14:28:12 -0800 Subject: [PATCH 0968/4053] Fix `buildMultiFilePatch` tests --- test/models/patch/builder.test.js | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 2933f218be..d1bc6e1161 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -590,6 +590,8 @@ describe('buildFilePatch', function() { }, ]); + const buffer = mp.getBuffer(); + assert.lengthOf(mp.getFilePatches(), 3); assert.strictEqual( @@ -599,20 +601,9 @@ describe('buildFilePatch', function() { 'line-0\nline-1\nline-2\n', ); - const assertAllSame = getter => { - assert.lengthOf( - Array.from(new Set(mp.getFilePatches().map(p => p[getter]()))), - 1, - `FilePatches have different results from ${getter}`, - ); - }; - for (const getter of ['getUnchangedLayer', 'getAdditionLayer', 'getDeletionLayer', 'getNoNewlineLayer']) { - assertAllSame(getter); - } - assert.strictEqual(mp.getFilePatches()[0].getOldPath(), 'first'); assert.deepEqual(mp.getFilePatches()[0].getMarker().getRange().serialize(), [[0, 0], [6, 6]]); - assertInFilePatch(mp.getFilePatches()[0]).hunks( + assertInFilePatch(mp.getFilePatches()[0], buffer).hunks( { startRow: 0, endRow: 3, header: '@@ -1,2 +1,4 @@', regions: [ {kind: 'unchanged', string: ' line-0\n', range: [[0, 0], [0, 6]]}, @@ -630,7 +621,7 @@ describe('buildFilePatch', function() { ); assert.strictEqual(mp.getFilePatches()[1].getOldPath(), 'second'); assert.deepEqual(mp.getFilePatches()[1].getMarker().getRange().serialize(), [[7, 0], [10, 6]]); - assertInFilePatch(mp.getFilePatches()[1]).hunks( + assertInFilePatch(mp.getFilePatches()[1], buffer).hunks( { startRow: 7, endRow: 10, header: '@@ -5,3 +5,3 @@', regions: [ {kind: 'unchanged', string: ' line-5\n', range: [[7, 0], [7, 6]]}, @@ -642,7 +633,7 @@ describe('buildFilePatch', function() { ); assert.strictEqual(mp.getFilePatches()[2].getOldPath(), 'third'); assert.deepEqual(mp.getFilePatches()[2].getMarker().getRange().serialize(), [[11, 0], [13, 6]]); - assertInFilePatch(mp.getFilePatches()[2]).hunks( + assertInFilePatch(mp.getFilePatches()[2], buffer).hunks( { startRow: 11, endRow: 13, header: '@@ -1,0 +1,3 @@', regions: [ {kind: 'addition', string: '+line-0\n+line-1\n+line-2\n', range: [[11, 0], [13, 6]]}, @@ -713,11 +704,13 @@ describe('buildFilePatch', function() { }, ]); + const buffer = mp.getBuffer(); + assert.lengthOf(mp.getFilePatches(), 4); const [fp0, fp1, fp2, fp3] = mp.getFilePatches(); assert.strictEqual(fp0.getOldPath(), 'first'); - assertInFilePatch(fp0).hunks({ + assertInFilePatch(fp0, buffer).hunks({ startRow: 0, endRow: 2, header: '@@ -1,2 +1,3 @@', regions: [ {kind: 'unchanged', string: ' line-0\n', range: [[0, 0], [0, 6]]}, {kind: 'addition', string: '+line-1\n', range: [[1, 0], [1, 6]]}, @@ -728,7 +721,7 @@ describe('buildFilePatch', function() { assert.strictEqual(fp1.getOldPath(), 'was-non-symlink'); assert.isTrue(fp1.hasTypechange()); assert.strictEqual(fp1.getNewSymlink(), 'was-non-symlink-destination'); - assertInFilePatch(fp1).hunks({ + assertInFilePatch(fp1, buffer).hunks({ startRow: 3, endRow: 4, header: '@@ -1,2 +1,0 @@', regions: [ {kind: 'deletion', string: '-line-0\n-line-1\n', range: [[3, 0], [4, 6]]}, ], @@ -737,14 +730,14 @@ describe('buildFilePatch', function() { assert.strictEqual(fp2.getOldPath(), 'was-symlink'); assert.isTrue(fp2.hasTypechange()); assert.strictEqual(fp2.getOldSymlink(), 'was-symlink-destination'); - assertInFilePatch(fp2).hunks({ + assertInFilePatch(fp2, buffer).hunks({ startRow: 5, endRow: 6, header: '@@ -1,0 +1,2 @@', regions: [ {kind: 'addition', string: '+line-0\n+line-1\n', range: [[5, 0], [6, 6]]}, ], }); assert.strictEqual(fp3.getNewPath(), 'third'); - assertInFilePatch(fp3).hunks({ + assertInFilePatch(fp3, buffer).hunks({ startRow: 7, endRow: 9, header: '@@ -1,3 +1,0 @@', regions: [ {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n', range: [[7, 0], [9, 6]]}, ], From 3a3c68a08e87255e16e059ab1d56de05ddd41739 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 16:16:41 -0800 Subject: [PATCH 0969/4053] Fix `Open in File` and make it work for multiple files --- lib/views/multi-file-patch-view.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index ec16a26ae0..dbc0f8f440 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -850,7 +850,7 @@ export default class MultiFilePatchView extends React.Component { } didOpenFile() { - const cursors = []; + const cursorsByFilePatch = new Map(); this.refEditor.map(editor => { const placedRows = new Set(); @@ -858,6 +858,7 @@ export default class MultiFilePatchView extends React.Component { for (const cursor of editor.getCursors()) { const cursorRow = cursor.getBufferPosition().row; const hunk = this.props.multiFilePatch.getHunkAt(cursorRow); + const filePatch = this.props.multiFilePatch.getFilePatchAt(cursorRow); /* istanbul ignore next */ if (!hunk) { continue; @@ -866,7 +867,7 @@ export default class MultiFilePatchView extends React.Component { let newRow = hunk.getNewRowAt(cursorRow); let newColumn = cursor.getBufferPosition().column; if (newRow === null) { - let nearestRow = hunk.getNewStartRow() - 1; + let nearestRow = hunk.getNewStartRow(); for (const region of hunk.getRegions()) { if (!region.includesBufferRow(cursorRow)) { region.when({ @@ -890,14 +891,24 @@ export default class MultiFilePatchView extends React.Component { } if (newRow !== null) { - cursors.push([newRow, newColumn]); + newRow -= 1; // Why is this needed? What's not being + const cursors = cursorsByFilePatch.get(filePatch); + if (!cursors) { + cursorsByFilePatch.set(filePatch, [[newRow, newColumn]]); + } else { + cursors.push([newRow, newColumn]); + } } } return null; }); - this.props.openFile(cursors); + return Promise.all(Array.from(cursorsByFilePatch).map(value => { + const [filePatch, cursors] = value; + return this.props.openFile(filePatch, cursors); + })); + } getSelectedRows() { From 52634b9f1b3e4a73fd771e660046eaf5005af1f6 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 9 Nov 2018 16:23:29 -0800 Subject: [PATCH 0970/4053] workshopping button text --- lib/views/commit-view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index d5aa351483..4aedd51159 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -164,10 +164,10 @@ export default class CommitView extends React.Component {
From 1a42c9abfacd17de1f92fe95b6f88e66412c32eb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 9 Nov 2018 19:41:06 -0500 Subject: [PATCH 0971/4053] Delete zombie FilePatch test --- test/models/file-patch.test.js | 427 --------------------------------- 1 file changed, 427 deletions(-) delete mode 100644 test/models/file-patch.test.js diff --git a/test/models/file-patch.test.js b/test/models/file-patch.test.js deleted file mode 100644 index 6edc3771b0..0000000000 --- a/test/models/file-patch.test.js +++ /dev/null @@ -1,427 +0,0 @@ -import {cloneRepository, buildRepository} from '../helpers'; -import {toGitPathSep} from '../../lib/helpers'; -import path from 'path'; -import fs from 'fs'; -import dedent from 'dedent-js'; - -import FilePatch from '../../lib/models/file-patch'; -import Hunk from '../../lib/models/hunk'; -import HunkLine from '../../lib/models/hunk-line'; - -function createFilePatch(oldFilePath, newFilePath, status, hunks) { - const oldFile = new FilePatch.File({path: oldFilePath}); - const newFile = new FilePatch.File({path: newFilePath}); - const patch = new FilePatch.Patch({status, hunks}); - - return new FilePatch(oldFile, newFile, patch); -} - -describe('FilePatch', function() { - it('detects executable mode changes', function() { - const of0 = new FilePatch.File({path: 'a.txt', mode: '100644'}); - const nf0 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const p0 = new FilePatch.Patch({status: 'modified', hunks: []}); - const fp0 = new FilePatch(of0, nf0, p0); - assert.isTrue(fp0.didChangeExecutableMode()); - - const of1 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const nf1 = new FilePatch.File({path: 'a.txt', mode: '100644'}); - const p1 = new FilePatch.Patch({status: 'modified', hunks: []}); - const fp1 = new FilePatch(of1, nf1, p1); - assert.isTrue(fp1.didChangeExecutableMode()); - - const of2 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const nf2 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const p2 = new FilePatch.Patch({status: 'modified', hunks: []}); - const fp2 = new FilePatch(of2, nf2, p2); - assert.isFalse(fp2.didChangeExecutableMode()); - - const of3 = FilePatch.File.empty(); - const nf3 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const p3 = new FilePatch.Patch({status: 'modified', hunks: []}); - const fp3 = new FilePatch(of3, nf3, p3); - assert.isFalse(fp3.didChangeExecutableMode()); - - const of4 = FilePatch.File.empty(); - const nf4 = new FilePatch.File({path: 'a.txt', mode: '100755'}); - const p4 = new FilePatch.Patch({status: 'modified', hunks: []}); - const fp4 = new FilePatch(of4, nf4, p4); - assert.isFalse(fp4.didChangeExecutableMode()); - }); - - describe('getStagePatchForLines()', function() { - it('returns a new FilePatch that applies only the specified lines', function() { - const filePatch = createFilePatch('a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 8), - new HunkLine('line-8', 'added', -1, 9), - new HunkLine('line-9', 'added', -1, 10), - new HunkLine('line-10', 'deleted', 8, -1), - new HunkLine('line-11', 'deleted', 9, -1), - ]), - new Hunk(20, 19, 2, 2, '', [ - new HunkLine('line-12', 'deleted', 20, -1), - new HunkLine('line-13', 'added', -1, 19), - new HunkLine('line-14', 'unchanged', 21, 20), - new HunkLine('No newline at end of file', 'nonewline', -1, -1), - ]), - ]); - const linesFromHunk2 = filePatch.getHunks()[1].getLines().slice(1, 4); - assert.deepEqual(filePatch.getStagePatchForLines(new Set(linesFromHunk2)), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(5, 5, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 5), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 6), - new HunkLine('line-10', 'unchanged', 8, 7), - new HunkLine('line-11', 'unchanged', 9, 8), - ]), - ], - )); - - // add lines from other hunks - const linesFromHunk1 = filePatch.getHunks()[0].getLines().slice(0, 1); - const linesFromHunk3 = filePatch.getHunks()[2].getLines().slice(1, 2); - const selectedLines = linesFromHunk2.concat(linesFromHunk1, linesFromHunk3); - assert.deepEqual(filePatch.getStagePatchForLines(new Set(selectedLines)), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 1, 2, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-3', 'unchanged', 1, 2), - ]), - new Hunk(5, 6, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 6), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 7), - new HunkLine('line-10', 'unchanged', 8, 8), - new HunkLine('line-11', 'unchanged', 9, 9), - ]), - new Hunk(20, 18, 2, 3, '', [ - new HunkLine('line-12', 'unchanged', 20, 18), - new HunkLine('line-13', 'added', -1, 19), - new HunkLine('line-14', 'unchanged', 21, 20), - new HunkLine('No newline at end of file', 'nonewline', -1, -1), - ]), - ], - )); - }); - - describe('staging lines from deleted files', function() { - it('handles staging part of the file', function() { - const filePatch = createFilePatch('a.txt', null, 'deleted', [ - new Hunk(1, 0, 3, 0, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'deleted', 3, -1), - ]), - ]); - const linesFromHunk = filePatch.getHunks()[0].getLines().slice(0, 2); - assert.deepEqual(filePatch.getStagePatchForLines(new Set(linesFromHunk)), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 3, 1, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'unchanged', 3, 1), - ]), - ], - )); - }); - - it('handles staging all lines, leaving nothing unstaged', function() { - const filePatch = createFilePatch('a.txt', null, 'deleted', [ - new Hunk(1, 0, 3, 0, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'deleted', 3, -1), - ]), - ]); - const linesFromHunk = filePatch.getHunks()[0].getLines(); - assert.deepEqual(filePatch.getStagePatchForLines(new Set(linesFromHunk)), createFilePatch( - 'a.txt', null, 'deleted', [ - new Hunk(1, 0, 3, 0, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'deleted', 3, -1), - ]), - ], - )); - }); - }); - }); - - describe('getUnstagePatchForLines()', function() { - it('returns a new FilePatch that applies only the specified lines', function() { - const filePatch = createFilePatch('a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - new HunkLine('line-7', 'added', -1, 8), - new HunkLine('line-8', 'added', -1, 9), - new HunkLine('line-9', 'added', -1, 10), - new HunkLine('line-10', 'deleted', 8, -1), - new HunkLine('line-11', 'deleted', 9, -1), - ]), - new Hunk(20, 19, 2, 2, '', [ - new HunkLine('line-12', 'deleted', 20, -1), - new HunkLine('line-13', 'added', -1, 19), - new HunkLine('line-14', 'unchanged', 21, 20), - new HunkLine('No newline at end of file', 'nonewline', -1, -1), - ]), - ]); - const lines = new Set(filePatch.getHunks()[1].getLines().slice(1, 5)); - filePatch.getHunks()[2].getLines().forEach(line => lines.add(line)); - assert.deepEqual(filePatch.getUnstagePatchForLines(lines), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(7, 7, 4, 4, '', [ - new HunkLine('line-4', 'unchanged', 7, 7), - new HunkLine('line-7', 'deleted', 8, -1), - new HunkLine('line-8', 'deleted', 9, -1), - new HunkLine('line-5', 'added', -1, 8), - new HunkLine('line-6', 'added', -1, 9), - new HunkLine('line-9', 'unchanged', 10, 10), - ]), - new Hunk(19, 21, 2, 2, '', [ - new HunkLine('line-13', 'deleted', 19, -1), - new HunkLine('line-12', 'added', -1, 21), - new HunkLine('line-14', 'unchanged', 20, 22), - new HunkLine('No newline at end of file', 'nonewline', -1, -1), - ]), - ], - )); - }); - - describe('unstaging lines from an added file', function() { - it('handles unstaging part of the file', function() { - const filePatch = createFilePatch(null, 'a.txt', 'added', [ - new Hunk(0, 1, 0, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - ]), - ]); - const linesFromHunk = filePatch.getHunks()[0].getLines().slice(0, 2); - assert.deepEqual(filePatch.getUnstagePatchForLines(new Set(linesFromHunk)), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 3, 1, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'unchanged', 3, 1), - ]), - ], - )); - }); - - it('handles unstaging all lines, leaving nothign staged', function() { - const filePatch = createFilePatch(null, 'a.txt', 'added', [ - new Hunk(0, 1, 0, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - ]), - ]); - - const linesFromHunk = filePatch.getHunks()[0].getLines(); - assert.deepEqual(filePatch.getUnstagePatchForLines(new Set(linesFromHunk)), createFilePatch( - 'a.txt', null, 'deleted', [ - new Hunk(1, 0, 3, 0, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'deleted', 3, -1), - ]), - ], - )); - }); - }); - }); - - it('handles newly added files', function() { - const filePatch = createFilePatch(null, 'a.txt', 'added', [ - new Hunk(0, 1, 0, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'added', -1, 3), - ]), - ]); - const linesFromHunk = filePatch.getHunks()[0].getLines().slice(0, 2); - assert.deepEqual(filePatch.getUnstagePatchForLines(new Set(linesFromHunk)), createFilePatch( - 'a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 3, 1, '', [ - new HunkLine('line-1', 'deleted', 1, -1), - new HunkLine('line-2', 'deleted', 2, -1), - new HunkLine('line-3', 'unchanged', 3, 1), - ]), - ], - )); - }); - - describe('toString()', function() { - it('converts the patch to the standard textual format', async function() { - const workdirPath = await cloneRepository('multi-line-file'); - const repository = await buildRepository(workdirPath); - - const lines = fs.readFileSync(path.join(workdirPath, 'sample.js'), 'utf8').split('\n'); - lines[0] = 'this is a modified line'; - lines.splice(1, 0, 'this is a new line'); - lines[11] = 'this is a modified line'; - lines.splice(12, 1); - fs.writeFileSync(path.join(workdirPath, 'sample.js'), lines.join('\n')); - - const patch = await repository.getFilePatchForPath('sample.js'); - assert.equal(patch.toString(), dedent` - diff --git a/sample.js b/sample.js - --- a/sample.js - +++ b/sample.js - @@ -1,4 +1,5 @@ - -var quicksort = function () { - +this is a modified line - +this is a new line - var sort = function(items) { - if (items.length <= 1) return items; - var pivot = items.shift(), current, left = [], right = []; - @@ -8,6 +9,5 @@ - } - return sort(left).concat(pivot).concat(sort(right)); - }; - - - - return sort(Array.apply(this, arguments)); - +this is a modified line - }; - - `); - }); - - it('correctly formats new files with no newline at the end', async function() { - const workingDirPath = await cloneRepository('three-files'); - const repo = await buildRepository(workingDirPath); - fs.writeFileSync(path.join(workingDirPath, 'e.txt'), 'qux', 'utf8'); - const patch = await repo.getFilePatchForPath('e.txt'); - - assert.equal(patch.toString(), dedent` - diff --git a/e.txt b/e.txt - new file mode 100644 - --- /dev/null - +++ b/e.txt - @@ -0,0 +1,1 @@ - +qux - \\ No newline at end of file - - `); - }); - - describe('typechange file patches', function() { - it('handles typechange patches for a symlink replaced with a file', async function() { - const workdirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workdirPath); - - await repository.git.exec(['config', 'core.symlinks', 'true']); - - const deletedSymlinkAddedFilePath = 'symlink.txt'; - fs.unlinkSync(path.join(workdirPath, deletedSymlinkAddedFilePath)); - fs.writeFileSync(path.join(workdirPath, deletedSymlinkAddedFilePath), 'qux\nfoo\nbar\n', 'utf8'); - - const patch = await repository.getFilePatchForPath(deletedSymlinkAddedFilePath); - assert.equal(patch.toString(), dedent` - diff --git a/symlink.txt b/symlink.txt - deleted file mode 120000 - --- a/symlink.txt - +++ /dev/null - @@ -1 +0,0 @@ - -./regular-file.txt - \\ No newline at end of file - diff --git a/symlink.txt b/symlink.txt - new file mode 100644 - --- /dev/null - +++ b/symlink.txt - @@ -0,0 +1,3 @@ - +qux - +foo - +bar - - `); - }); - - it('handles typechange patches for a file replaced with a symlink', async function() { - if (process.env.ATOM_GITHUB_SKIP_SYMLINKS) { - this.skip(); - return; - } - - const workdirPath = await cloneRepository('symlinks'); - const repository = await buildRepository(workdirPath); - - const deletedFileAddedSymlinkPath = 'a.txt'; - fs.unlinkSync(path.join(workdirPath, deletedFileAddedSymlinkPath)); - fs.symlinkSync(path.join(workdirPath, 'regular-file.txt'), path.join(workdirPath, deletedFileAddedSymlinkPath)); - - const patch = await repository.getFilePatchForPath(deletedFileAddedSymlinkPath); - assert.equal(patch.toString(), dedent` - diff --git a/a.txt b/a.txt - deleted file mode 100644 - --- a/a.txt - +++ /dev/null - @@ -1,4 +0,0 @@ - -foo - -bar - -baz - - - diff --git a/a.txt b/a.txt - new file mode 120000 - --- /dev/null - +++ b/a.txt - @@ -0,0 +1 @@ - +${toGitPathSep(path.join(workdirPath, 'regular-file.txt'))} - \\ No newline at end of file - - `); - }); - }); - }); - - describe('getHeaderString()', function() { - it('formats paths with git path separators', function() { - const oldPath = path.join('foo', 'bar', 'old.js'); - const newPath = path.join('baz', 'qux', 'new.js'); - - const patch = createFilePatch(oldPath, newPath, 'modified', []); - assert.equal(patch.getHeaderString(), dedent` - diff --git a/foo/bar/old.js b/baz/qux/new.js - --- a/foo/bar/old.js - +++ b/baz/qux/new.js - - `); - }); - }); - - it('returns the size in bytes from getByteSize()', function() { - const filePatch = createFilePatch('a.txt', 'a.txt', 'modified', [ - new Hunk(1, 1, 1, 3, '', [ - new HunkLine('line-1', 'added', -1, 1), - new HunkLine('line-2', 'added', -1, 2), - new HunkLine('line-3', 'unchanged', 1, 3), - ]), - new Hunk(5, 7, 5, 4, '', [ - new HunkLine('line-4', 'unchanged', 5, 7), - new HunkLine('line-5', 'deleted', 6, -1), - new HunkLine('line-6', 'deleted', 7, -1), - ]), - ]); - - assert.strictEqual(filePatch.getByteSize(), 36); - }); -}); From 73f5d69ed3280f22281c82b43d8cd724b54e41d5 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 16:41:03 -0800 Subject: [PATCH 0972/4053] Finish up that question/comment... --- lib/views/multi-file-patch-view.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index dbc0f8f440..538825d7b5 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -891,7 +891,9 @@ export default class MultiFilePatchView extends React.Component { } if (newRow !== null) { - newRow -= 1; // Why is this needed? What's not being + // Why is this needed? I _think_ everything is in terms of buffer position + // so there shouldn't be an off-by-one issue + newRow -= 1; const cursors = cursorsByFilePatch.get(filePatch); if (!cursors) { cursorsByFilePatch.set(filePatch, [[newRow, newColumn]]); From dc1c00993a369d06a0c4814dc314c6222d33a133 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 17:00:01 -0800 Subject: [PATCH 0973/4053] Fix `undoLastDiscard` --- lib/controllers/multi-file-patch-controller.js | 1 - lib/views/multi-file-patch-view.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 6b7a19c6e0..6c9b8b0707 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -91,7 +91,6 @@ export default class MultiFilePatchController extends React.Component { eventSource, }); - return this.props.undoLastDiscard(filePatch.getPath(), this.props.repository); } diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 538825d7b5..91a5e1d300 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -585,8 +585,8 @@ export default class MultiFilePatchView extends React.Component { } } - undoLastDiscardFromButton = () => { - this.props.undoLastDiscard({eventSource: 'button'}); + undoLastDiscardFromButton = filePatch => { + this.props.undoLastDiscard(filePatch, {eventSource: 'button'}); } discardSelectionFromCommand = () => { From 6ab88e844e6c20e64d5b2bfff5a5c73c6f5b9b3d Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 9 Nov 2018 17:04:29 -0800 Subject: [PATCH 0974/4053] :memo: add new components to React atlas --- docs/react-component-atlas.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md index 35e55f883f..81c80e0a43 100644 --- a/docs/react-component-atlas.md +++ b/docs/react-component-atlas.md @@ -34,6 +34,11 @@ This is a high-level overview of the structure of the React component tree that > > > > The "GitHub" tab that appears in the right dock (by default). > > +> > > [``](/lig/items/commit-preview-item.js) +> > > [``](/lib/containers/commit-preview-container.js) +> > > +> > > Allows users to view all unstaged commits in one pane. +> > > > > > [``](/lib/views/remote-selector-view.js) > > > > > > Shown if the current repository has more than one remote that's identified as a github.com remote. @@ -66,12 +71,13 @@ This is a high-level overview of the structure of the React component tree that > > > > > > > > > > > > Render a list of issueish results as rows within the result list of a specific search. > -> > [``](/lib/controllers/file-patch-controller.js) -> > [``](/lib/views/file-patch-view.js) +> > [ ``](/lib/containers/changed-file-container.js) +> > [``](/lib/controllers/multi-file-patch-controller.js) +> > [``](/lib/views/multi-file-patch-view.js) +> > +> > The workspace-center pane that appears when looking at the staged or unstaged changes associated with one or more files. > > -> > The workspace-center pane that appears when looking at the staged or unstaged changes associated with a file. > > -> > :construction: Being rewritten in [#1712](https://github.com/atom/github/pull/1512) :construction: > > > [``](/lib/items/issueish-detail-item.js) > > [``](/lib/containers/issueish-detail-container.js) From a92f21c85ecc9a280522d642cf82a45e8a53ba01 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 17:19:32 -0800 Subject: [PATCH 0975/4053] Fix `undoLastDiscard` test for MultiFilePatchView --- test/views/multi-file-patch-view.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 776f3ebd78..7b71f591dd 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -85,7 +85,9 @@ describe('MultiFilePatchView', function() { wrapper.find('FilePatchHeaderView').first().prop('undoLastDiscard')(); - assert.isTrue(undoLastDiscard.calledWith({eventSource: 'button'})); + assert.lengthOf(filePatches.getFilePatches(), 1); + const [filePatch] = filePatches.getFilePatches(); + assert.isTrue(undoLastDiscard.calledWith(filePatch, {eventSource: 'button'})); }); it('renders the file patch within an editor', function() { From a2d5c99242aa571c37a20b9e4dc8bf6850a68f4f Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 17:51:00 -0800 Subject: [PATCH 0976/4053] Update button text in tests --- test/views/commit-view.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/views/commit-view.test.js b/test/views/commit-view.test.js index 14aef60080..b4213f8217 100644 --- a/test/views/commit-view.test.js +++ b/test/views/commit-view.test.js @@ -656,13 +656,13 @@ describe('CommitView', function() { it('displays correct button text depending on prop value', function() { const wrapper = shallow(app); - assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'Preview Commit'); + assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'See All Staged Changes'); wrapper.setProps({commitPreviewActive: true}); - assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'Close Commit Preview'); + assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'Hide All Staged Changes'); wrapper.setProps({commitPreviewActive: false}); - assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'Preview Commit'); + assert.strictEqual(wrapper.find('.github-CommitView-commitPreview').text(), 'See All Staged Changes'); }); }); }); From a60cfc5bdbbd425e8a1588a47dece84f5fec56db Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 18:00:10 -0800 Subject: [PATCH 0977/4053] Fix tests for opening file when there is only a single file patch --- test/views/multi-file-patch-view.test.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 7b71f591dd..e16769805f 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -8,7 +8,7 @@ import {nullFile} from '../../lib/models/patch/file'; import FilePatch from '../../lib/models/patch/file-patch'; import RefHolder from '../../lib/models/ref-holder'; -describe('MultiFilePatchView', function() { +describe.only('MultiFilePatchView', function() { let atomEnv, workspace, repository, filePatches; beforeEach(async function() { @@ -1078,8 +1078,8 @@ describe('MultiFilePatchView', function() { }); }); - describe('opening the file', function() { - let mfp; + describe('opening the file when there is only one file patch', function() { + let mfp, fp; beforeEach(function() { const {multiFilePatch} = multiFilePatchBuilder().addFilePatch(fp => { @@ -1095,6 +1095,8 @@ describe('MultiFilePatchView', function() { }).build(); mfp = multiFilePatch; + assert.lengthOf(mfp.getFilePatches(), 1); + fp = mfp.getFilePatches()[0]; }); it('opens the file at the current unchanged row', function() { @@ -1105,7 +1107,9 @@ describe('MultiFilePatchView', function() { editor.setCursorBufferPosition([7, 2]); atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); - assert.isTrue(openFile.calledWith([[14, 2]])); + console.log(openFile.args); + console.log(fp); + assert.isTrue(openFile.calledWith(fp, [[13, 2]])); }); it('opens the file at a current added row', function() { @@ -1117,7 +1121,7 @@ describe('MultiFilePatchView', function() { atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); - assert.isTrue(openFile.calledWith([[15, 3]])); + assert.isTrue(openFile.calledWith(fp, [[14, 3]])); }); it('opens the file at the beginning of the previous added or unchanged row', function() { @@ -1129,7 +1133,7 @@ describe('MultiFilePatchView', function() { atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); - assert.isTrue(openFile.calledWith([[15, 0]])); + assert.isTrue(openFile.calledWith(fp, [[15, 0]])); }); it('preserves multiple cursors', function() { @@ -1147,10 +1151,10 @@ describe('MultiFilePatchView', function() { atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); - assert.isTrue(openFile.calledWith([ + assert.isTrue(openFile.calledWith(fp, [ + [10, 2], [11, 2], - [12, 2], - [3, 3], + [2, 3], [15, 0], ])); }); From 1c711f00dbe09760a406dd91323af13d9cbef592 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 18:08:03 -0800 Subject: [PATCH 0978/4053] :fire: console.logs --- test/views/multi-file-patch-view.test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index e16769805f..5653d947fe 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -1107,8 +1107,6 @@ describe.only('MultiFilePatchView', function() { editor.setCursorBufferPosition([7, 2]); atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:open-file'); - console.log(openFile.args); - console.log(fp); assert.isTrue(openFile.calledWith(fp, [[13, 2]])); }); From 36e1e5f9b397bea55d1bb9b276bf478c42329596 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 18:09:44 -0800 Subject: [PATCH 0979/4053] :shirt: don't shadow `fp` --- test/views/multi-file-patch-view.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 5653d947fe..382c4e41bd 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -1082,13 +1082,13 @@ describe.only('MultiFilePatchView', function() { let mfp, fp; beforeEach(function() { - const {multiFilePatch} = multiFilePatchBuilder().addFilePatch(fp => { - fp.setOldFile(f => f.path('path.txt')); - fp.addHunk(h => { + const {multiFilePatch} = multiFilePatchBuilder().addFilePatch(filePatch => { + filePatch.setOldFile(f => f.path('path.txt')); + filePatch.addHunk(h => { h.oldRow(2); h.unchanged('0000').added('0001').unchanged('0002'); }); - fp.addHunk(h => { + filePatch.addHunk(h => { h.oldRow(10); h.unchanged('0003').added('0004', '0005').deleted('0006').unchanged('0007').added('0008').deleted('0009').unchanged('0010'); }); From 54b23c99fb5c4186bdcd07df5cf1289d6c987954 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 9 Nov 2018 18:29:43 -0800 Subject: [PATCH 0980/4053] Make commit preview button styled as `secondary` rather than `primary` UXR with @mattmattmatt -- he says if it's primary it competes too much with the commit button Co-Authored-By: Matt --- lib/views/commit-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index 4aedd51159..fdfa143ef4 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -164,7 +164,7 @@ export default class CommitView extends React.Component {
Date: Tue, 13 Nov 2018 16:05:43 -0500 Subject: [PATCH 1032/4053] :fire: unused import --- test/models/patch/multi-file-patch.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 663c452558..3ef8b38660 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -1,5 +1,4 @@ import dedent from 'dedent-js'; -import {Point} from 'atom'; import {multiFilePatchBuilder, filePatchBuilder} from '../../builder/patch'; From c068bbf94e34667ea78a556057a06810aca46875 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 13 Nov 2018 16:20:06 -0500 Subject: [PATCH 1033/4053] Accept a pending argument in openFile action method --- lib/controllers/multi-file-patch-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 1f4794c431..247f802c2a 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -109,9 +109,9 @@ export default class MultiFilePatchController extends React.Component { return this.props.surfaceFileAtPath(filePatch.getPath(), this.props.stagingStatus); } - async openFile(filePatch, positions) { + async openFile(filePatch, positions, pending) { const absolutePath = path.join(this.props.repository.getWorkingDirectoryPath(), filePatch.getPath()); - const editor = await this.props.workspace.open(absolutePath, {pending: true}); + const editor = await this.props.workspace.open(absolutePath, {pending}); if (positions.length > 0) { editor.setCursorBufferPosition(positions[0], {autoscroll: false}); for (const position of positions.slice(1)) { From b653c0fb706e8a67ee36b465ab4fc4a0432b96d5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 13 Nov 2018 16:20:30 -0500 Subject: [PATCH 1034/4053] Unit tests for opening multiple files --- lib/views/multi-file-patch-view.js | 5 +- test/views/multi-file-patch-view.test.js | 58 +++++++++++++++++------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index d189c15da0..f33573c5d1 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -905,9 +905,10 @@ export default class MultiFilePatchView extends React.Component { return null; }); - return Promise.all(Array.from(cursorsByFilePatch).map(value => { + const pending = cursorsByFilePatch.size === 1; + return Promise.all(Array.from(cursorsByFilePatch, value => { const [filePatch, cursors] = value; - return this.props.openFile(filePatch, cursors); + return this.props.openFile(filePatch, cursors, pending); })); } diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index e1851f46ca..ca8b85280a 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -1293,24 +1293,32 @@ describe('MultiFilePatchView', function() { }); }); - describe('opening the file when there is only one file patch', function() { + describe('jump to file', function() { let mfp, fp; beforeEach(function() { - const {multiFilePatch} = multiFilePatchBuilder().addFilePatch(filePatch => { - filePatch.setOldFile(f => f.path('path.txt')); - filePatch.addHunk(h => { - h.oldRow(2); - h.unchanged('0000').added('0001').unchanged('0002'); - }); - filePatch.addHunk(h => { - h.oldRow(10); - h.unchanged('0003').added('0004', '0005').deleted('0006').unchanged('0007').added('0008').deleted('0009').unchanged('0010'); - }); - }).build(); + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(filePatch => { + filePatch.setOldFile(f => f.path('path.txt')); + filePatch.addHunk(h => { + h.oldRow(2); + h.unchanged('0000').added('0001').unchanged('0002'); + }); + filePatch.addHunk(h => { + h.oldRow(10); + h.unchanged('0003').added('0004', '0005').deleted('0006').unchanged('0007').added('0008').deleted('0009').unchanged('0010'); + }); + }) + .addFilePatch(filePatch => { + filePatch.setOldFile(f => f.path('other.txt')); + filePatch.addHunk(h => { + h.oldRow(10); + h.unchanged('0011').added('0012').unchanged('0013'); + }); + }) + .build(); mfp = multiFilePatch; - assert.lengthOf(mfp.getFilePatches(), 1); fp = mfp.getFilePatches()[0]; }); @@ -1322,7 +1330,7 @@ describe('MultiFilePatchView', function() { editor.setCursorBufferPosition([7, 2]); atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:jump-to-file'); - assert.isTrue(openFile.calledWith(fp, [[13, 2]])); + assert.isTrue(openFile.calledWith(fp, [[13, 2]], true)); }); it('opens the file at a current added row', function() { @@ -1334,7 +1342,7 @@ describe('MultiFilePatchView', function() { atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:jump-to-file'); - assert.isTrue(openFile.calledWith(fp, [[14, 3]])); + assert.isTrue(openFile.calledWith(fp, [[14, 3]], true)); }); it('opens the file at the beginning of the previous added or unchanged row', function() { @@ -1346,7 +1354,7 @@ describe('MultiFilePatchView', function() { atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:jump-to-file'); - assert.isTrue(openFile.calledWith(fp, [[15, 0]])); + assert.isTrue(openFile.calledWith(fp, [[15, 0]], true)); }); it('preserves multiple cursors', function() { @@ -1369,7 +1377,23 @@ describe('MultiFilePatchView', function() { [11, 2], [2, 3], [15, 0], - ])); + ], true)); + }); + + it('opens non-pending editors when opening multiple', function() { + const openFile = sinon.spy(); + const wrapper = mount(buildApp({multiFilePatch: mfp, openFile})); + + const editor = wrapper.find('AtomTextEditor').instance().getModel(); + editor.setSelectedBufferRanges([ + [[4, 0], [4, 0]], + [[12, 0], [12, 0]], + ]); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:jump-to-file'); + + assert.isTrue(openFile.calledWith(mfp.getFilePatches()[0], [[11, 0]], false)); + assert.isTrue(openFile.calledWith(mfp.getFilePatches()[1], [[10, 0]], false)); }); }); }); From 65a8c222c4c34fbbe2572e1bbbe26ed5e893ee97 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 13 Nov 2018 16:28:18 -0500 Subject: [PATCH 1035/4053] Update file spanning state when manipulating selected rows manually --- lib/controllers/multi-file-patch-controller.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 247f802c2a..a74f8ebe48 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -132,7 +132,8 @@ export default class MultiFilePatchController extends React.Component { async toggleRows(rowSet, nextSelectionMode) { let chosenRows = rowSet; if (chosenRows) { - await this.selectedRowsChanged(chosenRows, nextSelectionMode); + const nextMultipleFileSelections = this.props.multiFilePatch.spansMultipleFiles(chosenRows); + await this.selectedRowsChanged(chosenRows, nextSelectionMode, nextMultipleFileSelections); } else { chosenRows = this.state.selectedRows; } @@ -194,7 +195,8 @@ export default class MultiFilePatchController extends React.Component { let chosenRows = rowSet; if (chosenRows) { - await this.selectedRowsChanged(chosenRows, nextSelectionMode); + const nextMultipleFileSelections = this.props.multiFilePatch.spansMultipleFiles(chosenRows); + await this.selectedRowsChanged(chosenRows, nextSelectionMode, nextMultipleFileSelections); } else { chosenRows = this.state.selectedRows; } From a367b787a259e4ee1ed64fe9bfb572c223f4c2e1 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 13 Nov 2018 16:22:32 -0800 Subject: [PATCH 1036/4053] Jump to correct file if there is no diff selection for file --- lib/views/multi-file-patch-view.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index f33573c5d1..9de3b4ede9 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -320,7 +320,7 @@ export default class MultiFilePatchView extends React.Component { undoLastDiscard={() => this.undoLastDiscardFromButton(filePatch)} diveIntoMirrorPatch={() => this.props.diveIntoMirrorPatch(filePatch)} - openFile={this.didOpenFile} + openFile={() => this.didOpenFile(filePatch)} toggleFile={() => this.props.toggleFile(filePatch)} /> @@ -848,7 +848,7 @@ export default class MultiFilePatchView extends React.Component { }); } - didOpenFile() { + didOpenFile(selectedFilePatch) { const cursorsByFilePatch = new Map(); this.refEditor.map(editor => { @@ -905,6 +905,11 @@ export default class MultiFilePatchView extends React.Component { return null; }); + const filePatchesWithCursors = new Set(cursorsByFilePatch.values()); + if (!filePatchesWithCursors.has(selectedFilePatch)) { + return this.props.openFile(selectedFilePatch, [], {pending: true}); + } + const pending = cursorsByFilePatch.size === 1; return Promise.all(Array.from(cursorsByFilePatch, value => { const [filePatch, cursors] = value; From 7a18876891068a242f014ad606f5943c458c84ea Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 13 Nov 2018 16:25:29 -0800 Subject: [PATCH 1037/4053] Jump to first changed line Or more specifically, first line in hunk, which is often a context line --- lib/views/multi-file-patch-view.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 9de3b4ede9..a0773a0d7e 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -907,7 +907,9 @@ export default class MultiFilePatchView extends React.Component { const filePatchesWithCursors = new Set(cursorsByFilePatch.values()); if (!filePatchesWithCursors.has(selectedFilePatch)) { - return this.props.openFile(selectedFilePatch, [], {pending: true}); + const [firstHunk] = selectedFilePatch.getHunks(); + const cursorRow = firstHunk ? firstHunk.getNewStartRow() - 1 : 0; + return this.props.openFile(selectedFilePatch, [[cursorRow, 0]], {pending: true}); } const pending = cursorsByFilePatch.size === 1; From 7bd749cd505f8ce8dd696b0cdec402b54722bff1 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 13 Nov 2018 16:26:27 -0800 Subject: [PATCH 1038/4053] Drop the rest in an `else` clause --- lib/views/multi-file-patch-view.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index a0773a0d7e..6a1bc9475d 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -910,14 +910,14 @@ export default class MultiFilePatchView extends React.Component { const [firstHunk] = selectedFilePatch.getHunks(); const cursorRow = firstHunk ? firstHunk.getNewStartRow() - 1 : 0; return this.props.openFile(selectedFilePatch, [[cursorRow, 0]], {pending: true}); + } else { + const pending = cursorsByFilePatch.size === 1; + return Promise.all(Array.from(cursorsByFilePatch, value => { + const [filePatch, cursors] = value; + return this.props.openFile(filePatch, cursors, pending); + })); } - const pending = cursorsByFilePatch.size === 1; - return Promise.all(Array.from(cursorsByFilePatch, value => { - const [filePatch, cursors] = value; - return this.props.openFile(filePatch, cursors, pending); - })); - } getSelectedRows() { From 1685f11732f4636f754500ac31b421da3f3ec53b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 13 Nov 2018 16:34:02 -0800 Subject: [PATCH 1039/4053] Check for existence of `selectedFilePatch` before accessing methods on it --- lib/views/multi-file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 6a1bc9475d..564225959b 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -906,7 +906,7 @@ export default class MultiFilePatchView extends React.Component { }); const filePatchesWithCursors = new Set(cursorsByFilePatch.values()); - if (!filePatchesWithCursors.has(selectedFilePatch)) { + if (selectedFilePatch && !filePatchesWithCursors.has(selectedFilePatch)) { const [firstHunk] = selectedFilePatch.getHunks(); const cursorRow = firstHunk ? firstHunk.getNewStartRow() - 1 : 0; return this.props.openFile(selectedFilePatch, [[cursorRow, 0]], {pending: true}); From bed19dc744f2844e789c6d8c8ebb05570440c825 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 13 Nov 2018 16:37:13 -0800 Subject: [PATCH 1040/4053] Don't mistake filePatch param with command event --- lib/views/multi-file-patch-view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 564225959b..26bc45fd1e 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -320,7 +320,7 @@ export default class MultiFilePatchView extends React.Component { undoLastDiscard={() => this.undoLastDiscardFromButton(filePatch)} diveIntoMirrorPatch={() => this.props.diveIntoMirrorPatch(filePatch)} - openFile={() => this.didOpenFile(filePatch)} + openFile={() => this.didOpenFile({selectedFilePatch: filePatch})} toggleFile={() => this.props.toggleFile(filePatch)} /> @@ -848,7 +848,7 @@ export default class MultiFilePatchView extends React.Component { }); } - didOpenFile(selectedFilePatch) { + didOpenFile({selectedFilePatch} = {}) { const cursorsByFilePatch = new Map(); this.refEditor.map(editor => { From 2a70de1d1064978e7ae9b742c7178c3e5aeaecfc Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 13 Nov 2018 16:46:59 -0800 Subject: [PATCH 1041/4053] Oops we want to be getting the keys of `cursorsByFilePatch` --- lib/views/multi-file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 26bc45fd1e..5b615d8910 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -905,7 +905,7 @@ export default class MultiFilePatchView extends React.Component { return null; }); - const filePatchesWithCursors = new Set(cursorsByFilePatch.values()); + const filePatchesWithCursors = new Set(cursorsByFilePatch.keys()); if (selectedFilePatch && !filePatchesWithCursors.has(selectedFilePatch)) { const [firstHunk] = selectedFilePatch.getHunks(); const cursorRow = firstHunk ? firstHunk.getNewStartRow() - 1 : 0; From 62be5a4bc6fb380f124dce41827e26175d39256f Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 13 Nov 2018 17:06:30 -0800 Subject: [PATCH 1042/4053] Pass the right argument for pending panes --- lib/views/multi-file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 5b615d8910..ae473fcfd6 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -909,7 +909,7 @@ export default class MultiFilePatchView extends React.Component { if (selectedFilePatch && !filePatchesWithCursors.has(selectedFilePatch)) { const [firstHunk] = selectedFilePatch.getHunks(); const cursorRow = firstHunk ? firstHunk.getNewStartRow() - 1 : 0; - return this.props.openFile(selectedFilePatch, [[cursorRow, 0]], {pending: true}); + return this.props.openFile(selectedFilePatch, [[cursorRow, 0]], true); } else { const pending = cursorsByFilePatch.size === 1; return Promise.all(Array.from(cursorsByFilePatch, value => { From 18a08d9cc4ce1c85514f6e80046e8fcfcd3a7efc Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 13 Nov 2018 17:09:54 -0800 Subject: [PATCH 1043/4053] Add tests for jumping to file from header button --- test/views/multi-file-patch-view.test.js | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index ca8b85280a..23d4b3f490 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -1395,6 +1395,43 @@ describe('MultiFilePatchView', function() { assert.isTrue(openFile.calledWith(mfp.getFilePatches()[0], [[11, 0]], false)); assert.isTrue(openFile.calledWith(mfp.getFilePatches()[1], [[10, 0]], false)); }); + + describe('didOpenFile(selectedFilePatch)', function() { + describe('when there is a selection in the selectedFilePatch', function() { + it('opens the file and places the cursor corresponding to the selection', function() { + const openFile = sinon.spy(); + const wrapper = mount(buildApp({multiFilePatch: mfp, openFile})); + + const editor = wrapper.find('AtomTextEditor').instance().getModel(); + editor.setSelectedBufferRanges([ + [[4, 0], [4, 0]], // cursor in first file patch + ]); + + const firstFilePatch = mfp.getFilePatches()[0]; + wrapper.instance().didOpenFile({selectedFilePatch: firstFilePatch}); + + assert.isTrue(openFile.calledWith(firstFilePatch, [[11, 0]], true)); + }); + }); + + describe('when there are no selections in the selectedFilePatch', function() { + it('opens the file and places the cursor at the beginning of the first hunk', function() { + const openFile = sinon.spy(); + const wrapper = mount(buildApp({multiFilePatch: mfp, openFile})); + + const editor = wrapper.find('AtomTextEditor').instance().getModel(); + editor.setSelectedBufferRanges([ + [[4, 0], [4, 0]], // cursor in first file patch + ]); + + const secondFilePatch = mfp.getFilePatches()[1]; + const firstHunkBufferRow = secondFilePatch.getHunks()[0].getNewStartRow() - 1; + wrapper.instance().didOpenFile({selectedFilePatch: secondFilePatch}); + + assert.isTrue(openFile.calledWith(secondFilePatch, [[firstHunkBufferRow, 0]], true)); + }); + }); + }); }); }); }); From 4fe18c5928c202b639084ef19dd300b1ad393d1e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Tue, 13 Nov 2018 17:46:36 -0800 Subject: [PATCH 1044/4053] :art: test to get us back to 100% test coverage of new lines --- lib/views/file-patch-header-view.js | 2 +- test/views/multi-file-patch-view.test.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index b715423a3e..b7d83f1fab 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -107,7 +107,7 @@ export default class FilePatchHeaderView extends React.Component { return ( - Date: Tue, 13 Nov 2018 21:44:56 -0500 Subject: [PATCH 1045/4053] :shirt: --- lib/views/file-patch-header-view.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index b7d83f1fab..7b37cf810f 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -107,7 +107,10 @@ export default class FilePatchHeaderView extends React.Component { return ( - Date: Wed, 14 Nov 2018 10:51:21 -0500 Subject: [PATCH 1046/4053] Unused component state --- lib/controllers/file-patch-controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 14dc10db17..6ff4f077fc 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -37,7 +37,6 @@ export default class FilePatchController extends React.Component { ); this.state = { - lastFilePatch: this.props.filePatch, selectionMode: 'hunk', selectedRows: new Set(), }; From 2ce9ee18c97e6ae2b4596ff36afbe7bb94878a25 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 14 Nov 2018 10:51:54 -0500 Subject: [PATCH 1047/4053] Use a serialized form of the previous patch to detect patch changes --- lib/controllers/file-patch-controller.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/controllers/file-patch-controller.js b/lib/controllers/file-patch-controller.js index 6ff4f077fc..551d026259 100644 --- a/lib/controllers/file-patch-controller.js +++ b/lib/controllers/file-patch-controller.js @@ -44,13 +44,17 @@ export default class FilePatchController extends React.Component { this.mouseSelectionInProgress = false; this.stagingOperationInProgress = false; + this.lastPatchString = null; this.patchChangePromise = new Promise(resolve => { this.resolvePatchChangePromise = resolve; }); } componentDidUpdate(prevProps) { - if (prevProps.filePatch !== this.props.filePatch) { + if ( + this.lastPatchString !== null && + this.lastPatchString !== this.props.filePatch.toString() + ) { this.resolvePatchChangePromise(); this.patchChangePromise = new Promise(resolve => { this.resolvePatchChangePromise = resolve; @@ -219,11 +223,18 @@ export default class FilePatchController extends React.Component { if (this.stagingOperationInProgress) { return null; } + this.stagingOperationInProgress = true; - this.patchChangePromise.then(() => { - this.stagingOperationInProgress = false; - }); + this.lastPatchString = this.props.filePatch.toString(); + const operationPromise = fn(); + + operationPromise + .then(() => this.patchChangePromise) + .then(() => { + this.stagingOperationInProgress = false; + this.lastPatchString = null; + }); - return fn(); + return operationPromise; } } From caeaa479c8f284480f1d978a7428858820ded189 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 14 Nov 2018 11:29:16 -0500 Subject: [PATCH 1048/4053] Update staging operation tests for the new patch comparison logic --- test/controllers/file-patch-controller.test.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js index f7f6abb1db..2ea7d2d90e 100644 --- a/test/controllers/file-patch-controller.test.js +++ b/test/controllers/file-patch-controller.test.js @@ -4,6 +4,7 @@ import React from 'react'; import {shallow} from 'enzyme'; import FilePatchController from '../../lib/controllers/file-patch-controller'; +import FilePatch from '../../lib/models/patch/file-patch'; import * as reporterProxy from '../../lib/reporter-proxy'; import {cloneRepository, buildRepository} from '../helpers'; @@ -151,14 +152,22 @@ describe('FilePatchController', function() { const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); assert.strictEqual(await wrapper.find('FilePatchView').prop('toggleFile')(), 'staged'); - wrapper.setProps({stagingStatus: 'staged'}); + // No-op assert.isNull(await wrapper.find('FilePatchView').prop('toggleFile')()); - const promise = wrapper.instance().patchChangePromise; + // Simulate an identical patch arriving too soon wrapper.setProps({filePatch: filePatch.clone()}); + + // Still a no-op + assert.isNull(await wrapper.find('FilePatchView').prop('toggleFile')()); + + // Simulate updated patch arrival + const promise = wrapper.instance().patchChangePromise; + wrapper.setProps({filePatch: FilePatch.createNull()}); await promise; - assert.strictEqual(await wrapper.find('FilePatchView').prop('toggleFile')(), 'unstaged'); + // Performs an operation again + assert.strictEqual(await wrapper.find('FilePatchView').prop('toggleFile')(), 'staged'); }); }); From 44bd0df1adf9fbc35eafc6fd4edb502283324841 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 14 Nov 2018 11:33:22 -0500 Subject: [PATCH 1049/4053] Update Enzyme and its React 16 adapter --- package-lock.json | 174 +++++++++++++++++++++++++++++----------------- package.json | 4 +- 2 files changed, 111 insertions(+), 67 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1df9fb4073..3ca624ba5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -214,36 +214,10 @@ "tmp": "0.0.31" } }, - "@smashwilson/enzyme-adapter-react-16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smashwilson/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.2.tgz", - "integrity": "sha512-YGotyPAPOwi9vbRzvTutDgqYbBo5gX8mELNHPICcAXilV9XMa0yxqCQ6OY4ItIO5dPiRkc9RkjSYBr2M1fFOZw==", - "dev": true, - "requires": { - "@smashwilson/enzyme-adapter-utils": "^1.0.0", - "lodash": "^4.17.4", - "object.assign": "^4.1.0", - "object.values": "^1.0.4", - "prop-types": "^15.6.0", - "react-reconciler": "^0.7.0", - "react-test-renderer": "^16.0.0-0" - } - }, - "@smashwilson/enzyme-adapter-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@smashwilson/enzyme-adapter-utils/-/enzyme-adapter-utils-1.0.0.tgz", - "integrity": "sha512-/kQoTFU5bdbZbh6C9Ohxz1BgoHW+n2BX/kGUcS3DucGZfVNl4Em9lJqzd3aelyZSJz+cRX/FuE6ccnO6MnZc0Q==", - "dev": true, - "requires": { - "lodash": "^4.17.4", - "object.assign": "^4.1.0", - "prop-types": "^15.6.0" - } - }, "@types/node": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.7.1.tgz", - "integrity": "sha512-EGoI4ylB/lPOaqXqtzAyL8HcgOuCtH2hkEaLmkueOYufsTFWBn4VCvlCDC2HW8Q+9iF+QVC3sxjDKQYjHQeZ9w==", + "version": "10.12.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.7.tgz", + "integrity": "sha512-Zh5Z4kACfbeE8aAOYh9mqotRxaZMro8MbBQtR8vEXOMiZo2rGEh2LayJijKdlu48YnS6y2EFU/oo2NCe5P6jGw==", "dev": true }, "abstract-leveldown": { @@ -2247,9 +2221,9 @@ } }, "css-what": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", - "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz", + "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==", "dev": true }, "currently-unhandled": { @@ -2510,9 +2484,9 @@ "dev": true }, "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.2.1.tgz", + "integrity": "sha512-SQVCLFS2E7G5CRCMdn6K9bIhRj1bS6QBWZfF0TUPh4V/BbqrQ619IdSS3/izn0FZ+9l+uODzaZjb08fjOfablA==", "dev": true }, "domhandler": { @@ -2762,9 +2736,9 @@ } }, "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", "dev": true }, "env-paths": { @@ -2774,9 +2748,9 @@ "dev": true }, "enzyme": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.4.1.tgz", - "integrity": "sha512-XBZbyUy36WipNSBVZKIR1sg9iF6zXfkfDEzwTc10T9zhB61UPnMo+c3WE17T/jyhfmPJOz6X073NXXsR7G/1rA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.7.0.tgz", + "integrity": "sha512-QLWx+krGK6iDNyR1KlH5YPZqxZCQaVF6ike1eDJAOg0HvSkSCVImPsdWaNw6v+VrnK92Kg8jIOYhuOSS9sBpyg==", "dev": true, "requires": { "array.prototype.flat": "^1.2.1", @@ -2788,14 +2762,16 @@ "is-number-object": "^1.0.3", "is-string": "^1.0.4", "is-subset": "^0.1.1", - "lodash": "^4.17.4", + "lodash.escape": "^4.0.1", + "lodash.isequal": "^4.5.0", "object-inspect": "^1.6.0", "object-is": "^1.0.1", "object.assign": "^4.1.0", "object.entries": "^1.0.4", "object.values": "^1.0.4", "raf": "^3.4.0", - "rst-selector-parser": "^2.2.3" + "rst-selector-parser": "^2.2.3", + "string.prototype.trim": "^1.1.2" }, "dependencies": { "has": { @@ -2815,6 +2791,41 @@ } } }, + "enzyme-adapter-react-16": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.7.0.tgz", + "integrity": "sha512-rDr0xlnnFPffAPYrvG97QYJaRl9unVDslKee33wTStsBEwZTkESX1H7VHGT5eUc6ifNzPgOJGvSh2zpHT4gXjA==", + "dev": true, + "requires": { + "enzyme-adapter-utils": "^1.9.0", + "function.prototype.name": "^1.1.0", + "object.assign": "^4.1.0", + "object.values": "^1.0.4", + "prop-types": "^15.6.2", + "react-is": "^16.6.1", + "react-test-renderer": "^16.0.0-0" + }, + "dependencies": { + "react-is": { + "version": "16.6.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", + "integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==", + "dev": true + } + } + }, + "enzyme-adapter-utils": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.9.0.tgz", + "integrity": "sha512-uMe4xw4l/Iloh2Fz+EO23XUYMEQXj5k/5ioLUXCNOUCI8Dml5XQMO9+QwUq962hBsY5qftfHHns+d990byWHvg==", + "dev": true, + "requires": { + "function.prototype.name": "^1.1.0", + "object.assign": "^4.1.0", + "prop-types": "^15.6.2", + "semver": "^5.6.0" + } + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -4011,9 +4022,9 @@ "dev": true }, "htmlparser2": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", - "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", + "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", "dev": true, "requires": { "domelementtype": "^1.3.0", @@ -4021,7 +4032,35 @@ "domutils": "^1.5.1", "entities": "^1.1.1", "inherits": "^2.0.1", - "readable-stream": "^2.0.2" + "readable-stream": "^3.0.6" + }, + "dependencies": { + "domelementtype": { + "version": "1.3.0", + "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "readable-stream": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz", + "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "http-signature": { @@ -4901,6 +4940,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", "integrity": "sha1-maktZcAnLevoyWtgV7yPv6O+1RE=" }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", + "dev": true + }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -5585,9 +5630,9 @@ } }, "nth-check": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", - "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", "dev": true, "requires": { "boolbase": "~1.0.0" @@ -6384,9 +6429,9 @@ "dev": true }, "raf": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", - "integrity": "sha512-pDP/NMRAXoTfrhCfyfSEwJAKLaxBU9eApMeBPB1TkDouZmvPerIClV8lTAd+uF8ZiTaVl69e1FCxQrAd/VTjGw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", "dev": true, "requires": { "performance-now": "^2.1.0" @@ -6471,18 +6516,6 @@ "integrity": "sha512-xpb0PpALlFWNw/q13A+1aHeyJyLYCg0/cCHPUA43zYluZuIPHaHL3k8OBsTgQtxqW0FhyDEMvi8fZ/+7+r4OSQ==", "dev": true }, - "react-reconciler": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.7.0.tgz", - "integrity": "sha512-50JwZ3yNyMS8fchN+jjWEJOH3Oze7UmhxeoJLn2j6f3NjpfCRbcmih83XTWmzqtar/ivd5f7tvQhvvhism2fgg==", - "dev": true, - "requires": { - "fbjs": "^0.8.16", - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.0" - } - }, "react-relay": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/react-relay/-/react-relay-1.6.0.tgz", @@ -7566,6 +7599,17 @@ "regexp.prototype.flags": "^1.2.0" } }, + "string.prototype.trim": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", + "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.0", + "function-bind": "^1.0.2" + } + }, "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", diff --git a/package.json b/package.json index aeef54f6bc..2a9f3f95a8 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ }, "devDependencies": { "@smashwilson/atom-mocha-test-runner": "1.4.0", - "@smashwilson/enzyme-adapter-react-16": "1.0.2", "babel-plugin-istanbul": "4.1.6", "chai": "4.1.2", "chai-as-promised": "7.1.1", @@ -86,7 +85,8 @@ "electron-devtools-installer": "2.2.4", "electron-link": "0.2.2", "electron-mksnapshot": "3.0.0-beta.1", - "enzyme": "3.4.1", + "enzyme": "3.7.0", + "enzyme-adapter-react-16": "1.7.0", "eslint": "5.0.1", "eslint-config-fbjs-opensource": "1.0.0", "eslint-plugin-jsx-a11y": "^6.1.1", From fff2feb256191a07a7109ec8d06c9389835928f1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 14 Nov 2018 11:33:36 -0500 Subject: [PATCH 1050/4053] Require Enzyme's React adapter correctly --- test/runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runner.js b/test/runner.js index 101f08fc55..431f8f4036 100644 --- a/test/runner.js +++ b/test/runner.js @@ -101,7 +101,7 @@ module.exports = createRunner({ } const Enzyme = require('enzyme'); - const Adapter = require('@smashwilson/enzyme-adapter-react-16'); + const Adapter = require('enzyme-adapter-react-16'); Enzyme.configure({adapter: new Adapter()}); require('mocha-stress'); From fa7ef18671ac64b42ae538419ede2c41fabdc20b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 14 Nov 2018 11:46:25 -0500 Subject: [PATCH 1051/4053] Enzyme's shallow renderer can't track render props --- test/atom/pane-item.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/atom/pane-item.test.js b/test/atom/pane-item.test.js index d85f5b42de..388fb9cff8 100644 --- a/test/atom/pane-item.test.js +++ b/test/atom/pane-item.test.js @@ -89,7 +89,7 @@ describe('PaneItem', function() { describe('when opened with a matching URI', function() { it('calls its render prop', async function() { let called = false; - shallow( + mount( {() => { called = true; From f0869d3ed9d034103a68b08df2510de283dfddd9 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen <6842965+vanessayuenn@users.noreply.github.com> Date: Wed, 14 Nov 2018 19:00:49 +0100 Subject: [PATCH 1052/4053] Update 004-multi-file-diff.md Move sticky nav header to "out of scope" --- docs/rfcs/004-multi-file-diff.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/rfcs/004-multi-file-diff.md b/docs/rfcs/004-multi-file-diff.md index b601fb8013..195c3af70b 100644 --- a/docs/rfcs/004-multi-file-diff.md +++ b/docs/rfcs/004-multi-file-diff.md @@ -32,7 +32,7 @@ A new button added above the commit message box that, when clicked, opens a mult - Shows diffs of multiple files as a stack. - Each diff retains the file-specific controls it currently has in its header (e.g. the open file, stage file, undo last discard, etc). - **[[out of scope]](https://github.com/atom/github/blob/multi-diff-rfc/docs/rfcs/004-multi-file-diff.md#warning-out-of-scope)** It should be easy to jump quickly to a specific file you care about, or back to the file list to get to another file. Dotcom does so by creating a `jump to` drop down. -- As user scrolls through a long list of diffs, there should be a sticky heading which remains visible showing the filename of the diff being viewed. +- **[[out of scope]](https://github.com/atom/github/blob/multi-diff-rfc/docs/rfcs/004-multi-file-diff.md#warning-out-of-scope)** As user scrolls through a long list of diffs, there should be a sticky heading which remains visible showing the filename of the diff being viewed. - **[[out of scope]](https://github.com/atom/github/blob/multi-diff-rfc/docs/rfcs/004-multi-file-diff.md#warning-out-of-scope)** Each file diff can be collapsed. #### Workflow @@ -79,11 +79,11 @@ Unfiltered | Filtered **Alternative**: It might be possible to re-use the find+replace UI to filter the multi-file diff. And maybe even have "replace" working. +#### Sticky navigation header +As user scrolls through a long list of diffs, there should be a sticky heading which remains visible showing the filename of the diff being viewed. + #### Other out of scope UX considerations - whether `cmd+click` to select multiple files is discoverable ## :construction: Implementation phases -TBD - -## :white_check_mark: Definition of done -TBD +See [checklist on PR](https://github.com/atom/github/pull/1767) From df837c84b757f3811390d0dd2308e6af970c4441 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 14 Nov 2018 13:08:37 -0500 Subject: [PATCH 1053/4053] Delete extraneous test that slipped in with the merge conflict --- .../controllers/file-patch-controller.test.js | 460 ------------------ 1 file changed, 460 deletions(-) delete mode 100644 test/controllers/file-patch-controller.test.js diff --git a/test/controllers/file-patch-controller.test.js b/test/controllers/file-patch-controller.test.js deleted file mode 100644 index 2ea7d2d90e..0000000000 --- a/test/controllers/file-patch-controller.test.js +++ /dev/null @@ -1,460 +0,0 @@ -import path from 'path'; -import fs from 'fs-extra'; -import React from 'react'; -import {shallow} from 'enzyme'; - -import FilePatchController from '../../lib/controllers/file-patch-controller'; -import FilePatch from '../../lib/models/patch/file-patch'; -import * as reporterProxy from '../../lib/reporter-proxy'; -import {cloneRepository, buildRepository} from '../helpers'; - -describe('FilePatchController', function() { - let atomEnv, repository, filePatch; - - beforeEach(async function() { - atomEnv = global.buildAtomEnvironment(); - - const workdirPath = await cloneRepository(); - repository = await buildRepository(workdirPath); - - // a.txt: unstaged changes - await fs.writeFile(path.join(workdirPath, 'a.txt'), '00\n01\n02\n03\n04\n05\n06'); - - filePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); - }); - - afterEach(function() { - atomEnv.destroy(); - }); - - function buildApp(overrideProps = {}) { - const props = { - repository, - stagingStatus: 'unstaged', - relPath: 'a.txt', - isPartiallyStaged: false, - filePatch, - hasUndoHistory: false, - workspace: atomEnv.workspace, - commands: atomEnv.commands, - keymaps: atomEnv.keymaps, - tooltips: atomEnv.tooltips, - config: atomEnv.config, - destroy: () => {}, - discardLines: () => {}, - undoLastDiscard: () => {}, - surfaceFileAtPath: () => {}, - ...overrideProps, - }; - - return ; - } - - it('passes extra props to the FilePatchView', function() { - const extra = Symbol('extra'); - const wrapper = shallow(buildApp({extra})); - - assert.strictEqual(wrapper.find('FilePatchView').prop('extra'), extra); - }); - - it('calls undoLastDiscard through with set arguments', function() { - const undoLastDiscard = sinon.spy(); - const wrapper = shallow(buildApp({relPath: 'b.txt', undoLastDiscard})); - wrapper.find('FilePatchView').prop('undoLastDiscard')(); - - assert.isTrue(undoLastDiscard.calledWith('b.txt', repository)); - }); - - it('calls surfaceFileAtPath with set arguments', function() { - const surfaceFileAtPath = sinon.spy(); - const wrapper = shallow(buildApp({relPath: 'c.txt', surfaceFileAtPath})); - wrapper.find('FilePatchView').prop('surfaceFile')(); - - assert.isTrue(surfaceFileAtPath.calledWith('c.txt', 'unstaged')); - }); - - describe('diveIntoMirrorPatch()', function() { - it('destroys the current pane and opens the staged changes', async function() { - const destroy = sinon.spy(); - sinon.stub(atomEnv.workspace, 'open').resolves(); - const wrapper = shallow(buildApp({relPath: 'c.txt', stagingStatus: 'unstaged', destroy})); - - await wrapper.find('FilePatchView').prop('diveIntoMirrorPatch')(); - - assert.isTrue(destroy.called); - assert.isTrue(atomEnv.workspace.open.calledWith( - 'atom-github://file-patch/c.txt' + - `?workdir=${encodeURIComponent(repository.getWorkingDirectoryPath())}&stagingStatus=staged`, - )); - }); - - it('destroys the current pane and opens the unstaged changes', async function() { - const destroy = sinon.spy(); - sinon.stub(atomEnv.workspace, 'open').resolves(); - const wrapper = shallow(buildApp({relPath: 'd.txt', stagingStatus: 'staged', destroy})); - - await wrapper.find('FilePatchView').prop('diveIntoMirrorPatch')(); - - assert.isTrue(destroy.called); - assert.isTrue(atomEnv.workspace.open.calledWith( - 'atom-github://file-patch/d.txt' + - `?workdir=${encodeURIComponent(repository.getWorkingDirectoryPath())}&stagingStatus=unstaged`, - )); - }); - }); - - describe('openFile()', function() { - it('opens an editor on the current file', async function() { - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - const editor = await wrapper.find('FilePatchView').prop('openFile')([]); - - assert.strictEqual(editor.getPath(), path.join(repository.getWorkingDirectoryPath(), 'a.txt')); - }); - - it('sets the cursor to a single position', async function() { - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - const editor = await wrapper.find('FilePatchView').prop('openFile')([[1, 1]]); - - assert.deepEqual(editor.getCursorBufferPositions().map(p => p.serialize()), [[1, 1]]); - }); - - it('adds cursors at a set of positions', async function() { - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - const editor = await wrapper.find('FilePatchView').prop('openFile')([[1, 1], [3, 1], [5, 0]]); - - assert.deepEqual(editor.getCursorBufferPositions().map(p => p.serialize()), [[1, 1], [3, 1], [5, 0]]); - }); - }); - - describe('toggleFile()', function() { - it('stages the current file if unstaged', async function() { - sinon.spy(repository, 'stageFiles'); - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - - await wrapper.find('FilePatchView').prop('toggleFile')(); - - assert.isTrue(repository.stageFiles.calledWith(['a.txt'])); - }); - - it('unstages the current file if staged', async function() { - sinon.spy(repository, 'unstageFiles'); - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'staged'})); - - await wrapper.find('FilePatchView').prop('toggleFile')(); - - assert.isTrue(repository.unstageFiles.calledWith(['a.txt'])); - }); - - it('is a no-op if a staging operation is already in progress', async function() { - sinon.stub(repository, 'stageFiles').resolves('staged'); - sinon.stub(repository, 'unstageFiles').resolves('unstaged'); - - const wrapper = shallow(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - assert.strictEqual(await wrapper.find('FilePatchView').prop('toggleFile')(), 'staged'); - - // No-op - assert.isNull(await wrapper.find('FilePatchView').prop('toggleFile')()); - - // Simulate an identical patch arriving too soon - wrapper.setProps({filePatch: filePatch.clone()}); - - // Still a no-op - assert.isNull(await wrapper.find('FilePatchView').prop('toggleFile')()); - - // Simulate updated patch arrival - const promise = wrapper.instance().patchChangePromise; - wrapper.setProps({filePatch: FilePatch.createNull()}); - await promise; - - // Performs an operation again - assert.strictEqual(await wrapper.find('FilePatchView').prop('toggleFile')(), 'staged'); - }); - }); - - describe('selected row and selection mode tracking', function() { - it('captures the selected row set', function() { - const wrapper = shallow(buildApp()); - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); - - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'line'); - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); - }); - - it('does not re-render if the row set and selection mode are unchanged', function() { - const wrapper = shallow(buildApp()); - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), []); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); - - sinon.spy(wrapper.instance(), 'render'); - - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'line'); - - assert.isTrue(wrapper.instance().render.called); - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); - - wrapper.instance().render.resetHistory(); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([2, 1]), 'line'); - - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'line'); - assert.isFalse(wrapper.instance().render.called); - - wrapper.instance().render.resetHistory(); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2]), 'hunk'); - - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [1, 2]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); - assert.isTrue(wrapper.instance().render.called); - }); - - describe('discardLines()', function() { - it('records an event', async function() { - const wrapper = shallow(buildApp()); - sinon.stub(reporterProxy, 'addEvent'); - await wrapper.find('FilePatchView').prop('discardRows')(new Set([1, 2])); - assert.isTrue(reporterProxy.addEvent.calledWith('discard-unstaged-changes', { - package: 'github', - component: 'FilePatchController', - lineCount: 2, - eventSource: undefined, - })); - }); - }); - - describe('undoLastDiscard()', function() { - it('records an event', function() { - const wrapper = shallow(buildApp()); - sinon.stub(reporterProxy, 'addEvent'); - wrapper.find('FilePatchView').prop('undoLastDiscard')(); - assert.isTrue(reporterProxy.addEvent.calledWith('undo-last-discard', { - package: 'github', - component: 'FilePatchController', - eventSource: undefined, - })); - }); - }); - }); - - describe('toggleRows()', function() { - it('is a no-op with no selected rows', async function() { - const wrapper = shallow(buildApp()); - - sinon.spy(repository, 'applyPatchToIndex'); - - await wrapper.find('FilePatchView').prop('toggleRows')(); - assert.isFalse(repository.applyPatchToIndex.called); - }); - - it('applies a stage patch to the index', async function() { - const wrapper = shallow(buildApp()); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1])); - - sinon.spy(filePatch, 'getStagePatchForLines'); - sinon.spy(repository, 'applyPatchToIndex'); - - await wrapper.find('FilePatchView').prop('toggleRows')(); - - assert.sameMembers(Array.from(filePatch.getStagePatchForLines.lastCall.args[0]), [1]); - assert.isTrue(repository.applyPatchToIndex.calledWith(filePatch.getStagePatchForLines.returnValues[0])); - }); - - it('toggles a different row set if provided', async function() { - const wrapper = shallow(buildApp()); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1]), 'line'); - - sinon.spy(filePatch, 'getStagePatchForLines'); - sinon.spy(repository, 'applyPatchToIndex'); - - await wrapper.find('FilePatchView').prop('toggleRows')(new Set([2]), 'hunk'); - - assert.sameMembers(Array.from(filePatch.getStagePatchForLines.lastCall.args[0]), [2]); - assert.isTrue(repository.applyPatchToIndex.calledWith(filePatch.getStagePatchForLines.returnValues[0])); - - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [2]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); - }); - - it('applies an unstage patch to the index', async function() { - await repository.stageFiles(['a.txt']); - const otherPatch = await repository.getFilePatchForPath('a.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: otherPatch, stagingStatus: 'staged'})); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([2])); - - sinon.spy(otherPatch, 'getUnstagePatchForLines'); - sinon.spy(repository, 'applyPatchToIndex'); - - await wrapper.find('FilePatchView').prop('toggleRows')(); - - assert.sameMembers(Array.from(otherPatch.getUnstagePatchForLines.lastCall.args[0]), [2]); - assert.isTrue(repository.applyPatchToIndex.calledWith(otherPatch.getUnstagePatchForLines.returnValues[0])); - }); - }); - - if (process.platform !== 'win32') { - describe('toggleModeChange()', function() { - it("it stages an unstaged file's new mode", async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); - await fs.chmod(p, 0o755); - repository.refresh(); - const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: false}); - - const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'unstaged'})); - - sinon.spy(repository, 'stageFileModeChange'); - await wrapper.find('FilePatchView').prop('toggleModeChange')(); - - assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100755')); - }); - - it("it stages a staged file's old mode", async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'a.txt'); - await fs.chmod(p, 0o755); - await repository.stageFiles(['a.txt']); - repository.refresh(); - const newFilePatch = await repository.getFilePatchForPath('a.txt', {staged: true}); - - const wrapper = shallow(buildApp({filePatch: newFilePatch, stagingStatus: 'staged'})); - - sinon.spy(repository, 'stageFileModeChange'); - await wrapper.find('FilePatchView').prop('toggleModeChange')(); - - assert.isTrue(repository.stageFileModeChange.calledWith('a.txt', '100644')); - }); - }); - - describe('toggleSymlinkChange', function() { - it('handles an addition and typechange with a special repository method', async function() { - if (process.env.ATOM_GITHUB_SKIP_SYMLINKS) { - this.skip(); - return; - } - - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.symlink(dest, p); - - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); - - await fs.unlink(p); - await fs.writeFile(p, 'fdsa\n', 'utf8'); - - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); - - sinon.spy(repository, 'stageFileSymlinkChange'); - - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - - assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); - }); - - it('stages non-addition typechanges normally', async function() { - if (process.env.ATOM_GITHUB_SKIP_SYMLINKS) { - this.skip(); - return; - } - - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.symlink(dest, p); - - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); - - await fs.unlink(p); - - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: false}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'unstaged'})); - - sinon.spy(repository, 'stageFiles'); - - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - - assert.isTrue(repository.stageFiles.calledWith(['waslink.txt'])); - }); - - it('handles a deletion and typechange with a special repository method', async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.writeFile(p, 'fdsa\n', 'utf8'); - - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); - - await fs.unlink(p); - await fs.symlink(dest, p); - await repository.stageFiles(['waslink.txt']); - - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); - - sinon.spy(repository, 'stageFileSymlinkChange'); - - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - - assert.isTrue(repository.stageFileSymlinkChange.calledWith('waslink.txt')); - }); - - it('unstages non-deletion typechanges normally', async function() { - const p = path.join(repository.getWorkingDirectoryPath(), 'waslink.txt'); - const dest = path.join(repository.getWorkingDirectoryPath(), 'destination'); - await fs.writeFile(dest, 'asdf\n', 'utf8'); - await fs.symlink(dest, p); - - await repository.stageFiles(['waslink.txt', 'destination']); - await repository.commit('zero'); - - await fs.unlink(p); - - repository.refresh(); - const symlinkPatch = await repository.getFilePatchForPath('waslink.txt', {staged: true}); - const wrapper = shallow(buildApp({filePatch: symlinkPatch, relPath: 'waslink.txt', stagingStatus: 'staged'})); - - sinon.spy(repository, 'unstageFiles'); - - await wrapper.find('FilePatchView').prop('toggleSymlinkChange')(); - - assert.isTrue(repository.unstageFiles.calledWith(['waslink.txt'])); - }); - }); - } - - it('calls discardLines with selected rows', async function() { - const discardLines = sinon.spy(); - const wrapper = shallow(buildApp({discardLines})); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); - - await wrapper.find('FilePatchView').prop('discardRows')(); - - const lastArgs = discardLines.lastCall.args; - assert.strictEqual(lastArgs[0], filePatch); - assert.sameMembers(Array.from(lastArgs[1]), [1, 2]); - assert.strictEqual(lastArgs[2], repository); - }); - - it('calls discardLines with explicitly provided rows', async function() { - const discardLines = sinon.spy(); - const wrapper = shallow(buildApp({discardLines})); - wrapper.find('FilePatchView').prop('selectedRowsChanged')(new Set([1, 2])); - - await wrapper.find('FilePatchView').prop('discardRows')(new Set([4, 5]), 'hunk'); - - const lastArgs = discardLines.lastCall.args; - assert.strictEqual(lastArgs[0], filePatch); - assert.sameMembers(Array.from(lastArgs[1]), [4, 5]); - assert.strictEqual(lastArgs[2], repository); - - assert.sameMembers(Array.from(wrapper.find('FilePatchView').prop('selectedRows')), [4, 5]); - assert.strictEqual(wrapper.find('FilePatchView').prop('selectionMode'), 'hunk'); - }); -}); From 5e123b5c5d8657784d38f7986bd0bce1b8a5f8eb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 14 Nov 2018 13:08:59 -0500 Subject: [PATCH 1054/4053] Move test changes into MultiFilePatchController --- .../multi-file-patch-controller.test.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/controllers/multi-file-patch-controller.test.js b/test/controllers/multi-file-patch-controller.test.js index 4c601e76fc..14a1209a1e 100644 --- a/test/controllers/multi-file-patch-controller.test.js +++ b/test/controllers/multi-file-patch-controller.test.js @@ -4,6 +4,7 @@ import React from 'react'; import {shallow} from 'enzyme'; import MultiFilePatchController from '../../lib/controllers/multi-file-patch-controller'; +import MultiFilePatch from '../../lib/models/patch/multi-file-patch'; import * as reporterProxy from '../../lib/reporter-proxy'; import {multiFilePatchBuilder} from '../builder/patch'; import {cloneRepository, buildRepository} from '../helpers'; @@ -154,14 +155,22 @@ describe('MultiFilePatchController', function() { const wrapper = shallow(buildApp({stagingStatus: 'unstaged'})); assert.strictEqual(await wrapper.find('MultiFilePatchView').prop('toggleFile')(filePatch), 'staged'); - wrapper.setProps({stagingStatus: 'staged'}); + // No-op assert.isNull(await wrapper.find('MultiFilePatchView').prop('toggleFile')(filePatch)); - const promise = wrapper.instance().patchChangePromise; + // Simulate an identical patch arriving too soon wrapper.setProps({multiFilePatch: multiFilePatch.clone()}); + + // Still a no-op + assert.isNull(await wrapper.find('MultiFilePatchView').prop('toggleFile')(filePatch)); + + // Simulate updated patch arrival + const promise = wrapper.instance().patchChangePromise; + wrapper.setProps({multiFilePatch: new MultiFilePatch({})}); await promise; - assert.strictEqual(await wrapper.find('MultiFilePatchView').prop('toggleFile')(filePatch), 'unstaged'); + // Performs an operation again + assert.strictEqual(await wrapper.find('MultiFilePatchView').prop('toggleFile')(filePatch), 'staged'); }); }); From 0adec32e79d245fdd9f7cff46852306dc6af1742 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 14 Nov 2018 13:24:00 -0800 Subject: [PATCH 1055/4053] Fix error where getFilePath is not a function on a CommitPreviewItem Just early return if it's not a ChangedFileItem, to avoid doing the check in the first place. Co-Authored-By: Katrina Uychaco --- lib/views/staging-view.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index fb4587ea0a..8629ff8953 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -762,11 +762,13 @@ export default class StagingView extends React.Component { const pendingItem = pane.getPendingItem(); if (!pendingItem || !pendingItem.getRealItem) { return false; } const realItem = pendingItem.getRealItem(); - const isDiffViewItem = realItem instanceof ChangedFileItem; + if (!(realItem instanceof ChangedFileItem)) { + return false; + } // We only want to update pending diff views for currently active repo const isInActiveRepo = realItem.getWorkingDirectory() === this.props.workingDirectoryPath; const isStale = !this.changedFileExists(realItem.getFilePath(), realItem.getStagingStatus()); - return isDiffViewItem && isInActiveRepo && isStale; + return isInActiveRepo && isStale; }); } From 3a62b791f3d9bbf54da2b4044558a0386ad5554a Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 14 Nov 2018 13:33:55 -0800 Subject: [PATCH 1056/4053] props only work if you pass them to child components Co-Authored-By: Katrina Uychaco --- lib/containers/changed-file-container.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/containers/changed-file-container.js b/lib/containers/changed-file-container.js index eb6a8a7f8a..d4661a8ac1 100644 --- a/lib/containers/changed-file-container.js +++ b/lib/containers/changed-file-container.js @@ -52,10 +52,13 @@ export default class ChangedFileContainer extends React.Component { return ; } + const {multiFilePatch, hasUndoHistory, isPartiallyStaged} = data; + return ( ); From bc58a8ab66867a585696366ca6d7d34bd82d9052 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 14 Nov 2018 13:45:17 -0800 Subject: [PATCH 1057/4053] :art: Make UI more consistent Co-Authored-By: Katrina Uychaco --- lib/views/file-patch-header-view.js | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 7b37cf810f..a01e01c5f1 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import RefHolder from '../models/ref-holder'; -import Tooltip from '../atom/tooltip'; export default class FilePatchHeaderView extends React.Component { static propTypes = { @@ -76,11 +75,11 @@ export default class FilePatchHeaderView extends React.Component { const attrs = this.props.stagingStatus === 'unstaged' ? { iconClass: 'icon-tasklist', - tooltipText: 'View staged changes', + buttonText: 'View Staged', } : { iconClass: 'icon-list-unordered', - tooltipText: 'View unstaged changes', + buttonText: 'View Unstaged', }; return ( @@ -88,19 +87,15 @@ export default class FilePatchHeaderView extends React.Component { ); } renderOpenFileButton() { - let buttonText = 'Jump to file'; + let buttonText = 'Jump To File'; if (this.props.hasMultipleFileSelections) { buttonText += 's'; } @@ -113,11 +108,6 @@ export default class FilePatchHeaderView extends React.Component { onClick={this.props.openFile}> {buttonText} - ); } From 3e1e971e1e5104122bc8ce8eac25de375624d539 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 14 Nov 2018 23:08:30 +0000 Subject: [PATCH 1058/4053] fix(package): update event-kit to version 2.5.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6dc1565e29..47f7a81085 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "classnames": "2.2.6", "compare-sets": "1.0.1", "dugite": "^1.79.0", - "event-kit": "2.5.2", + "event-kit": "2.5.3", "fs-extra": "4.0.3", "graphql": "0.13.2", "keytar": "4.2.1", From d48ad877d7a62fff7a310c0d096e0215b782b199 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 14 Nov 2018 23:08:33 +0000 Subject: [PATCH 1059/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 06e0f6da07..faf19375d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3197,9 +3197,9 @@ } }, "event-kit": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.5.2.tgz", - "integrity": "sha512-1w3eEk45CstP8gzQtJdxhNl6kmvT+3dsGMK31VX7Wmt1/hlwS+s2yJY7SeVRhyhhx2W8neomdBfSZ9ACJ9eNeg==" + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.5.3.tgz", + "integrity": "sha512-b7Qi1JNzY4BfAYfnIRanLk0DOD1gdkWHT4GISIn8Q2tAf3LpU8SP2CMwWaq40imYoKWbtN4ZhbSRxvsnikooZQ==" }, "execa": { "version": "0.7.0", From f49d4db56c62e88c20374b748733778dcce5433d Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 14 Nov 2018 16:10:43 -0800 Subject: [PATCH 1060/4053] we don't need to test for no stinkin tooltips --- test/views/file-patch-header-view.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/views/file-patch-header-view.test.js b/test/views/file-patch-header-view.test.js index 93d647748b..d626ecfee0 100644 --- a/test/views/file-patch-header-view.test.js +++ b/test/views/file-patch-header-view.test.js @@ -76,8 +76,6 @@ describe('FilePatchHeaderView', function() { wrapper.find(`button.${buttonClass}`).simulate('click'); assert.isTrue(diveIntoMirrorPatch.called, `${buttonClass} click did nothing`); - - assert.isTrue(wrapper.find('Tooltip').someWhere(n => n.prop('title') === tooltip)); }; } @@ -128,12 +126,12 @@ describe('FilePatchHeaderView', function() { it('is singular when selections exist within a single file patch', function() { const wrapper = shallow(buildApp({hasMultipleFileSelections: false})); - assert.strictEqual(wrapper.find('button.icon-code').text(), 'Jump to file'); + assert.strictEqual(wrapper.find('button.icon-code').text(), 'Jump To File'); }); it('is plural when selections exist within multiple file patches', function() { const wrapper = shallow(buildApp({hasMultipleFileSelections: true})); - assert.strictEqual(wrapper.find('button.icon-code').text(), 'Jump to files'); + assert.strictEqual(wrapper.find('button.icon-code').text(), 'Jump To Files'); }); }); From 1fc35c4c3546ccf3cbaabec6a1d8b5d2b1d6e96d Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 14 Nov 2018 18:12:36 -0800 Subject: [PATCH 1061/4053] Explicitly only render Undo Discard button if ChangedFileItem --- lib/items/changed-file-item.js | 1 + lib/items/commit-preview-item.js | 1 + lib/views/file-patch-header-view.js | 10 ++++++++-- lib/views/multi-file-patch-view.js | 1 + 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/items/changed-file-item.js b/lib/items/changed-file-item.js index 57ddc3302b..bff241401c 100644 --- a/lib/items/changed-file-item.js +++ b/lib/items/changed-file-item.js @@ -78,6 +78,7 @@ export default class ChangedFileItem extends React.Component { return ( Date: Wed, 14 Nov 2018 18:14:38 -0800 Subject: [PATCH 1062/4053] Explicitly only render Undo Discard button if ChangedFileItem --- lib/views/file-patch-header-view.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 20bb1e2e27..5d7bbd6eda 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -62,15 +62,16 @@ export default class FilePatchHeaderView extends React.Component { } renderUndoDiscardButton() { - if (!this.props.hasUndoHistory || this.props.stagingStatus !== 'unstaged') { + const unstagedChangedFileItem = this.props.itemType === ChangedFileItem && this.props.stagingStatus === 'unstaged'; + if (unstagedChangedFileItem && this.props.hasUndoHistory) { + return ( + + ); + } else { return null; } - - return ( - - ); } renderMirrorPatchButton() { From 1f685962622e29bdb37da2253a29161e3a2c0bed Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 14 Nov 2018 18:15:12 -0800 Subject: [PATCH 1063/4053] Add `itemType` prop types for :shirt: --- lib/views/file-patch-header-view.js | 2 ++ lib/views/multi-file-patch-view.js | 3 +++ 2 files changed, 5 insertions(+) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 5d7bbd6eda..348d5efbd2 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -21,6 +21,8 @@ export default class FilePatchHeaderView extends React.Component { diveIntoMirrorPatch: PropTypes.func.isRequired, openFile: PropTypes.func.isRequired, toggleFile: PropTypes.func.isRequired, + + itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem]).isRequired, }; constructor(props) { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 14e2385b5b..4f0fdb015c 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -16,6 +16,8 @@ import FilePatchHeaderView from './file-patch-header-view'; import FilePatchMetaView from './file-patch-meta-view'; import HunkHeaderView from './hunk-header-view'; import RefHolder from '../models/ref-holder'; +import ChangedFileItem from '../items/changed-file-item'; +import CommitPreviewItem from '../items/commit-preview-item'; const executableText = { 100644: 'non executable', @@ -57,6 +59,7 @@ export default class MultiFilePatchView extends React.Component { discardRows: PropTypes.func.isRequired, refInitialFocus: RefHolderPropType, + itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem]).isRequired, } static defaultProps = { From 4b53ba75dfcd3d7091baf76b9efa11a20c680bd6 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 14 Nov 2018 18:41:28 -0800 Subject: [PATCH 1064/4053] Make basename bold in FilePatchHeaderView --- lib/views/file-patch-header-view.js | 25 +++++++++++++++++++++++-- styles/file-patch-view.less | 4 ++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 348d5efbd2..b157e70230 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -1,3 +1,5 @@ +import path from 'path'; + import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; @@ -46,9 +48,28 @@ export default class FilePatchHeaderView extends React.Component { renderTitle() { if (this.props.itemType === ChangedFileItem) { const status = this.props.stagingStatus; - return `${status[0].toUpperCase()}${status.slice(1)} Changes for ${this.props.relPath}`; + return ( + {status[0].toUpperCase()}{status.slice(1)} Changes for {this.renderPath()} + ); } else { - return this.props.relPath; + return this.renderPath(); + } + } + + renderPath() { + const dirname = path.dirname(this.props.relPath); + const basename = path.basename(this.props.relPath); + + if (dirname === '.') { + return {basename}; + } else { + // TODO: double check that the forward-slash works cross-platform... + // iirc git always uses `/` for paths, even on Windows + return ( + + {dirname}/{basename} + + ); } } diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 63a42c5ef1..f20069c650 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -261,3 +261,7 @@ } } } + +.gitub-FilePatchHeaderView-basename { + font-weight: bold; +} From ea07e4d8aa976777a6e508d40462fee3207dab05 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 14 Nov 2018 18:50:30 -0800 Subject: [PATCH 1065/4053] Fix tests to include itemType --- test/views/file-patch-header-view.test.js | 30 +++++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/test/views/file-patch-header-view.test.js b/test/views/file-patch-header-view.test.js index d626ecfee0..8e8d514e66 100644 --- a/test/views/file-patch-header-view.test.js +++ b/test/views/file-patch-header-view.test.js @@ -2,6 +2,7 @@ import React from 'react'; import {shallow} from 'enzyme'; import FilePatchHeaderView from '../../lib/views/file-patch-header-view'; +import ChangedFileItem from '../../lib/items/changed-file-item'; describe('FilePatchHeaderView', function() { let atomEnv; @@ -17,6 +18,7 @@ describe('FilePatchHeaderView', function() { function buildApp(overrideProps = {}) { return ( Date: Wed, 14 Nov 2018 19:54:15 -0800 Subject: [PATCH 1066/4053] Update docs/react-component-atlas.md Co-Authored-By: annthurium --- docs/react-component-atlas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md index 81c80e0a43..7341d3cec0 100644 --- a/docs/react-component-atlas.md +++ b/docs/react-component-atlas.md @@ -75,7 +75,7 @@ This is a high-level overview of the structure of the React component tree that > > [``](/lib/controllers/multi-file-patch-controller.js) > > [``](/lib/views/multi-file-patch-view.js) > > -> > The workspace-center pane that appears when looking at the staged or unstaged changes associated with one or more files. +> > The workspace-center pane that appears when looking at the staged or unstaged changes associated with a file. > > > > > From 989c90ddf4f39725299f23a5ee2c323149ac838b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 15 Nov 2018 11:16:00 -0500 Subject: [PATCH 1067/4053] Update docs/react-component-atlas.md Co-Authored-By: smashwilson --- docs/react-component-atlas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md index 7341d3cec0..d40cd142de 100644 --- a/docs/react-component-atlas.md +++ b/docs/react-component-atlas.md @@ -37,7 +37,7 @@ This is a high-level overview of the structure of the React component tree that > > > [``](/lig/items/commit-preview-item.js) > > > [``](/lib/containers/commit-preview-container.js) > > > -> > > Allows users to view all unstaged commits in one pane. +> > > The workspace-center pane that appears when looking at _all_ the staged changes that will be going into the next commit. > > > > > > [``](/lib/views/remote-selector-view.js) > > > From a16e7f2f049caaa98d7ae914059138b9416372c1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 11:38:34 -0500 Subject: [PATCH 1068/4053] :world: CommitPreviewItem is beneath RootController, not GitHubTabItem Separate MultiFilePatchController and MultiFilePatchView and replicate them --- docs/react-component-atlas.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md index d40cd142de..a4ae15f3e2 100644 --- a/docs/react-component-atlas.md +++ b/docs/react-component-atlas.md @@ -34,11 +34,6 @@ This is a high-level overview of the structure of the React component tree that > > > > The "GitHub" tab that appears in the right dock (by default). > > -> > > [``](/lig/items/commit-preview-item.js) -> > > [``](/lib/containers/commit-preview-container.js) -> > > -> > > The workspace-center pane that appears when looking at _all_ the staged changes that will be going into the next commit. -> > > > > > [``](/lib/views/remote-selector-view.js) > > > > > > Shown if the current repository has more than one remote that's identified as a github.com remote. @@ -71,13 +66,24 @@ This is a high-level overview of the structure of the React component tree that > > > > > > > > > > > > Render a list of issueish results as rows within the result list of a specific search. > -> > [ ``](/lib/containers/changed-file-container.js) -> > [``](/lib/controllers/multi-file-patch-controller.js) -> > [``](/lib/views/multi-file-patch-view.js) +> > [``](/lib/items/changed-file-item.js) +> > [``](/lib/containers/changed-file-container.js) > > > > The workspace-center pane that appears when looking at the staged or unstaged changes associated with a file. > > +> > > [``](/lib/controllers/multi-file-patch-controller.js) +> > > [``](/lib/views/multi-file-patch-view.js) +> > > +> > > Render a sequence of git-generated file patches within a TextEditor, using decorations to include contextually +> > > relevant controls. +> +> > [``](/lig/items/commit-preview-item.js) +> > [``](/lib/containers/commit-preview-container.js) +> > +> > The workspace-center pane that appears when looking at _all_ the staged changes that will be going into the next commit. > > +> > > [``](/lib/controllers/multi-file-patch-controller.js) +> > > [``](/lib/views/multi-file-patch-view.js) > > > [``](/lib/items/issueish-detail-item.js) > > [``](/lib/containers/issueish-detail-container.js) From 64c9608d0eb54cc245a4ab4162a94162b05c47a6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 11:40:13 -0500 Subject: [PATCH 1069/4053] Use an object spread for that data why not --- lib/containers/changed-file-container.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/containers/changed-file-container.js b/lib/containers/changed-file-container.js index d4661a8ac1..1c3ed45a70 100644 --- a/lib/containers/changed-file-container.js +++ b/lib/containers/changed-file-container.js @@ -52,13 +52,9 @@ export default class ChangedFileContainer extends React.Component { return ; } - const {multiFilePatch, hasUndoHistory, isPartiallyStaged} = data; - return ( ); From 9aa1a6051ed01dc5cb24d6610f9c6e7855b94ae9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 11:57:07 -0500 Subject: [PATCH 1070/4053] Pass a Decoration's className to the created DOM element --- lib/atom/decoration.js | 3 ++- test/atom/decoration.test.js | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/atom/decoration.js b/lib/atom/decoration.js index 841b8bd22e..9e3049f1a2 100644 --- a/lib/atom/decoration.js +++ b/lib/atom/decoration.js @@ -2,6 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import {Disposable} from 'event-kit'; +import cx from 'classnames'; import {createItem, autobind, extractProps} from '../helpers'; import {RefHolderPropType} from '../prop-types'; @@ -49,7 +50,7 @@ class BareDecoration extends React.Component { this.item = null; if (['gutter', 'overlay', 'block'].includes(this.props.type)) { this.domNode = document.createElement('div'); - this.domNode.className = 'react-atom-decoration'; + this.domNode.className = cx('react-atom-decoration', this.props.className); } } diff --git a/test/atom/decoration.test.js b/test/atom/decoration.test.js index dcde9c4412..2e9fd4fdcd 100644 --- a/test/atom/decoration.test.js +++ b/test/atom/decoration.test.js @@ -44,7 +44,7 @@ describe('Decoration', function() { it('creates a block decoration', function() { const app = ( - +
This is a subtree
@@ -55,7 +55,9 @@ describe('Decoration', function() { const args = editor.decorateMarker.firstCall.args; assert.equal(args[0], marker); assert.equal(args[1].type, 'block'); - const child = args[1].item.getElement().firstElementChild; + const element = args[1].item.getElement(); + assert.strictEqual(element.className, 'react-atom-decoration parent'); + const child = element.firstElementChild; assert.equal(child.className, 'decoration-subtree'); assert.equal(child.textContent, 'This is a subtree'); }); From 3d63ab0e548c2f3813a2ca17ccab5910142ee531 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 11:57:27 -0500 Subject: [PATCH 1071/4053] Set and style a more specific CSS class for control blocks --- lib/views/multi-file-patch-view.js | 4 ++-- styles/file-patch-view.less | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 4f0fdb015c..6990f98ba6 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -306,7 +306,7 @@ export default class MultiFilePatchView extends React.Component { return ( - + {this.renderExecutableModeChangeMeta(filePatch)} {this.renderSymlinkChangeMeta(filePatch)} @@ -484,7 +484,7 @@ export default class MultiFilePatchView extends React.Component { return ( - + Date: Thu, 15 Nov 2018 12:24:27 -0500 Subject: [PATCH 1072/4053] File.modes constants for well-known file modes --- lib/models/patch/file.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/models/patch/file.js b/lib/models/patch/file.js index c9017ec56f..0c893ca4f1 100644 --- a/lib/models/patch/file.js +++ b/lib/models/patch/file.js @@ -1,4 +1,18 @@ export default class File { + static modes = { + // Non-executable, non-symlink + NORMAL: '100644', + + // +x bit set + EXECUTABLE: '100755', + + // Soft link to another filesystem location + SYMLINK: '120000', + + // Submodule mount point + GITLINK: '160000', + } + constructor({path, mode, symlink}) { this.path = path; this.mode = mode; @@ -18,15 +32,15 @@ export default class File { } isSymlink() { - return this.getMode() === '120000'; + return this.getMode() === this.constructor.modes.SYMLINK; } isRegularFile() { - return this.getMode() === '100644' || this.getMode() === '100755'; + return this.getMode() === this.constructor.modes.NORMAL || this.getMode() === this.constructor.modes.EXECUTABLE; } isExecutable() { - return this.getMode() === '100755'; + return this.getMode() === this.constructor.modes.EXECUTABLE; } isPresent() { From 8fc267fd4e1b05668ecda84c38574937ad42adbd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 12:24:41 -0500 Subject: [PATCH 1073/4053] Use File mode constants where possible --- lib/git-shell-out-strategy.js | 15 ++++++++------- lib/models/patch/builder.js | 6 +++--- lib/views/multi-file-patch-view.js | 5 +++-- test/builder/patch.js | 4 ++-- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 3f152c5f58..19b422f502 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -20,6 +20,7 @@ import { normalizeGitHelperPath, toNativePathSep, toGitPathSep, LINE_ENDING_REGEX, CO_AUTHOR_REGEX, } from './helpers'; import GitTimingsView from './views/git-timings-view'; +import File from './models/patch/file'; import WorkerManager from './worker-manager'; const MAX_STATUS_OUTPUT_LENGTH = 1024 * 1024 * 10; @@ -640,12 +641,12 @@ export default class GitShellOutStrategy { let mode; let realpath; if (executable) { - mode = '100755'; + mode = File.modes.EXECUTABLE; } else if (symlink) { - mode = '120000'; + mode = File.modes.SYMLINK; realpath = await fs.realpath(absPath); } else { - mode = '100644'; + mode = File.modes.NORMAL; } rawDiffs.push(buildAddedFilePatch(filePath, binary ? null : contents, mode, realpath)); @@ -1061,11 +1062,11 @@ export default class GitShellOutStrategy { const executable = await isFileExecutable(path.join(this.workingDir, filePath)); const symlink = await isFileSymlink(path.join(this.workingDir, filePath)); if (executable) { - return '100755'; + return File.modes.EXECUTABLE; } else if (symlink) { - return '120000'; + return File.modes.SYMLINK; } else { - return '100644'; + return File.modes.NORMAL; } } } @@ -1080,7 +1081,7 @@ function buildAddedFilePatch(filePath, contents, mode, realpath) { if (contents) { const noNewLine = contents[contents.length - 1] !== '\n'; let lines; - if (mode === '120000') { + if (mode === File.modes.SYMLINK) { lines = [`+${toGitPathSep(realpath)}`, '\\ No newline at end of file']; } else { lines = contents.trim().split(LINE_ENDING_REGEX).map(line => `+${line}`); diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 061745f443..aea6acae74 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -68,8 +68,8 @@ function emptyDiffFilePatch() { } function singleDiffFilePatch(diff, layeredBuffer) { - const wasSymlink = diff.oldMode === '120000'; - const isSymlink = diff.newMode === '120000'; + const wasSymlink = diff.oldMode === File.modes.SYMLINK; + const isSymlink = diff.newMode === File.modes.SYMLINK; const [hunks, patchMarker] = buildHunks(diff, layeredBuffer); @@ -97,7 +97,7 @@ function singleDiffFilePatch(diff, layeredBuffer) { function dualDiffFilePatch(diff1, diff2, layeredBuffer) { let modeChangeDiff, contentChangeDiff; - if (diff1.oldMode === '120000' || diff1.newMode === '120000') { + if (diff1.oldMode === File.modes.SYMLINK || diff1.newMode === File.modes.SYMLINK) { modeChangeDiff = diff1; contentChangeDiff = diff2; } else { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 6990f98ba6..47f41dcaeb 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -18,10 +18,11 @@ import HunkHeaderView from './hunk-header-view'; import RefHolder from '../models/ref-holder'; import ChangedFileItem from '../items/changed-file-item'; import CommitPreviewItem from '../items/commit-preview-item'; +import File from '../models/patch/file'; const executableText = { - 100644: 'non executable', - 100755: 'executable', + [File.modes.NORMAL]: 'non executable', + [File.modes.EXECUTABLE]: 'executable', }; const NBSP_CHARACTER = '\u00a0'; diff --git a/test/builder/patch.js b/test/builder/patch.js index ee863bc262..18ff5067b5 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -82,7 +82,7 @@ class FilePatchBuilder { constructor(layeredBuffer = null) { this.layeredBuffer = layeredBuffer; - this.oldFile = new File({path: 'file', mode: '100644'}); + this.oldFile = new File({path: 'file', mode: File.modes.NORMAL}); this.newFile = null; this.patchBuilder = new PatchBuilder(this.layeredBuffer); @@ -143,7 +143,7 @@ class FilePatchBuilder { class FileBuilder { constructor() { this._path = 'file.txt'; - this._mode = '100644'; + this._mode = File.modes.NORMAL; this._symlink = null; } From 3f3ae216f27a6aa0955c4cbb374190c15583bad7 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 15 Nov 2018 09:35:56 -0800 Subject: [PATCH 1074/4053] Add Release Notes section --- PULL_REQUEST_TEMPLATE.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index a51a32ed8a..c6d8eb658e 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -54,6 +54,24 @@ Write "N/A" if not applicable. +### Release Notes + + + ### User Experience Research (Optional) From 0ec9039246f5a820f94b4ff01ea501fc8b0bdf28 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 12:37:24 -0500 Subject: [PATCH 1075/4053] Go into more detail on why that opener is necessary --- test/controllers/commit-controller.test.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/controllers/commit-controller.test.js b/test/controllers/commit-controller.test.js index 9a35a22eb6..19b307dc1a 100644 --- a/test/controllers/commit-controller.test.js +++ b/test/controllers/commit-controller.test.js @@ -30,7 +30,14 @@ describe('CommitController', function() { const noop = () => {}; const store = new UserStore({config}); - // Ensure the Workspace doesn't mangle atom-github://... URIs + // Ensure the Workspace doesn't mangle atom-github://... URIs. + // If you don't have an opener registered for a non-standard URI protocol, the Workspace coerces it into a file URI + // and tries to open it with a TextEditor. In the process, the URI gets mangled: + // + // atom.workspace.open('atom-github://unknown/whatever').then(item => console.log(item.getURI())) + // > 'atom-github:/unknown/whatever' + // + // Adding an opener that creates fake items prevents it from doing this and keeps the URIs unchanged. const pattern = new URIPattern(CommitPreviewItem.uriPattern); workspace.addOpener(uri => { if (pattern.matches(uri).ok()) { From 707f13646b99533707dd6f7f5b62f4e9ac831e38 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 15 Nov 2018 09:57:39 -0800 Subject: [PATCH 1076/4053] tweak symlink changes styles - The meta title text was weirdly big. Now it's not. - The meta title text should render underneath the file name, to make it clear which file it belongs to. --- lib/views/multi-file-patch-view.js | 2 +- styles/file-patch-view.less | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 47f41dcaeb..1155432903 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -310,7 +310,6 @@ export default class MultiFilePatchView extends React.Component { {this.renderExecutableModeChangeMeta(filePatch)} - {this.renderSymlinkChangeMeta(filePatch)} this.didOpenFile({selectedFilePatch: filePatch})} toggleFile={() => this.props.toggleFile(filePatch)} /> + {this.renderSymlinkChangeMeta(filePatch)} diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 037b2b5bc7..3b4fec51bb 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -112,14 +112,14 @@ &-metaHeader { display: flex; align-items: center; - padding: @component-padding; + padding: @component-padding / 2; background-color: @background-color-highlight; } &-metaTitle { flex: 1; margin: 0; - font-size: 1.25em; + font-size: 1.0em; line-height: 1.5; overflow: hidden; text-overflow: ellipsis; From 43c16bf54f5ffbe14a3e6595e718b60bf1acc75e Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 15 Nov 2018 10:12:19 -0800 Subject: [PATCH 1077/4053] executable mode change styling tweaks Executable mode changes should also go under the FilePatchView title so it's clear which file the mode change belongs to. --- lib/views/multi-file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 1155432903..f94658c2bf 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -309,7 +309,6 @@ export default class MultiFilePatchView extends React.Component { - {this.renderExecutableModeChangeMeta(filePatch)} this.didOpenFile({selectedFilePatch: filePatch})} toggleFile={() => this.props.toggleFile(filePatch)} /> + {this.renderExecutableModeChangeMeta(filePatch)} {this.renderSymlinkChangeMeta(filePatch)} From 04ed3c888c9dcbf7274b10ee561c4607285a2b0e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 15 Nov 2018 13:03:23 -0800 Subject: [PATCH 1078/4053] :fire: unused cache key for stagedChangesSinceParentCommit --- lib/models/repository-states/present.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 22a803c9e9..7ef7879840 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -120,8 +120,6 @@ export default class Present extends State { if (filePathEndsWith(fullPath, '.git', 'index')) { keys.add(Keys.stagedChanges); - // todo: stagedChangesSinceParentCommit appears to be dead code and can be removed - keys.add(Keys.stagedChangesSinceParentCommit); keys.add(Keys.filePatch.all); keys.add(Keys.index.all); keys.add(Keys.statusBundle); @@ -348,7 +346,6 @@ export default class Present extends State { () => [ Keys.statusBundle, Keys.stagedChanges, - Keys.stagedChangesSinceParentCommit, Keys.filePatch.all, Keys.index.all, ], @@ -371,7 +368,6 @@ export default class Present extends State { return this.invalidate( () => [ Keys.statusBundle, - Keys.stagedChangesSinceParentCommit, Keys.stagedChanges, ...Keys.filePatch.eachWithFileOpts([filePath], [{staged: false}, {staged: true}]), Keys.index.oneWith(filePath), @@ -386,7 +382,6 @@ export default class Present extends State { return this.invalidate( () => [ Keys.stagedChanges, - Keys.stagedChangesSinceParentCommit, Keys.lastCommit, Keys.recentCommits, Keys.authors, @@ -408,7 +403,6 @@ export default class Present extends State { () => [ Keys.statusBundle, Keys.stagedChanges, - Keys.stagedChangesSinceParentCommit, ...paths.map(fileName => Keys.index.oneWith(fileName)), ...Keys.filePatch.eachWithFileOpts(paths, [{staged: true}]), ], @@ -421,7 +415,6 @@ export default class Present extends State { undoLastCommit() { return this.invalidate( () => [ - Keys.stagedChangesSinceParentCommit, Keys.lastCommit, Keys.recentCommits, Keys.authors, @@ -1036,8 +1029,6 @@ const Keys = { stagedChanges: new CacheKey('staged-changes'), - stagedChangesSinceParentCommit: new CacheKey('staged-changes-since-parent-commit'), - filePatch: { _optKey: ({staged}) => { return staged ? 's' : 'u'; @@ -1116,12 +1107,10 @@ const Keys = { ...Keys.filePatch.eachWithFileOpts(fileNames, [{staged: true}]), ...fileNames.map(Keys.index.oneWith), Keys.stagedChanges, - Keys.stagedChangesSinceParentCommit, ], headOperationKeys: () => [ ...Keys.filePatch.eachWithOpts({staged: true}), - Keys.stagedChangesSinceParentCommit, Keys.lastCommit, Keys.recentCommits, Keys.authors, From b48fcebf3c066256cd9d94aaf473e6bed172e99f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 19:33:54 -0500 Subject: [PATCH 1079/4053] Use `path.sep` to join dirname and basename --- lib/views/file-patch-header-view.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index b157e70230..8f7db7d8e2 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -63,11 +63,9 @@ export default class FilePatchHeaderView extends React.Component { if (dirname === '.') { return {basename}; } else { - // TODO: double check that the forward-slash works cross-platform... - // iirc git always uses `/` for paths, even on Windows return ( - {dirname}/{basename} + {dirname}{path.sep}{basename} ); } From da9fe4ce6e2ff69e152d9e60722ea42f6fdf53f5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 22:11:35 -0500 Subject: [PATCH 1080/4053] Avoid double "\\ No newline at end of file" --- lib/git-shell-out-strategy.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 19b422f502..25d0122a32 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -1079,11 +1079,13 @@ export default class GitShellOutStrategy { function buildAddedFilePatch(filePath, contents, mode, realpath) { const hunks = []; if (contents) { - const noNewLine = contents[contents.length - 1] !== '\n'; + let noNewLine; let lines; if (mode === File.modes.SYMLINK) { + noNewLine = false; lines = [`+${toGitPathSep(realpath)}`, '\\ No newline at end of file']; } else { + noNewLine = contents[contents.length - 1] !== '\n'; lines = contents.trim().split(LINE_ENDING_REGEX).map(line => `+${line}`); } if (noNewLine) { lines.push('\\ No newline at end of file'); } From 6147c86a7f917c38b7798b09758cfd4f2916e0e2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 22:11:45 -0500 Subject: [PATCH 1081/4053] Skip filesystem events with no path --- lib/models/repository-states/present.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 7ef7879840..cc3ef01db9 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -184,6 +184,10 @@ export default class Present extends State { for (let i = 0; i < events.length; i++) { const event = events[i]; + if (!event.path) { + continue; + } + if (filePathEndsWith(event.path, '.git', 'MERGE_HEAD')) { if (event.action === 'created') { if (this.isCommitMessageClean()) { From f6f56d684ab5580c4a7b775fb6b45f48a4b3388c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 22:12:38 -0500 Subject: [PATCH 1082/4053] Remove unnecessary --- lib/views/multi-file-patch-view.js | 39 ++++++++++++++---------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index f94658c2bf..65dd925e12 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -308,27 +308,24 @@ export default class MultiFilePatchView extends React.Component { - - - 0} - hasUndoHistory={this.props.hasUndoHistory} - hasMultipleFileSelections={this.props.hasMultipleFileSelections} - - tooltips={this.props.tooltips} - - undoLastDiscard={() => this.undoLastDiscardFromButton(filePatch)} - diveIntoMirrorPatch={() => this.props.diveIntoMirrorPatch(filePatch)} - openFile={() => this.didOpenFile({selectedFilePatch: filePatch})} - toggleFile={() => this.props.toggleFile(filePatch)} - /> - {this.renderExecutableModeChangeMeta(filePatch)} - {this.renderSymlinkChangeMeta(filePatch)} - + 0} + hasUndoHistory={this.props.hasUndoHistory} + hasMultipleFileSelections={this.props.hasMultipleFileSelections} + + tooltips={this.props.tooltips} + + undoLastDiscard={() => this.undoLastDiscardFromButton(filePatch)} + diveIntoMirrorPatch={() => this.props.diveIntoMirrorPatch(filePatch)} + openFile={() => this.didOpenFile({selectedFilePatch: filePatch})} + toggleFile={() => this.props.toggleFile(filePatch)} + /> + {this.renderSymlinkChangeMeta(filePatch)} + {this.renderExecutableModeChangeMeta(filePatch)} From b7049eef752c62fbe648e449fb809bd4dd811989 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 22:12:53 -0500 Subject: [PATCH 1083/4053] Wait for new unstaged patch to arrive --- test/integration/file-patch.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 9cd3dfdcde..7cd7d714a8 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -511,6 +511,12 @@ describe('integration: file patches', function() { getPatchEditor('unstaged', 'sample.js').selectAll(); getPatchItem('unstaged', 'sample.js').find('.github-HunkHeaderView-stageButton').simulate('click'); + await patchContent( + 'unstaged', 'sample.js', + [repoPath('target.txt'), 'selected'], + [' No newline at end of file'], + ); + assert.isTrue(getPatchItem('unstaged', 'sample.js').find('.github-FilePatchView-metaTitle').exists()); await clickFileInGitTab('staged', 'sample.js'); From 83ccf8f19993dea56fcd93f8565e0de729a7f31f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 15 Nov 2018 22:39:30 -0500 Subject: [PATCH 1084/4053] Adapt the expected path separator to the current platform --- test/views/file-patch-header-view.test.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/views/file-patch-header-view.test.js b/test/views/file-patch-header-view.test.js index 8e8d514e66..6dae9a60a1 100644 --- a/test/views/file-patch-header-view.test.js +++ b/test/views/file-patch-header-view.test.js @@ -1,10 +1,12 @@ import React from 'react'; import {shallow} from 'enzyme'; +import path from 'path'; import FilePatchHeaderView from '../../lib/views/file-patch-header-view'; import ChangedFileItem from '../../lib/items/changed-file-item'; describe('FilePatchHeaderView', function() { + const relPath = path.join('dir', 'a.txt'); let atomEnv; beforeEach(function() { @@ -19,7 +21,7 @@ describe('FilePatchHeaderView', function() { return ( Date: Thu, 15 Nov 2018 22:42:54 -0500 Subject: [PATCH 1085/4053] Default itemType to avoid the PropTypes warning --- test/views/file-patch-header-view.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/views/file-patch-header-view.test.js b/test/views/file-patch-header-view.test.js index 6dae9a60a1..2d4e0acb65 100644 --- a/test/views/file-patch-header-view.test.js +++ b/test/views/file-patch-header-view.test.js @@ -4,6 +4,7 @@ import path from 'path'; import FilePatchHeaderView from '../../lib/views/file-patch-header-view'; import ChangedFileItem from '../../lib/items/changed-file-item'; +import CommitPreviewItem from '../../lib/items/commit-preview-item'; describe('FilePatchHeaderView', function() { const relPath = path.join('dir', 'a.txt'); @@ -20,7 +21,7 @@ describe('FilePatchHeaderView', function() { function buildApp(overrideProps = {}) { return ( Date: Thu, 15 Nov 2018 16:35:03 -0800 Subject: [PATCH 1086/4053] Workspace item watcher only cares about active items So we can drop the option, and rename the class --- lib/controllers/commit-controller.js | 1 - lib/watch-workspace-item.js | 7 +++---- test/watch-workspace-item.test.js | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index f74e936503..94175cbe66 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -60,7 +60,6 @@ export default class CommitController extends React.Component { CommitPreviewItem.buildURI(this.props.repository.getWorkingDirectoryPath()), this, 'commitPreviewActive', - {active: true}, ); this.subscriptions.add(this.previewWatcher); } diff --git a/lib/watch-workspace-item.js b/lib/watch-workspace-item.js index fafe8028a8..bff7714121 100644 --- a/lib/watch-workspace-item.js +++ b/lib/watch-workspace-item.js @@ -2,13 +2,12 @@ import {CompositeDisposable} from 'atom'; import URIPattern from './atom/uri-pattern'; -class ActiveItemWatcher { - constructor(workspace, pattern, component, stateKey, opts) { +class ItemWatcher { + constructor(workspace, pattern, component, stateKey) { this.workspace = workspace; this.pattern = pattern instanceof URIPattern ? pattern : new URIPattern(pattern); this.component = component; this.stateKey = stateKey; - this.opts = opts; this.activeItem = this.isActiveItem(); this.subs = new CompositeDisposable(); @@ -67,7 +66,7 @@ class ActiveItemWatcher { } export function watchWorkspaceItem(workspace, pattern, component, stateKey) { - return new ActiveItemWatcher(workspace, pattern, component, stateKey) + return new ItemWatcher(workspace, pattern, component, stateKey) .setInitialState() .subscribeToWorkspace(); } diff --git a/test/watch-workspace-item.test.js b/test/watch-workspace-item.test.js index f233f0ca40..1df9ca25c9 100644 --- a/test/watch-workspace-item.test.js +++ b/test/watch-workspace-item.test.js @@ -67,14 +67,14 @@ describe('watchWorkspaceItem', function() { assert.isTrue(component.state.theKey); }); - it('is true when the pane is open and active in any pane', async function() { + it.only('is true when the pane is open and active in any pane', async function() { await workspace.open('atom-github://some-item', {location: 'right'}); await workspace.open('atom-github://nonmatching'); assert.strictEqual(workspace.getRightDock().getActivePaneItem().getURI(), 'atom-github://some-item'); assert.strictEqual(workspace.getActivePaneItem().getURI(), 'atom-github://nonmatching'); - sub = watchWorkspaceItem(workspace, 'atom-github://some-item', component, 'someKey', {active: true}); + sub = watchWorkspaceItem(workspace, 'atom-github://some-item', component, 'someKey'); assert.isTrue(component.state.someKey); }); From 098edb405c3c5e49bc047d5f370331d25f4fca5e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 15 Nov 2018 16:58:55 -0800 Subject: [PATCH 1087/4053] :fire: props that aren't used in `CommitPreviewItem` --- lib/controllers/root-controller.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 6fd7dbb3ac..e1f73588de 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -355,9 +355,6 @@ export default class RootController extends React.Component { keymaps={this.props.keymaps} tooltips={this.props.tooltips} config={this.props.config} - discardLines={this.discardLines} - undoLastDiscard={this.undoLastDiscard} - surfaceFileAtPath={this.surfaceFromFileAtPath} /> )} From 83622791175023b81df331138fb8988df3e0a9b9 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 15 Nov 2018 16:59:24 -0800 Subject: [PATCH 1088/4053] :fire: mysterious handleClick prop in MultiFilePatchController --- lib/controllers/multi-file-patch-controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 6df6e91821..d5d0c25425 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -25,7 +25,6 @@ export default class MultiFilePatchController extends React.Component { discardLines: PropTypes.func, undoLastDiscard: PropTypes.func, surfaceFileAtPath: PropTypes.func, - handleClick: PropTypes.func, } constructor(props) { From 7eb7db03a8263440df2521e920d34bb19e82700c Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 15 Nov 2018 18:28:52 -0800 Subject: [PATCH 1089/4053] Invalidate `stagedChanges` key when undoing last commit or head moves --- lib/models/repository-states/present.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index cc3ef01db9..a9f9691a2a 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -419,6 +419,7 @@ export default class Present extends State { undoLastCommit() { return this.invalidate( () => [ + Keys.stagedChanges, Keys.lastCommit, Keys.recentCommits, Keys.authors, @@ -1115,6 +1116,7 @@ const Keys = { headOperationKeys: () => [ ...Keys.filePatch.eachWithOpts({staged: true}), + Keys.stagedChanges, Keys.lastCommit, Keys.recentCommits, Keys.authors, From cd7003add3c5cfc11d55162822dbdd6158a3dad2 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 15 Nov 2018 19:39:11 -0800 Subject: [PATCH 1090/4053] :fire: useEditorAutoHeight since we are now using a single editor --- lib/views/multi-file-patch-view.js | 6 ------ test/views/multi-file-patch-view.test.js | 9 --------- 2 files changed, 15 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 65dd925e12..2cc6f06acc 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -39,7 +39,6 @@ export default class MultiFilePatchView extends React.Component { hasMultipleFileSelections: PropTypes.bool.isRequired, repository: PropTypes.object.isRequired, hasUndoHistory: PropTypes.bool, - useEditorAutoHeight: PropTypes.bool, workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, @@ -63,10 +62,6 @@ export default class MultiFilePatchView extends React.Component { itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem]).isRequired, } - static defaultProps = { - useEditorAutoHeight: false, - } - constructor(props) { super(props); autobind( @@ -236,7 +231,6 @@ export default class MultiFilePatchView extends React.Component { buffer={this.props.multiFilePatch.getBuffer()} lineNumberGutterVisible={false} autoWidth={false} - autoHeight={this.props.useEditorAutoHeight} readOnly={true} softWrapped={true} diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 021a1b055b..11cf5037c2 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -137,15 +137,6 @@ describe('MultiFilePatchView', function() { assert.strictEqual(editor.instance().getModel().getText(), filePatches.getBuffer().getText()); }); - it('enables autoHeight on the editor when requested', function() { - const wrapper = mount(buildApp({useEditorAutoHeight: true})); - - assert.isTrue(wrapper.find('AtomTextEditor').prop('autoHeight')); - - wrapper.setProps({useEditorAutoHeight: false}); - assert.isFalse(wrapper.find('AtomTextEditor').prop('autoHeight')); - }); - it('sets the root class when in hunk selection mode', function() { const wrapper = shallow(buildApp({selectionMode: 'line'})); assert.isFalse(wrapper.find('.github-FilePatchView--hunkMode').exists()); From 218d6cbb8aa9599f47db2432197f24f05afd25bb Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 15 Nov 2018 20:02:22 -0800 Subject: [PATCH 1091/4053] Actually we want autoHeight set to false on AtomTextEditor --- lib/views/multi-file-patch-view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 2cc6f06acc..16399a227d 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -231,6 +231,7 @@ export default class MultiFilePatchView extends React.Component { buffer={this.props.multiFilePatch.getBuffer()} lineNumberGutterVisible={false} autoWidth={false} + autoHeight={false} readOnly={true} softWrapped={true} From 1c1b2d2595e154fa521fe797069eb413276a9978 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 15 Nov 2018 21:13:08 -0800 Subject: [PATCH 1092/4053] :fire: .only --- test/watch-workspace-item.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/watch-workspace-item.test.js b/test/watch-workspace-item.test.js index 1df9ca25c9..0d63a4c95d 100644 --- a/test/watch-workspace-item.test.js +++ b/test/watch-workspace-item.test.js @@ -67,7 +67,7 @@ describe('watchWorkspaceItem', function() { assert.isTrue(component.state.theKey); }); - it.only('is true when the pane is open and active in any pane', async function() { + it('is true when the pane is open and active in any pane', async function() { await workspace.open('atom-github://some-item', {location: 'right'}); await workspace.open('atom-github://nonmatching'); From 3b5d0307d921a1fcb2e364f992145f01fad3c38a Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 16 Nov 2018 15:48:57 +0100 Subject: [PATCH 1093/4053] =?UTF-8?q?=F0=9F=93=9D=20comment=20for=20`inter?= =?UTF-8?q?sectRows`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/patch/region.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index f08901772f..3afaef844d 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -25,6 +25,16 @@ class Region { return this.getRange().intersectsRow(row); } + /* + * intersectRows breaks a Region into runs of rows that are included in + * rowSet and rows that are not. For example: + * @this Region row 10-20 + * @param rowSet row 11, 12, 13, 17, 19 + * @param includeGaps true (whether the result will include gaps or not) + * @return an array of regions like this: + * (10, gap = true) (11, 12, 13, gap = false) (14, 15, 16, gap = true) + * (17, gap = false) (18, gap = true) (19, gap = false) (20, gap = true) + */ intersectRows(rowSet, includeGaps) { const intersections = []; let withinIntersection = false; From d599b3c15da09e20d5fefcba7a777dcdf90f6d21 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 16 Nov 2018 08:57:48 -0800 Subject: [PATCH 1094/4053] :fire: unnecessary button class name `btn-secondary` does nothing --- lib/views/commit-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index fdfa143ef4..de83bee13f 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -164,7 +164,7 @@ export default class CommitView extends React.Component {
diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 7f3f9f21ac..3de411746a 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -98,10 +98,11 @@ } // Meta section + // Used for mode changes &-meta { - padding: @component-padding; - border-bottom: 1px solid @base-border-color; + font-family: @font-family; + padding-top: @component-padding; } &-metaContainer { @@ -112,15 +113,14 @@ &-metaHeader { display: flex; align-items: center; - padding: @component-padding / 2; - background-color: @background-color-highlight; + font-size: .9em; + border-bottom: 1px solid @base-border-color; } &-metaTitle { flex: 1; margin: 0; - font-size: 1.0em; - line-height: 1.5; + padding-left: @component-padding; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -128,14 +128,25 @@ &-metaControls { margin-left: @component-padding; + } - .btn { - &.icon-move-up::before, - &.icon-move-down::before { - font-size: 1em; - margin-right: .5em; - vertical-align: baseline; - } + &-metaButton { + line-height: 1.9; // Magic number to match the hunk height + padding-left: @component-padding; + padding-right: @component-padding; + font-family: @font-family; + border: none; + border-left: 1px solid @base-border-color; + background-color: transparent; + cursor: default; + &:hover { background-color: mix(@syntax-text-color, @syntax-background-color, 4%); } + &:active { background-color: mix(@syntax-text-color, @syntax-background-color, 2%); } + + &.icon-move-up::before, + &.icon-move-down::before { + font-size: 1em; + margin-right: .25em; + vertical-align: baseline; } } From 3fd23efa033b4edb80603c38d8488c2c3bd1c052 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 13:35:25 -0800 Subject: [PATCH 1139/4053] create scaffolding for splitting `IssueishDetailView` --- lib/controllers/issueish-detail-controller.js | 31 +++++++++++++------ lib/views/issue-detail-view.js | 8 +++++ lib/views/pr-detail-view.js | 8 +++++ 3 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 lib/views/issue-detail-view.js create mode 100644 lib/views/pr-detail-view.js diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 2db4fba498..65f537a3c2 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -6,6 +6,8 @@ import {BranchSetPropType, RemoteSetPropType} from '../prop-types'; import {GitError} from '../git-shell-out-strategy'; import EnableableOperation from '../models/enableable-operation'; import IssueishDetailView, {checkoutStates} from '../views/issueish-detail-view'; +import {PullRequestDetailView} from '../views/pr-detail-view'; +import {IssueDetailView} from '../views/issue-detail-view'; import {incrementCounter} from '../reporter-proxy'; export class BareIssueishDetailController extends React.Component { @@ -78,15 +80,26 @@ export class BareIssueishDetailController extends React.Component { } this.checkoutOp = this.nextCheckoutOp(); - - return ( - - ); + const isPr = repository.issueish.__typename === 'PullRequest'; + if (isPr) { + return ( + + ); + } else { + return ( + + ); + } } nextCheckoutOp() { diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js new file mode 100644 index 0000000000..b91d340189 --- /dev/null +++ b/lib/views/issue-detail-view.js @@ -0,0 +1,8 @@ +import React from 'react'; + +export class IssueDetailView extends React.Component { + + render() { + return (
hi I'm an issue detail view
); + } +} diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js new file mode 100644 index 0000000000..0c4c69ec41 --- /dev/null +++ b/lib/views/pr-detail-view.js @@ -0,0 +1,8 @@ +import React from 'react'; + +export class PullRequestDetailView extends React.Component { + + render() { + return (
hi I'm a pull request detail view
); + } +} From 8e1a10404998fead3195991d9d5d6c9370a3a73d Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 14:10:15 -0800 Subject: [PATCH 1140/4053] better to copy everything over to both new components and then remove unneded code --- lib/controllers/issueish-detail-controller.js | 4 +- lib/views/issue-detail-view.js | 429 +++++++++++++++++- lib/views/pr-detail-view.js | 429 +++++++++++++++++- 3 files changed, 854 insertions(+), 8 deletions(-) diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 65f537a3c2..006534a501 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -6,8 +6,8 @@ import {BranchSetPropType, RemoteSetPropType} from '../prop-types'; import {GitError} from '../git-shell-out-strategy'; import EnableableOperation from '../models/enableable-operation'; import IssueishDetailView, {checkoutStates} from '../views/issueish-detail-view'; -import {PullRequestDetailView} from '../views/pr-detail-view'; -import {IssueDetailView} from '../views/issue-detail-view'; +import PullRequestDetailView from '../views/pr-detail-view'; +import IssueDetailView from '../views/issue-detail-view'; import {incrementCounter} from '../reporter-proxy'; export class BareIssueishDetailController extends React.Component { diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index b91d340189..f97cae1165 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -1,8 +1,431 @@ -import React from 'react'; +import React, {Fragment} from 'react'; +import {graphql, createRefetchContainer} from 'react-relay'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; -export class IssueDetailView extends React.Component { +import IssueTimelineController from '../controllers/issue-timeline-controller'; +import PrTimelineContainer from '../controllers/pr-timeline-controller'; +import PrStatusesView from '../views/pr-statuses-view'; +import Octicon from '../atom/octicon'; +import IssueishBadge from '../views/issueish-badge'; +import GithubDotcomMarkdown from '../views/github-dotcom-markdown'; +import PeriodicRefresher from '../periodic-refresher'; +import {EnableableOperationPropType} from '../prop-types'; +import {autobind} from '../helpers'; +import {addEvent} from '../reporter-proxy'; +import PrCommitsView from '../views/pr-commits-view'; + +const reactionTypeToEmoji = { + THUMBS_UP: '👍', + THUMBS_DOWN: '👎', + LAUGH: '😆', + HOORAY: '🎉', + CONFUSED: '😕', + HEART: '❤️', +}; + +function createCheckoutState(name) { + return function(cases) { + return cases[name] || cases.default; + }; +} + +export const checkoutStates = { + HIDDEN: createCheckoutState('hidden'), + DISABLED: createCheckoutState('disabled'), + BUSY: createCheckoutState('busy'), + CURRENT: createCheckoutState('current'), +}; + +export class BareIssueDetailView extends React.Component { + static propTypes = { + relay: PropTypes.shape({ + refetch: PropTypes.func.isRequired, + }), + switchToIssueish: PropTypes.func.isRequired, + checkoutOp: EnableableOperationPropType.isRequired, + repository: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + owner: PropTypes.shape({ + login: PropTypes.string, + }), + }), + issueish: PropTypes.shape({ + __typename: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, + title: PropTypes.string, + countedCommits: PropTypes.shape({ + totalCount: PropTypes.number.isRequired, + }).isRequired, + isCrossRepository: PropTypes.bool, + changedFiles: PropTypes.number.isRequired, + url: PropTypes.string.isRequired, + bodyHTML: PropTypes.string, + number: PropTypes.number, + state: PropTypes.oneOf([ + 'OPEN', 'CLOSED', 'MERGED', + ]).isRequired, + author: PropTypes.shape({ + login: PropTypes.string.isRequired, + avatarUrl: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + }).isRequired, + reactionGroups: PropTypes.arrayOf( + PropTypes.shape({ + content: PropTypes.string.isRequired, + users: PropTypes.shape({ + totalCount: PropTypes.number.isRequired, + }).isRequired, + }), + ).isRequired, + }).isRequired, + } + + state = { + refreshing: false, + } + + constructor(props) { + super(props); + autobind(this, 'handleRefreshClick', 'refresh', 'renderEmojiReactions', 'renderIssueBody', 'renderPullRequestBody'); + } + + componentDidMount() { + this.refresher = new PeriodicRefresher(BareIssueDetailView, { + interval: () => 5 * 60 * 1000, + getCurrentId: () => this.props.issueish.id, + refresh: this.refresh, + minimumIntervalPerId: 2 * 60 * 1000, + }); + // auto-refresh disabled for now until pagination is handled + // this.refresher.start(); + } + + componentWillUnmount() { + this.refresher.destroy(); + } + + renderPrMetadata(issueish, repo) { + return ( +
+ + {issueish.author.login} wants to merge{' '} + {issueish.countedCommits.totalCount} commits and{' '} + {issueish.changedFiles} changed files into{' '} + {issueish.isCrossRepository ? + `${repo.owner.login}/${issueish.baseRefName}` : issueish.baseRefName} from{' '} + {issueish.isCrossRepository ? + `${issueish.author.login}/${issueish.headRefName}` : issueish.headRefName} + +
+ ); + } + + renderIssueBody(issueish, childProps) { + return ( + + No description provided.'} + switchToIssueish={this.props.switchToIssueish} + /> + {this.renderEmojiReactions(issueish)} + + + ); + } + + renderPullRequestBody(issueish, childProps) { + return ( + + + + Overview + + + Build Status + + + + Commits + + + {/* 'Files Changed' and 'Reviews' tabs to be added in the future. */} + + {/* overview */} + + No description provided.'} + switchToIssueish={this.props.switchToIssueish} + /> + {this.renderEmojiReactions(issueish)} + + + + + {/* build status */} + +
+ +
+
+ + {/* commits */} + + + +
+ ); + } + + renderEmojiReactions(issueish) { + return ( +
+ {issueish.reactionGroups.map(group => ( + group.users.totalCount > 0 + ? + {reactionTypeToEmoji[group.content]}   {group.users.totalCount} + + : null + ))} +
+ ); + } render() { - return (
hi I'm an issue detail view
); + const repo = this.props.repository; + const issueish = this.props.issueish; + const isPr = issueish.__typename === 'PullRequest'; + const childProps = { + issue: issueish.__typename === 'Issue' ? issueish : null, + pullRequest: issueish.__typename === 'PullRequest' ? issueish : null, + }; + // todo(tt, 9/2018): it could enhance readability to extract header rendering into + // 2 functions: one for rendering an issue header, and one for rendering a pr header. + // however, the tradeoff there is having some repetitive code. + return ( +
+
+ +
+
+ +
+ +
+
+ {isPr && this.renderPrMetadata(issueish, repo)} +
+ +
+ {this.renderCheckoutButton()} +
+
+
+ {isPr ? this.renderPullRequestBody(issueish, childProps) : this.renderIssueBody(issueish, childProps)} + + + +
+
+ ); + } + + renderCheckoutButton() { + const {checkoutOp} = this.props; + let extraClass = null; + let buttonText = 'Checkout'; + let buttonTitle = null; + + if (!checkoutOp.isEnabled()) { + buttonTitle = checkoutOp.getMessage(); + const reason = checkoutOp.why(); + if (reason({hidden: true, default: false})) { + return null; + } + + buttonText = reason({ + current: 'Checked out', + default: 'Checkout', + }); + + extraClass = 'github-IssueishDetailView-checkoutButton--' + reason({ + disabled: 'disabled', + busy: 'busy', + current: 'current', + }); + } + + const classNames = cx('btn', 'btn-primary', 'github-IssueishDetailView-checkoutButton', extraClass); + return ( + + ); + } + + handleRefreshClick(e) { + e.preventDefault(); + this.refresher.refreshNow(true); + } + + recordOpenInBrowserEvent() { + addEvent('open-issueish-in-browser', {package: 'github', from: 'issueish-header'}); + } + + refresh() { + if (this.state.refreshing) { + return; + } + + this.setState({refreshing: true}); + this.props.relay.refetch({ + repoId: this.props.repository.id, + issueishId: this.props.issueish.id, + timelineCount: 100, + timelineCursor: null, + commitCount: 100, + commitCursor: null, + }, null, () => { + this.setState({refreshing: false}); + }, {force: true}); } } + +export default createRefetchContainer(BareIssueDetailView, { + repository: graphql` + fragment issueishDetailView_repository on Repository { + id + name + owner { + login + } + } + `, + + issueish: graphql` + fragment issueishDetailView_issueish on IssueOrPullRequest + @argumentDefinitions( + timelineCount: {type: "Int!"}, + timelineCursor: {type: "String"}, + commitCount: {type: "Int!"}, + commitCursor: {type: "String"}, + ) { + __typename + + ... on Node { + id + } + + ... on Issue { + state number title bodyHTML + author { + login avatarUrl + ... on User { url } + ... on Bot { url } + } + + ...issueTimelineController_issue @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) + } + + ... on PullRequest { + isCrossRepository + changedFiles + ...prCommitsView_pullRequest @arguments(commitCount: $commitCount, commitCursor: $commitCursor) + countedCommits: commits { + totalCount + } + ...prStatusesView_pullRequest + state number title bodyHTML baseRefName headRefName + author { + login avatarUrl + ... on User { url } + ... on Bot { url } + } + + ...prTimelineController_pullRequest @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) + } + + ... on UniformResourceLocatable { url } + + ... on Reactable { + reactionGroups { + content users { totalCount } + } + } + } + `, +}, graphql` + query issueishDetailViewRefetchQuery + ( + $repoId: ID!, + $issueishId: ID!, + $timelineCount: Int!, + $timelineCursor: String, + $commitCount: Int!, + $commitCursor: String + ) { + repository:node(id: $repoId) { + ...issueishDetailView_repository @arguments( + timelineCount: $timelineCount, + timelineCursor: $timelineCursor + ) + } + + issueish:node(id: $issueishId) { + ...issueishDetailView_issueish @arguments( + timelineCount: $timelineCount, + timelineCursor: $timelineCursor, + commitCount: $commitCount, + commitCursor: $commitCursor + ) + } + } +`); diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 0c4c69ec41..468b166426 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -1,8 +1,431 @@ -import React from 'react'; +import React, {Fragment} from 'react'; +import {graphql, createRefetchContainer} from 'react-relay'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; -export class PullRequestDetailView extends React.Component { +import IssueTimelineController from '../controllers/issue-timeline-controller'; +import PrTimelineContainer from '../controllers/pr-timeline-controller'; +import PrStatusesView from '../views/pr-statuses-view'; +import Octicon from '../atom/octicon'; +import IssueishBadge from '../views/issueish-badge'; +import GithubDotcomMarkdown from '../views/github-dotcom-markdown'; +import PeriodicRefresher from '../periodic-refresher'; +import {EnableableOperationPropType} from '../prop-types'; +import {autobind} from '../helpers'; +import {addEvent} from '../reporter-proxy'; +import PrCommitsView from '../views/pr-commits-view'; + +const reactionTypeToEmoji = { + THUMBS_UP: '👍', + THUMBS_DOWN: '👎', + LAUGH: '😆', + HOORAY: '🎉', + CONFUSED: '😕', + HEART: '❤️', +}; + +function createCheckoutState(name) { + return function(cases) { + return cases[name] || cases.default; + }; +} + +export const checkoutStates = { + HIDDEN: createCheckoutState('hidden'), + DISABLED: createCheckoutState('disabled'), + BUSY: createCheckoutState('busy'), + CURRENT: createCheckoutState('current'), +}; + +export class BarePullRequestDetailView extends React.Component { + static propTypes = { + relay: PropTypes.shape({ + refetch: PropTypes.func.isRequired, + }), + switchToIssueish: PropTypes.func.isRequired, + checkoutOp: EnableableOperationPropType.isRequired, + repository: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + owner: PropTypes.shape({ + login: PropTypes.string, + }), + }), + issueish: PropTypes.shape({ + __typename: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, + title: PropTypes.string, + countedCommits: PropTypes.shape({ + totalCount: PropTypes.number.isRequired, + }).isRequired, + isCrossRepository: PropTypes.bool, + changedFiles: PropTypes.number.isRequired, + url: PropTypes.string.isRequired, + bodyHTML: PropTypes.string, + number: PropTypes.number, + state: PropTypes.oneOf([ + 'OPEN', 'CLOSED', 'MERGED', + ]).isRequired, + author: PropTypes.shape({ + login: PropTypes.string.isRequired, + avatarUrl: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + }).isRequired, + reactionGroups: PropTypes.arrayOf( + PropTypes.shape({ + content: PropTypes.string.isRequired, + users: PropTypes.shape({ + totalCount: PropTypes.number.isRequired, + }).isRequired, + }), + ).isRequired, + }).isRequired, + } + + state = { + refreshing: false, + } + + constructor(props) { + super(props); + autobind(this, 'handleRefreshClick', 'refresh', 'renderEmojiReactions', 'renderIssueBody', 'renderPullRequestBody'); + } + + componentDidMount() { + this.refresher = new PeriodicRefresher(BarePullRequestDetailView, { + interval: () => 5 * 60 * 1000, + getCurrentId: () => this.props.issueish.id, + refresh: this.refresh, + minimumIntervalPerId: 2 * 60 * 1000, + }); + // auto-refresh disabled for now until pagination is handled + // this.refresher.start(); + } + + componentWillUnmount() { + this.refresher.destroy(); + } + + renderPrMetadata(issueish, repo) { + return ( +
+ + {issueish.author.login} wants to merge{' '} + {issueish.countedCommits.totalCount} commits and{' '} + {issueish.changedFiles} changed files into{' '} + {issueish.isCrossRepository ? + `${repo.owner.login}/${issueish.baseRefName}` : issueish.baseRefName} from{' '} + {issueish.isCrossRepository ? + `${issueish.author.login}/${issueish.headRefName}` : issueish.headRefName} + +
+ ); + } + + renderIssueBody(issueish, childProps) { + return ( + + No description provided.'} + switchToIssueish={this.props.switchToIssueish} + /> + {this.renderEmojiReactions(issueish)} + + + ); + } + + renderPullRequestBody(issueish, childProps) { + return ( + + + + Overview + + + Build Status + + + + Commits + + + {/* 'Files Changed' and 'Reviews' tabs to be added in the future. */} + + {/* overview */} + + No description provided.'} + switchToIssueish={this.props.switchToIssueish} + /> + {this.renderEmojiReactions(issueish)} + + + + + {/* build status */} + +
+ +
+
+ + {/* commits */} + + + +
+ ); + } + + renderEmojiReactions(issueish) { + return ( +
+ {issueish.reactionGroups.map(group => ( + group.users.totalCount > 0 + ? + {reactionTypeToEmoji[group.content]}   {group.users.totalCount} + + : null + ))} +
+ ); + } render() { - return (
hi I'm a pull request detail view
); + const repo = this.props.repository; + const issueish = this.props.issueish; + const isPr = issueish.__typename === 'PullRequest'; + const childProps = { + issue: issueish.__typename === 'Issue' ? issueish : null, + pullRequest: issueish.__typename === 'PullRequest' ? issueish : null, + }; + // todo(tt, 9/2018): it could enhance readability to extract header rendering into + // 2 functions: one for rendering an issue header, and one for rendering a pr header. + // however, the tradeoff there is having some repetitive code. + return ( +
+
+ +
+
+ +
+ +
+
+ {isPr && this.renderPrMetadata(issueish, repo)} +
+ +
+ {this.renderCheckoutButton()} +
+
+
+ {isPr ? this.renderPullRequestBody(issueish, childProps) : this.renderIssueBody(issueish, childProps)} + + + +
+
+ ); + } + + renderCheckoutButton() { + const {checkoutOp} = this.props; + let extraClass = null; + let buttonText = 'Checkout'; + let buttonTitle = null; + + if (!checkoutOp.isEnabled()) { + buttonTitle = checkoutOp.getMessage(); + const reason = checkoutOp.why(); + if (reason({hidden: true, default: false})) { + return null; + } + + buttonText = reason({ + current: 'Checked out', + default: 'Checkout', + }); + + extraClass = 'github-IssueishDetailView-checkoutButton--' + reason({ + disabled: 'disabled', + busy: 'busy', + current: 'current', + }); + } + + const classNames = cx('btn', 'btn-primary', 'github-IssueishDetailView-checkoutButton', extraClass); + return ( + + ); + } + + handleRefreshClick(e) { + e.preventDefault(); + this.refresher.refreshNow(true); + } + + recordOpenInBrowserEvent() { + addEvent('open-issueish-in-browser', {package: 'github', from: 'issueish-header'}); + } + + refresh() { + if (this.state.refreshing) { + return; + } + + this.setState({refreshing: true}); + this.props.relay.refetch({ + repoId: this.props.repository.id, + issueishId: this.props.issueish.id, + timelineCount: 100, + timelineCursor: null, + commitCount: 100, + commitCursor: null, + }, null, () => { + this.setState({refreshing: false}); + }, {force: true}); } } + +export default createRefetchContainer(BarePullRequestDetailView, { + repository: graphql` + fragment issueishDetailView_repository on Repository { + id + name + owner { + login + } + } + `, + + issueish: graphql` + fragment issueishDetailView_issueish on IssueOrPullRequest + @argumentDefinitions( + timelineCount: {type: "Int!"}, + timelineCursor: {type: "String"}, + commitCount: {type: "Int!"}, + commitCursor: {type: "String"}, + ) { + __typename + + ... on Node { + id + } + + ... on Issue { + state number title bodyHTML + author { + login avatarUrl + ... on User { url } + ... on Bot { url } + } + + ...issueTimelineController_issue @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) + } + + ... on PullRequest { + isCrossRepository + changedFiles + ...prCommitsView_pullRequest @arguments(commitCount: $commitCount, commitCursor: $commitCursor) + countedCommits: commits { + totalCount + } + ...prStatusesView_pullRequest + state number title bodyHTML baseRefName headRefName + author { + login avatarUrl + ... on User { url } + ... on Bot { url } + } + + ...prTimelineController_pullRequest @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) + } + + ... on UniformResourceLocatable { url } + + ... on Reactable { + reactionGroups { + content users { totalCount } + } + } + } + `, +}, graphql` + query issueishDetailViewRefetchQuery + ( + $repoId: ID!, + $issueishId: ID!, + $timelineCount: Int!, + $timelineCursor: String, + $commitCount: Int!, + $commitCursor: String + ) { + repository:node(id: $repoId) { + ...issueishDetailView_repository @arguments( + timelineCount: $timelineCount, + timelineCursor: $timelineCursor + ) + } + + issueish:node(id: $issueishId) { + ...issueishDetailView_issueish @arguments( + timelineCount: $timelineCount, + timelineCursor: $timelineCursor, + commitCount: $commitCount, + commitCursor: $commitCursor + ) + } + } +`); From 2a421bba2c6cf416c0e706f55d510184c46eb5cc Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 14:14:15 -0800 Subject: [PATCH 1141/4053] remove `isPr` variable from `PullRequestDetailView` --- lib/views/pr-detail-view.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 468b166426..2c5704acf6 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -208,7 +208,6 @@ export class BarePullRequestDetailView extends React.Component { render() { const repo = this.props.repository; const issueish = this.props.issueish; - const isPr = issueish.__typename === 'PullRequest'; const childProps = { issue: issueish.__typename === 'Issue' ? issueish : null, pullRequest: issueish.__typename === 'PullRequest' ? issueish : null, @@ -240,7 +239,7 @@ export class BarePullRequestDetailView extends React.Component { />
- {isPr && this.renderPrMetadata(issueish, repo)} + {this.renderPrMetadata(issueish, repo)}
{repo.owner.login}/{repo.name}#{issueish.number} - {isPr && + - } +
{this.renderCheckoutButton()}
- {isPr ? this.renderPullRequestBody(issueish, childProps) : this.renderIssueBody(issueish, childProps)} + {this.renderPullRequestBody(issueish, childProps)}
- {isPr ? this.renderPullRequestBody(issueish, childProps) : this.renderIssueBody(issueish, childProps)} + {this.renderIssueBody(issueish, childProps)}
Date: Tue, 20 Nov 2018 14:39:25 -0800 Subject: [PATCH 1145/4053] nuke more dead code from `IssueDetailView` --- lib/views/issue-detail-view.js | 73 +--------------------------------- 1 file changed, 1 insertion(+), 72 deletions(-) diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 0665b7d038..18afd1c6ac 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -2,11 +2,8 @@ import React, {Fragment} from 'react'; import {graphql, createRefetchContainer} from 'react-relay'; import PropTypes from 'prop-types'; import cx from 'classnames'; -import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; import IssueTimelineController from '../controllers/issue-timeline-controller'; -import PrTimelineContainer from '../controllers/pr-timeline-controller'; -import PrStatusesView from '../views/pr-statuses-view'; import Octicon from '../atom/octicon'; import IssueishBadge from '../views/issueish-badge'; import GithubDotcomMarkdown from '../views/github-dotcom-markdown'; @@ -14,7 +11,6 @@ import PeriodicRefresher from '../periodic-refresher'; import {EnableableOperationPropType} from '../prop-types'; import {autobind} from '../helpers'; import {addEvent} from '../reporter-proxy'; -import PrCommitsView from '../views/pr-commits-view'; const reactionTypeToEmoji = { THUMBS_UP: '👍', @@ -89,7 +85,7 @@ export class BareIssueDetailView extends React.Component { constructor(props) { super(props); - autobind(this, 'handleRefreshClick', 'refresh', 'renderEmojiReactions', 'renderIssueBody', 'renderPullRequestBody'); + autobind(this, 'handleRefreshClick', 'refresh', 'renderEmojiReactions', 'renderIssueBody'); } componentDidMount() { @@ -107,25 +103,6 @@ export class BareIssueDetailView extends React.Component { this.refresher.destroy(); } - renderPrMetadata(issueish, repo) { - return ( -
- - {issueish.author.login} wants to merge{' '} - {issueish.countedCommits.totalCount} commits and{' '} - {issueish.changedFiles} changed files into{' '} - {issueish.isCrossRepository ? - `${repo.owner.login}/${issueish.baseRefName}` : issueish.baseRefName} from{' '} - {issueish.isCrossRepository ? - `${issueish.author.login}/${issueish.headRefName}` : issueish.headRefName} - -
- ); - } - renderIssueBody(issueish, childProps) { return ( @@ -142,54 +119,6 @@ export class BareIssueDetailView extends React.Component { ); } - renderPullRequestBody(issueish, childProps) { - return ( - - - - Overview - - - Build Status - - - - Commits - - - {/* 'Files Changed' and 'Reviews' tabs to be added in the future. */} - - {/* overview */} - - No description provided.'} - switchToIssueish={this.props.switchToIssueish} - /> - {this.renderEmojiReactions(issueish)} - - - - - {/* build status */} - -
- -
-
- - {/* commits */} - - - -
- ); - } - renderEmojiReactions(issueish) { return (
From 611d3146adbb3620c1ac329edf734e76c0cac466 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 14:43:35 -0800 Subject: [PATCH 1146/4053] remove checkout button from `IssueDetailView` you can't check out issues, dawg. --- lib/controllers/issueish-detail-controller.js | 1 - lib/views/issue-detail-view.js | 44 ------------------- 2 files changed, 45 deletions(-) diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 006534a501..1d1100cf9a 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -95,7 +95,6 @@ export class BareIssueishDetailController extends React.Component { ); diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 18afd1c6ac..ca73743c90 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -40,7 +40,6 @@ export class BareIssueDetailView extends React.Component { refetch: PropTypes.func.isRequired, }), switchToIssueish: PropTypes.func.isRequired, - checkoutOp: EnableableOperationPropType.isRequired, repository: PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, @@ -141,9 +140,6 @@ export class BareIssueDetailView extends React.Component { issue: issueish.__typename === 'Issue' ? issueish : null, pullRequest: issueish.__typename === 'PullRequest' ? issueish : null, }; - // todo(tt, 9/2018): it could enhance readability to extract header rendering into - // 2 functions: one for rendering an issue header, and one for rendering a pr header. - // however, the tradeoff there is having some repetitive code. return (
@@ -180,9 +176,6 @@ export class BareIssueDetailView extends React.Component { {repo.owner.login}/{repo.name}#{issueish.number}
-
- {this.renderCheckoutButton()} -
{this.renderIssueBody(issueish, childProps)} @@ -198,43 +191,6 @@ export class BareIssueDetailView extends React.Component { ); } - renderCheckoutButton() { - const {checkoutOp} = this.props; - let extraClass = null; - let buttonText = 'Checkout'; - let buttonTitle = null; - - if (!checkoutOp.isEnabled()) { - buttonTitle = checkoutOp.getMessage(); - const reason = checkoutOp.why(); - if (reason({hidden: true, default: false})) { - return null; - } - - buttonText = reason({ - current: 'Checked out', - default: 'Checkout', - }); - - extraClass = 'github-IssueishDetailView-checkoutButton--' + reason({ - disabled: 'disabled', - busy: 'busy', - current: 'current', - }); - } - - const classNames = cx('btn', 'btn-primary', 'github-IssueishDetailView-checkoutButton', extraClass); - return ( - - ); - } - handleRefreshClick(e) { e.preventDefault(); this.refresher.refreshNow(true); From d11355c89a3722ea7960082bac2f03c6f2c12a7d Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 14:48:24 -0800 Subject: [PATCH 1147/4053] dead code removal will continue until morale improves --- lib/views/issue-detail-view.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index ca73743c90..0e3d811b26 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -8,7 +8,6 @@ import Octicon from '../atom/octicon'; import IssueishBadge from '../views/issueish-badge'; import GithubDotcomMarkdown from '../views/github-dotcom-markdown'; import PeriodicRefresher from '../periodic-refresher'; -import {EnableableOperationPropType} from '../prop-types'; import {autobind} from '../helpers'; import {addEvent} from '../reporter-proxy'; @@ -21,19 +20,6 @@ const reactionTypeToEmoji = { HEART: '❤️', }; -function createCheckoutState(name) { - return function(cases) { - return cases[name] || cases.default; - }; -} - -export const checkoutStates = { - HIDDEN: createCheckoutState('hidden'), - DISABLED: createCheckoutState('disabled'), - BUSY: createCheckoutState('busy'), - CURRENT: createCheckoutState('current'), -}; - export class BareIssueDetailView extends React.Component { static propTypes = { relay: PropTypes.shape({ From c31094ff5844626be811f78e44c35c53de3bc085 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 14:52:34 -0800 Subject: [PATCH 1148/4053] remove some unused imports from `IssueishDetailController` --- lib/controllers/issueish-detail-controller.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 1d1100cf9a..845ea18486 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -5,8 +5,7 @@ import PropTypes from 'prop-types'; import {BranchSetPropType, RemoteSetPropType} from '../prop-types'; import {GitError} from '../git-shell-out-strategy'; import EnableableOperation from '../models/enableable-operation'; -import IssueishDetailView, {checkoutStates} from '../views/issueish-detail-view'; -import PullRequestDetailView from '../views/pr-detail-view'; +import PullRequestDetailView, {checkoutStates} from '../views/pr-detail-view'; import IssueDetailView from '../views/issue-detail-view'; import {incrementCounter} from '../reporter-proxy'; From 303746e7f7fb6a2f6d4e6de165e62ead9221407d Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 15:17:56 -0800 Subject: [PATCH 1149/4053] make graphql happy --- .../issueishDetailContainerQuery.graphql.js | 79 +- ...eishDetailController_repository.graphql.js | 76 +- lib/controllers/issueish-detail-controller.js | 4 +- .../issueDetailViewRefetchQuery.graphql.js | 1579 +++++++++++++++++ .../issueDetailView_issueish.graphql.js | 322 ++++ .../issueDetailView_repository.graphql.js | 67 + .../prDetailViewRefetchQuery.graphql.js | 1579 +++++++++++++++++ .../prDetailView_issueish.graphql.js | 322 ++++ .../prDetailView_repository.graphql.js | 67 + lib/views/issue-detail-view.js | 11 +- lib/views/pr-detail-view.js | 10 +- 11 files changed, 4064 insertions(+), 52 deletions(-) create mode 100644 lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js create mode 100644 lib/views/__generated__/issueDetailView_issueish.graphql.js create mode 100644 lib/views/__generated__/issueDetailView_repository.graphql.js create mode 100644 lib/views/__generated__/prDetailViewRefetchQuery.graphql.js create mode 100644 lib/views/__generated__/prDetailView_issueish.graphql.js create mode 100644 lib/views/__generated__/prDetailView_repository.graphql.js diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 73d262cf8c..7fac7a736c 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash fe501fb51956752e2d45ff061c36622e + * @relayHash 547454dd6453e107bd844739888fae07 */ /* eslint-disable */ @@ -60,7 +60,7 @@ fragment issueishDetailController_repository_1mXVvq on Repository { ... on Issue { title number - ...issueishDetailView_issueish_4cAEh0 + ...issueDetailView_issueish_4cAEh0 } ... on PullRequest { title @@ -77,7 +77,7 @@ fragment issueishDetailController_repository_1mXVvq on Repository { sshUrl id } - ...issueishDetailView_issueish_4cAEh0 + ...prDetailView_issueish_4cAEh0 } ... on Node { id @@ -95,7 +95,76 @@ fragment issueishDetailView_repository on Repository { } } -fragment issueishDetailView_issueish_4cAEh0 on IssueOrPullRequest { +fragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest { + __typename + ... on Node { + id + } + ... on Issue { + state + number + title + bodyHTML + author { + __typename + login + avatarUrl + ... on User { + url + } + ... on Bot { + url + } + ... on Node { + id + } + } + ...issueTimelineController_issue_3D8CP9 + } + ... on PullRequest { + isCrossRepository + changedFiles + ...prCommitsView_pullRequest_38TpXw + countedCommits: commits { + totalCount + } + ...prStatusesView_pullRequest + state + number + title + bodyHTML + baseRefName + headRefName + author { + __typename + login + avatarUrl + ... on User { + url + } + ... on Bot { + url + } + ... on Node { + id + } + } + ...prTimelineController_pullRequest_3D8CP9 + } + ... on UniformResourceLocatable { + url + } + ... on Reactable { + reactionGroups { + content + users { + totalCount + } + } + } +} + +fragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest { __typename ... on Node { id @@ -997,7 +1066,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueishDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueishDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...issueishDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueishDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueishDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueishDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueishDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", diff --git a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js index 39335a7657..3efd3d66ff 100644 --- a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js +++ b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js @@ -8,8 +8,9 @@ /*:: import type { ConcreteFragment } from 'relay-runtime'; -type issueishDetailView_issueish$ref = any; +type issueDetailView_issueish$ref = any; type issueishDetailView_repository$ref = any; +type prDetailView_issueish$ref = any; import type { FragmentReference } from "relay-runtime"; declare export opaque type issueishDetailController_repository$ref: FragmentReference; export type issueishDetailController_repository = {| @@ -21,7 +22,7 @@ export type issueishDetailController_repository = {| +__typename: "Issue", +title: string, +number: number, - +$fragmentRefs: issueishDetailView_issueish$ref, + +$fragmentRefs: issueDetailView_issueish$ref, |} | {| +__typename: "PullRequest", +headRefName: string, @@ -33,6 +34,7 @@ export type issueishDetailController_repository = {| +url: any, +sshUrl: any, |}, + +$fragmentRefs: prDetailView_issueish$ref, |} | {| // This will never be '%other', but we need some // value in case none of the concrete values match. @@ -84,36 +86,32 @@ v3 = { "args": null, "storageKey": null }, -v4 = { - "kind": "FragmentSpread", - "name": "issueishDetailView_issueish", - "args": [ - { - "kind": "Variable", - "name": "commitCount", - "variableName": "commitCount", - "type": null - }, - { - "kind": "Variable", - "name": "commitCursor", - "variableName": "commitCursor", - "type": null - }, - { - "kind": "Variable", - "name": "timelineCount", - "variableName": "timelineCount", - "type": null - }, - { - "kind": "Variable", - "name": "timelineCursor", - "variableName": "timelineCursor", - "type": null - } - ] -}; +v4 = [ + { + "kind": "Variable", + "name": "commitCount", + "variableName": "commitCount", + "type": null + }, + { + "kind": "Variable", + "name": "commitCursor", + "variableName": "commitCursor", + "type": null + }, + { + "kind": "Variable", + "name": "timelineCount", + "variableName": "timelineCount", + "type": null + }, + { + "kind": "Variable", + "name": "timelineCursor", + "variableName": "timelineCursor", + "type": null + } +]; return { "kind": "Fragment", "name": "issueishDetailController_repository", @@ -222,7 +220,11 @@ return { } ] }, - v4 + { + "kind": "FragmentSpread", + "name": "prDetailView_issueish", + "args": v4 + } ] }, { @@ -231,7 +233,11 @@ return { "selections": [ v2, v3, - v4 + { + "kind": "FragmentSpread", + "name": "issueDetailView_issueish", + "args": v4 + } ] } ] @@ -240,5 +246,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '85f9f003c5256db379ff5b0bdf7794a4'; +(node/*: any*/).hash = 'ab44d7d5d91ffcd4d01a304fe8e33a2b'; module.exports = node; diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 845ea18486..ad038627eb 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -234,7 +234,7 @@ export default createFragmentContainer(BareIssueishDetailController, { ... on Issue { title number - ...issueishDetailView_issueish @arguments( + ...issueDetailView_issueish @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor, commitCount: $commitCount, @@ -253,7 +253,7 @@ export default createFragmentContainer(BareIssueishDetailController, { url sshUrl } - ...issueishDetailView_issueish @arguments( + ...prDetailView_issueish @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor, commitCount: $commitCount, diff --git a/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js new file mode 100644 index 0000000000..da470e3b7d --- /dev/null +++ b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js @@ -0,0 +1,1579 @@ +/** + * @flow + * @relayHash b15e84159f798cffa1d97aa4c9cb7ebc + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest } from 'relay-runtime'; +type issueDetailView_issueish$ref = any; +type issueDetailView_repository$ref = any; +export type issueDetailViewRefetchQueryVariables = {| + repoId: string, + issueishId: string, + timelineCount: number, + timelineCursor?: ?string, + commitCount: number, + commitCursor?: ?string, +|}; +export type issueDetailViewRefetchQueryResponse = {| + +repository: ?{| + +$fragmentRefs: issueDetailView_repository$ref + |}, + +issueish: ?{| + +$fragmentRefs: issueDetailView_issueish$ref + |}, +|}; +export type issueDetailViewRefetchQuery = {| + variables: issueDetailViewRefetchQueryVariables, + response: issueDetailViewRefetchQueryResponse, +|}; +*/ + + +/* +query issueDetailViewRefetchQuery( + $repoId: ID! + $issueishId: ID! + $timelineCount: Int! + $timelineCursor: String + $commitCount: Int! + $commitCursor: String +) { + repository: node(id: $repoId) { + __typename + ...issueDetailView_repository_3D8CP9 + id + } + issueish: node(id: $issueishId) { + __typename + ...issueDetailView_issueish_4cAEh0 + id + } +} + +fragment issueDetailView_repository_3D8CP9 on Repository { + id + name + owner { + __typename + login + id + } +} + +fragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest { + __typename + ... on Node { + id + } + ... on Issue { + state + number + title + bodyHTML + author { + __typename + login + avatarUrl + ... on User { + url + } + ... on Bot { + url + } + ... on Node { + id + } + } + ...issueTimelineController_issue_3D8CP9 + } + ... on PullRequest { + isCrossRepository + changedFiles + ...prCommitsView_pullRequest_38TpXw + countedCommits: commits { + totalCount + } + ...prStatusesView_pullRequest + state + number + title + bodyHTML + baseRefName + headRefName + author { + __typename + login + avatarUrl + ... on User { + url + } + ... on Bot { + url + } + ... on Node { + id + } + } + ...prTimelineController_pullRequest_3D8CP9 + } + ... on UniformResourceLocatable { + url + } + ... on Reactable { + reactionGroups { + content + users { + totalCount + } + } + } +} + +fragment issueTimelineController_issue_3D8CP9 on Issue { + url + timeline(first: $timelineCount, after: $timelineCursor) { + pageInfo { + endCursor + hasNextPage + } + edges { + cursor + node { + __typename + ...commitsView_nodes + ...issueCommentView_item + ...crossReferencedEventsView_nodes + ... on Node { + id + } + } + } + } +} + +fragment prCommitsView_pullRequest_38TpXw on PullRequest { + url + commits(first: $commitCount, after: $commitCursor) { + pageInfo { + endCursor + hasNextPage + } + edges { + cursor + node { + commit { + id + ...prCommitView_item + } + id + __typename + } + } + } +} + +fragment prStatusesView_pullRequest on PullRequest { + id + recentCommits: commits(last: 1) { + edges { + node { + commit { + status { + state + contexts { + id + state + ...prStatusContextView_context + } + id + } + id + } + id + } + } + } +} + +fragment prTimelineController_pullRequest_3D8CP9 on PullRequest { + url + ...headRefForcePushedEventView_issueish + timeline(first: $timelineCount, after: $timelineCursor) { + pageInfo { + endCursor + hasNextPage + } + edges { + cursor + node { + __typename + ...commitsView_nodes + ...issueCommentView_item + ...mergedEventView_item + ...headRefForcePushedEventView_item + ...commitCommentThreadView_item + ...crossReferencedEventsView_nodes + ... on Node { + id + } + } + } + } +} + +fragment headRefForcePushedEventView_issueish on PullRequest { + headRefName + headRepositoryOwner { + __typename + login + id + } + repository { + owner { + __typename + login + id + } + id + } +} + +fragment commitsView_nodes on Commit { + id + author { + name + user { + login + id + } + } + ...commitView_item +} + +fragment issueCommentView_item on IssueComment { + author { + __typename + avatarUrl + login + ... on Node { + id + } + } + bodyHTML + createdAt + url +} + +fragment mergedEventView_item on MergedEvent { + actor { + __typename + avatarUrl + login + ... on Node { + id + } + } + commit { + oid + id + } + mergeRefName + createdAt +} + +fragment headRefForcePushedEventView_item on HeadRefForcePushedEvent { + actor { + __typename + avatarUrl + login + ... on Node { + id + } + } + beforeCommit { + oid + id + } + afterCommit { + oid + id + } + createdAt +} + +fragment commitCommentThreadView_item on CommitCommentThread { + commit { + oid + id + } + comments(first: 100) { + edges { + node { + id + ...commitCommentView_item + } + } + } +} + +fragment crossReferencedEventsView_nodes on CrossReferencedEvent { + id + referencedAt + isCrossRepository + actor { + __typename + login + avatarUrl + ... on Node { + id + } + } + source { + __typename + ... on RepositoryNode { + repository { + name + owner { + __typename + login + id + } + id + } + } + ... on Node { + id + } + } + ...crossReferencedEventView_item +} + +fragment crossReferencedEventView_item on CrossReferencedEvent { + id + isCrossRepository + source { + __typename + ... on Issue { + number + title + url + issueState: state + } + ... on PullRequest { + number + title + url + prState: state + } + ... on RepositoryNode { + repository { + name + isPrivate + owner { + __typename + login + id + } + id + } + } + ... on Node { + id + } + } +} + +fragment commitCommentView_item on CommitComment { + author { + __typename + login + avatarUrl + ... on Node { + id + } + } + commit { + oid + id + } + bodyHTML + createdAt + path + position +} + +fragment commitView_item on Commit { + author { + name + avatarUrl + user { + login + id + } + } + committer { + name + avatarUrl + user { + login + id + } + } + authoredByCommitter + oid + message + messageHeadlineHTML + commitUrl +} + +fragment prStatusContextView_context on StatusContext { + context + description + state + targetUrl +} + +fragment prCommitView_item on Commit { + committer { + avatarUrl + name + date + } + messageHeadline + messageBody + abbreviatedOid + url +} +*/ + +const node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "kind": "LocalArgument", + "name": "repoId", + "type": "ID!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "issueishId", + "type": "ID!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "timelineCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "timelineCursor", + "type": "String", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commitCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commitCursor", + "type": "String", + "defaultValue": null + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "repoId", + "type": "ID!" + } +], +v2 = { + "kind": "Variable", + "name": "timelineCount", + "variableName": "timelineCount", + "type": null +}, +v3 = { + "kind": "Variable", + "name": "timelineCursor", + "variableName": "timelineCursor", + "type": null +}, +v4 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "issueishId", + "type": "ID!" + } +], +v5 = { + "kind": "ScalarField", + "alias": null, + "name": "__typename", + "args": null, + "storageKey": null +}, +v6 = { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null +}, +v7 = { + "kind": "ScalarField", + "alias": null, + "name": "name", + "args": null, + "storageKey": null +}, +v8 = { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null +}, +v9 = [ + v5, + v8, + v6 +], +v10 = { + "kind": "LinkedField", + "alias": null, + "name": "owner", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v9 +}, +v11 = { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null +}, +v12 = [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } +], +v13 = { + "kind": "ScalarField", + "alias": null, + "name": "number", + "args": null, + "storageKey": null +}, +v14 = { + "kind": "ScalarField", + "alias": null, + "name": "isCrossRepository", + "args": null, + "storageKey": null +}, +v15 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "commitCursor", + "type": "String" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "commitCount", + "type": "Int" + } +], +v16 = { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null + } + ] +}, +v17 = { + "kind": "ScalarField", + "alias": null, + "name": "cursor", + "args": null, + "storageKey": null +}, +v18 = { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null +}, +v19 = { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null +}, +v20 = { + "kind": "ScalarField", + "alias": null, + "name": "title", + "args": null, + "storageKey": null +}, +v21 = { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null +}, +v22 = [ + v11 +], +v23 = { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v8, + v18, + v6, + { + "kind": "InlineFragment", + "type": "Bot", + "selections": v22 + }, + { + "kind": "InlineFragment", + "type": "User", + "selections": v22 + } + ] +}, +v24 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "timelineCursor", + "type": "String" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "timelineCount", + "type": "Int" + } +], +v25 = [ + v5, + v8, + v18, + v6 +], +v26 = { + "kind": "InlineFragment", + "type": "CrossReferencedEvent", + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "referencedAt", + "args": null, + "storageKey": null + }, + v14, + { + "kind": "LinkedField", + "alias": null, + "name": "actor", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v25 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "source", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + { + "kind": "LinkedField", + "alias": null, + "name": "repository", + "storageKey": null, + "args": null, + "concreteType": "Repository", + "plural": false, + "selections": [ + v7, + v10, + v6, + { + "kind": "ScalarField", + "alias": null, + "name": "isPrivate", + "args": null, + "storageKey": null + } + ] + }, + v6, + { + "kind": "InlineFragment", + "type": "PullRequest", + "selections": [ + v13, + v20, + v11, + { + "kind": "ScalarField", + "alias": "prState", + "name": "state", + "args": null, + "storageKey": null + } + ] + }, + { + "kind": "InlineFragment", + "type": "Issue", + "selections": [ + v13, + v20, + v11, + { + "kind": "ScalarField", + "alias": "issueState", + "name": "state", + "args": null, + "storageKey": null + } + ] + } + ] + } + ] +}, +v27 = { + "kind": "ScalarField", + "alias": null, + "name": "oid", + "args": null, + "storageKey": null +}, +v28 = [ + v27, + v6 +], +v29 = { + "kind": "LinkedField", + "alias": null, + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v28 +}, +v30 = { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null +}, +v31 = [ + v5, + v18, + v8, + v6 +], +v32 = { + "kind": "LinkedField", + "alias": null, + "name": "actor", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v31 +}, +v33 = { + "kind": "InlineFragment", + "type": "IssueComment", + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v31 + }, + v21, + v30, + v11 + ] +}, +v34 = { + "kind": "LinkedField", + "alias": null, + "name": "user", + "storageKey": null, + "args": null, + "concreteType": "User", + "plural": false, + "selections": [ + v8, + v6 + ] +}, +v35 = { + "kind": "InlineFragment", + "type": "Commit", + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": "GitActor", + "plural": false, + "selections": [ + v7, + v34, + v18 + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "committer", + "storageKey": null, + "args": null, + "concreteType": "GitActor", + "plural": false, + "selections": [ + v7, + v18, + v34 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "authoredByCommitter", + "args": null, + "storageKey": null + }, + v27, + { + "kind": "ScalarField", + "alias": null, + "name": "message", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "messageHeadlineHTML", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "commitUrl", + "args": null, + "storageKey": null + } + ] +}; +return { + "kind": "Request", + "operationKind": "query", + "name": "issueDetailViewRefetchQuery", + "id": null, + "text": "query issueDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...issueDetailView_issueish_4cAEh0\n id\n }\n}\n\nfragment issueDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "metadata": {}, + "fragment": { + "kind": "Fragment", + "name": "issueDetailViewRefetchQuery", + "type": "Query", + "metadata": null, + "argumentDefinitions": v0, + "selections": [ + { + "kind": "LinkedField", + "alias": "repository", + "name": "node", + "storageKey": null, + "args": v1, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "FragmentSpread", + "name": "issueDetailView_repository", + "args": [ + v2, + v3 + ] + } + ] + }, + { + "kind": "LinkedField", + "alias": "issueish", + "name": "node", + "storageKey": null, + "args": v4, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "FragmentSpread", + "name": "issueDetailView_issueish", + "args": [ + { + "kind": "Variable", + "name": "commitCount", + "variableName": "commitCount", + "type": null + }, + { + "kind": "Variable", + "name": "commitCursor", + "variableName": "commitCursor", + "type": null + }, + v2, + v3 + ] + } + ] + } + ] + }, + "operation": { + "kind": "Operation", + "name": "issueDetailViewRefetchQuery", + "argumentDefinitions": v0, + "selections": [ + { + "kind": "LinkedField", + "alias": "repository", + "name": "node", + "storageKey": null, + "args": v1, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v6, + { + "kind": "InlineFragment", + "type": "Repository", + "selections": [ + v7, + v10 + ] + } + ] + }, + { + "kind": "LinkedField", + "alias": "issueish", + "name": "node", + "storageKey": null, + "args": v4, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v6, + v11, + { + "kind": "LinkedField", + "alias": null, + "name": "reactionGroups", + "storageKey": null, + "args": null, + "concreteType": "ReactionGroup", + "plural": true, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "content", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "users", + "storageKey": null, + "args": null, + "concreteType": "ReactingUserConnection", + "plural": false, + "selections": v12 + } + ] + }, + { + "kind": "InlineFragment", + "type": "PullRequest", + "selections": [ + v13, + v14, + { + "kind": "LinkedField", + "alias": null, + "name": "commits", + "storageKey": null, + "args": v15, + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": [ + v16, + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommitEdge", + "plural": true, + "selections": [ + v17, + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommit", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": [ + v6, + { + "kind": "LinkedField", + "alias": null, + "name": "committer", + "storageKey": null, + "args": null, + "concreteType": "GitActor", + "plural": false, + "selections": [ + v18, + v7, + { + "kind": "ScalarField", + "alias": null, + "name": "date", + "args": null, + "storageKey": null + } + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "messageHeadline", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "messageBody", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "abbreviatedOid", + "args": null, + "storageKey": null + }, + v11 + ] + }, + v6, + v5 + ] + } + ] + } + ] + }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "commits", + "args": v15, + "handle": "connection", + "key": "prCommitsView_commits", + "filters": null + }, + { + "kind": "LinkedField", + "alias": "countedCommits", + "name": "commits", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": v12 + }, + { + "kind": "LinkedField", + "alias": "recentCommits", + "name": "commits", + "storageKey": "commits(last:1)", + "args": [ + { + "kind": "Literal", + "name": "last", + "value": 1, + "type": "Int" + } + ], + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommitEdge", + "plural": true, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommit", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "status", + "storageKey": null, + "args": null, + "concreteType": "Status", + "plural": false, + "selections": [ + v19, + { + "kind": "LinkedField", + "alias": null, + "name": "contexts", + "storageKey": null, + "args": null, + "concreteType": "StatusContext", + "plural": true, + "selections": [ + v6, + v19, + { + "kind": "ScalarField", + "alias": null, + "name": "context", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "description", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "targetUrl", + "args": null, + "storageKey": null + } + ] + }, + v6 + ] + }, + v6 + ] + }, + v6 + ] + } + ] + } + ] + }, + v19, + { + "kind": "ScalarField", + "alias": null, + "name": "changedFiles", + "args": null, + "storageKey": null + }, + v20, + v21, + { + "kind": "ScalarField", + "alias": null, + "name": "baseRefName", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "headRefName", + "args": null, + "storageKey": null + }, + v23, + { + "kind": "LinkedField", + "alias": null, + "name": "headRepositoryOwner", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v9 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "repository", + "storageKey": null, + "args": null, + "concreteType": "Repository", + "plural": false, + "selections": [ + v10, + v6 + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "timeline", + "storageKey": null, + "args": v24, + "concreteType": "PullRequestTimelineConnection", + "plural": false, + "selections": [ + v16, + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestTimelineItemEdge", + "plural": true, + "selections": [ + v17, + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v6, + v26, + { + "kind": "InlineFragment", + "type": "CommitCommentThread", + "selections": [ + v29, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": "comments(first:100)", + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100, + "type": "Int" + } + ], + "concreteType": "CommitCommentConnection", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "CommitCommentEdge", + "plural": true, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "CommitComment", + "plural": false, + "selections": [ + v6, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v25 + }, + v29, + v21, + v30, + { + "kind": "ScalarField", + "alias": null, + "name": "path", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "position", + "args": null, + "storageKey": null + } + ] + } + ] + } + ] + } + ] + }, + { + "kind": "InlineFragment", + "type": "HeadRefForcePushedEvent", + "selections": [ + v32, + { + "kind": "LinkedField", + "alias": null, + "name": "beforeCommit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v28 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "afterCommit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v28 + }, + v30 + ] + }, + { + "kind": "InlineFragment", + "type": "MergedEvent", + "selections": [ + v32, + v29, + { + "kind": "ScalarField", + "alias": null, + "name": "mergeRefName", + "args": null, + "storageKey": null + }, + v30 + ] + }, + v33, + v35 + ] + } + ] + } + ] + }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "timeline", + "args": v24, + "handle": "connection", + "key": "prTimelineContainer_timeline", + "filters": null + } + ] + }, + { + "kind": "InlineFragment", + "type": "Issue", + "selections": [ + v19, + v13, + v20, + v21, + v23, + { + "kind": "LinkedField", + "alias": null, + "name": "timeline", + "storageKey": null, + "args": v24, + "concreteType": "IssueTimelineConnection", + "plural": false, + "selections": [ + v16, + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "IssueTimelineItemEdge", + "plural": true, + "selections": [ + v17, + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v6, + v26, + v33, + v35 + ] + } + ] + } + ] + }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "timeline", + "args": v24, + "handle": "connection", + "key": "IssueTimelineController_timeline", + "filters": null + } + ] + } + ] + } + ] + } +}; +})(); +// prettier-ignore +(node/*: any*/).hash = '3440598bd522ed93e9e1ca4bbf729225'; +module.exports = node; diff --git a/lib/views/__generated__/issueDetailView_issueish.graphql.js b/lib/views/__generated__/issueDetailView_issueish.graphql.js new file mode 100644 index 0000000000..7d6f04463b --- /dev/null +++ b/lib/views/__generated__/issueDetailView_issueish.graphql.js @@ -0,0 +1,322 @@ +/** + * @flow + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteFragment } from 'relay-runtime'; +type issueTimelineController_issue$ref = any; +type prCommitsView_pullRequest$ref = any; +type prStatusesView_pullRequest$ref = any; +type prTimelineController_pullRequest$ref = any; +export type IssueState = "CLOSED" | "OPEN" | "%future added value"; +export type PullRequestState = "CLOSED" | "MERGED" | "OPEN" | "%future added value"; +export type ReactionContent = "CONFUSED" | "HEART" | "HOORAY" | "LAUGH" | "THUMBS_DOWN" | "THUMBS_UP" | "%future added value"; +import type { FragmentReference } from "relay-runtime"; +declare export opaque type issueDetailView_issueish$ref: FragmentReference; +export type issueDetailView_issueish = {| + +__typename: string, + +id?: string, + +url?: any, + +reactionGroups?: ?$ReadOnlyArray<{| + +content: ReactionContent, + +users: {| + +totalCount: number + |}, + |}>, + +state?: IssueState, + +number?: number, + +title?: string, + +bodyHTML?: any, + +author?: ?{| + +login: string, + +avatarUrl: any, + +url?: any, + |}, + +isCrossRepository?: boolean, + +changedFiles?: number, + +countedCommits?: {| + +totalCount: number + |}, + +baseRefName?: string, + +headRefName?: string, + +$fragmentRefs: issueTimelineController_issue$ref & prCommitsView_pullRequest$ref & prStatusesView_pullRequest$ref & prTimelineController_pullRequest$ref, + +$refType: issueDetailView_issueish$ref, +|}; +*/ + + +const node/*: ConcreteFragment*/ = (function(){ +var v0 = { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null +}, +v1 = [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } +], +v2 = { + "kind": "ScalarField", + "alias": null, + "name": "number", + "args": null, + "storageKey": null +}, +v3 = { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null +}, +v4 = { + "kind": "ScalarField", + "alias": null, + "name": "title", + "args": null, + "storageKey": null +}, +v5 = { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null +}, +v6 = [ + v0 +], +v7 = { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null + }, + { + "kind": "InlineFragment", + "type": "Bot", + "selections": v6 + }, + { + "kind": "InlineFragment", + "type": "User", + "selections": v6 + } + ] +}, +v8 = [ + { + "kind": "Variable", + "name": "timelineCount", + "variableName": "timelineCount", + "type": null + }, + { + "kind": "Variable", + "name": "timelineCursor", + "variableName": "timelineCursor", + "type": null + } +]; +return { + "kind": "Fragment", + "name": "issueDetailView_issueish", + "type": "IssueOrPullRequest", + "metadata": null, + "argumentDefinitions": [ + { + "kind": "LocalArgument", + "name": "timelineCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "timelineCursor", + "type": "String", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commitCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commitCursor", + "type": "String", + "defaultValue": null + } + ], + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "__typename", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null + }, + v0, + { + "kind": "LinkedField", + "alias": null, + "name": "reactionGroups", + "storageKey": null, + "args": null, + "concreteType": "ReactionGroup", + "plural": true, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "content", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "users", + "storageKey": null, + "args": null, + "concreteType": "ReactingUserConnection", + "plural": false, + "selections": v1 + } + ] + }, + { + "kind": "InlineFragment", + "type": "PullRequest", + "selections": [ + v2, + { + "kind": "ScalarField", + "alias": null, + "name": "isCrossRepository", + "args": null, + "storageKey": null + }, + { + "kind": "FragmentSpread", + "name": "prCommitsView_pullRequest", + "args": [ + { + "kind": "Variable", + "name": "commitCount", + "variableName": "commitCount", + "type": null + }, + { + "kind": "Variable", + "name": "commitCursor", + "variableName": "commitCursor", + "type": null + } + ] + }, + { + "kind": "LinkedField", + "alias": "countedCommits", + "name": "commits", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": v1 + }, + { + "kind": "FragmentSpread", + "name": "prStatusesView_pullRequest", + "args": null + }, + v3, + { + "kind": "ScalarField", + "alias": null, + "name": "changedFiles", + "args": null, + "storageKey": null + }, + v4, + v5, + { + "kind": "ScalarField", + "alias": null, + "name": "baseRefName", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "headRefName", + "args": null, + "storageKey": null + }, + v7, + { + "kind": "FragmentSpread", + "name": "prTimelineController_pullRequest", + "args": v8 + } + ] + }, + { + "kind": "InlineFragment", + "type": "Issue", + "selections": [ + v3, + v2, + v4, + v5, + v7, + { + "kind": "FragmentSpread", + "name": "issueTimelineController_issue", + "args": v8 + } + ] + } + ] +}; +})(); +// prettier-ignore +(node/*: any*/).hash = '41157daae98ed2eb4b4128690565a282'; +module.exports = node; diff --git a/lib/views/__generated__/issueDetailView_repository.graphql.js b/lib/views/__generated__/issueDetailView_repository.graphql.js new file mode 100644 index 0000000000..efd212d10c --- /dev/null +++ b/lib/views/__generated__/issueDetailView_repository.graphql.js @@ -0,0 +1,67 @@ +/** + * @flow + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteFragment } from 'relay-runtime'; +import type { FragmentReference } from "relay-runtime"; +declare export opaque type issueDetailView_repository$ref: FragmentReference; +export type issueDetailView_repository = {| + +id: string, + +name: string, + +owner: {| + +login: string + |}, + +$refType: issueDetailView_repository$ref, +|}; +*/ + + +const node/*: ConcreteFragment*/ = { + "kind": "Fragment", + "name": "issueDetailView_repository", + "type": "Repository", + "metadata": null, + "argumentDefinitions": [], + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "name", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "owner", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null + } + ] + } + ] +}; +// prettier-ignore +(node/*: any*/).hash = '295a60f53b25b6fdb07a1539cda447f2'; +module.exports = node; diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js new file mode 100644 index 0000000000..0e4ea05be1 --- /dev/null +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -0,0 +1,1579 @@ +/** + * @flow + * @relayHash 2e4f33f58832e71ea98f6fb4f9477af8 + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest } from 'relay-runtime'; +type prDetailView_issueish$ref = any; +type prDetailView_repository$ref = any; +export type prDetailViewRefetchQueryVariables = {| + repoId: string, + issueishId: string, + timelineCount: number, + timelineCursor?: ?string, + commitCount: number, + commitCursor?: ?string, +|}; +export type prDetailViewRefetchQueryResponse = {| + +repository: ?{| + +$fragmentRefs: prDetailView_repository$ref + |}, + +issueish: ?{| + +$fragmentRefs: prDetailView_issueish$ref + |}, +|}; +export type prDetailViewRefetchQuery = {| + variables: prDetailViewRefetchQueryVariables, + response: prDetailViewRefetchQueryResponse, +|}; +*/ + + +/* +query prDetailViewRefetchQuery( + $repoId: ID! + $issueishId: ID! + $timelineCount: Int! + $timelineCursor: String + $commitCount: Int! + $commitCursor: String +) { + repository: node(id: $repoId) { + __typename + ...prDetailView_repository_3D8CP9 + id + } + issueish: node(id: $issueishId) { + __typename + ...prDetailView_issueish_4cAEh0 + id + } +} + +fragment prDetailView_repository_3D8CP9 on Repository { + id + name + owner { + __typename + login + id + } +} + +fragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest { + __typename + ... on Node { + id + } + ... on Issue { + state + number + title + bodyHTML + author { + __typename + login + avatarUrl + ... on User { + url + } + ... on Bot { + url + } + ... on Node { + id + } + } + ...issueTimelineController_issue_3D8CP9 + } + ... on PullRequest { + isCrossRepository + changedFiles + ...prCommitsView_pullRequest_38TpXw + countedCommits: commits { + totalCount + } + ...prStatusesView_pullRequest + state + number + title + bodyHTML + baseRefName + headRefName + author { + __typename + login + avatarUrl + ... on User { + url + } + ... on Bot { + url + } + ... on Node { + id + } + } + ...prTimelineController_pullRequest_3D8CP9 + } + ... on UniformResourceLocatable { + url + } + ... on Reactable { + reactionGroups { + content + users { + totalCount + } + } + } +} + +fragment issueTimelineController_issue_3D8CP9 on Issue { + url + timeline(first: $timelineCount, after: $timelineCursor) { + pageInfo { + endCursor + hasNextPage + } + edges { + cursor + node { + __typename + ...commitsView_nodes + ...issueCommentView_item + ...crossReferencedEventsView_nodes + ... on Node { + id + } + } + } + } +} + +fragment prCommitsView_pullRequest_38TpXw on PullRequest { + url + commits(first: $commitCount, after: $commitCursor) { + pageInfo { + endCursor + hasNextPage + } + edges { + cursor + node { + commit { + id + ...prCommitView_item + } + id + __typename + } + } + } +} + +fragment prStatusesView_pullRequest on PullRequest { + id + recentCommits: commits(last: 1) { + edges { + node { + commit { + status { + state + contexts { + id + state + ...prStatusContextView_context + } + id + } + id + } + id + } + } + } +} + +fragment prTimelineController_pullRequest_3D8CP9 on PullRequest { + url + ...headRefForcePushedEventView_issueish + timeline(first: $timelineCount, after: $timelineCursor) { + pageInfo { + endCursor + hasNextPage + } + edges { + cursor + node { + __typename + ...commitsView_nodes + ...issueCommentView_item + ...mergedEventView_item + ...headRefForcePushedEventView_item + ...commitCommentThreadView_item + ...crossReferencedEventsView_nodes + ... on Node { + id + } + } + } + } +} + +fragment headRefForcePushedEventView_issueish on PullRequest { + headRefName + headRepositoryOwner { + __typename + login + id + } + repository { + owner { + __typename + login + id + } + id + } +} + +fragment commitsView_nodes on Commit { + id + author { + name + user { + login + id + } + } + ...commitView_item +} + +fragment issueCommentView_item on IssueComment { + author { + __typename + avatarUrl + login + ... on Node { + id + } + } + bodyHTML + createdAt + url +} + +fragment mergedEventView_item on MergedEvent { + actor { + __typename + avatarUrl + login + ... on Node { + id + } + } + commit { + oid + id + } + mergeRefName + createdAt +} + +fragment headRefForcePushedEventView_item on HeadRefForcePushedEvent { + actor { + __typename + avatarUrl + login + ... on Node { + id + } + } + beforeCommit { + oid + id + } + afterCommit { + oid + id + } + createdAt +} + +fragment commitCommentThreadView_item on CommitCommentThread { + commit { + oid + id + } + comments(first: 100) { + edges { + node { + id + ...commitCommentView_item + } + } + } +} + +fragment crossReferencedEventsView_nodes on CrossReferencedEvent { + id + referencedAt + isCrossRepository + actor { + __typename + login + avatarUrl + ... on Node { + id + } + } + source { + __typename + ... on RepositoryNode { + repository { + name + owner { + __typename + login + id + } + id + } + } + ... on Node { + id + } + } + ...crossReferencedEventView_item +} + +fragment crossReferencedEventView_item on CrossReferencedEvent { + id + isCrossRepository + source { + __typename + ... on Issue { + number + title + url + issueState: state + } + ... on PullRequest { + number + title + url + prState: state + } + ... on RepositoryNode { + repository { + name + isPrivate + owner { + __typename + login + id + } + id + } + } + ... on Node { + id + } + } +} + +fragment commitCommentView_item on CommitComment { + author { + __typename + login + avatarUrl + ... on Node { + id + } + } + commit { + oid + id + } + bodyHTML + createdAt + path + position +} + +fragment commitView_item on Commit { + author { + name + avatarUrl + user { + login + id + } + } + committer { + name + avatarUrl + user { + login + id + } + } + authoredByCommitter + oid + message + messageHeadlineHTML + commitUrl +} + +fragment prStatusContextView_context on StatusContext { + context + description + state + targetUrl +} + +fragment prCommitView_item on Commit { + committer { + avatarUrl + name + date + } + messageHeadline + messageBody + abbreviatedOid + url +} +*/ + +const node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "kind": "LocalArgument", + "name": "repoId", + "type": "ID!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "issueishId", + "type": "ID!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "timelineCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "timelineCursor", + "type": "String", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commitCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commitCursor", + "type": "String", + "defaultValue": null + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "repoId", + "type": "ID!" + } +], +v2 = { + "kind": "Variable", + "name": "timelineCount", + "variableName": "timelineCount", + "type": null +}, +v3 = { + "kind": "Variable", + "name": "timelineCursor", + "variableName": "timelineCursor", + "type": null +}, +v4 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "issueishId", + "type": "ID!" + } +], +v5 = { + "kind": "ScalarField", + "alias": null, + "name": "__typename", + "args": null, + "storageKey": null +}, +v6 = { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null +}, +v7 = { + "kind": "ScalarField", + "alias": null, + "name": "name", + "args": null, + "storageKey": null +}, +v8 = { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null +}, +v9 = [ + v5, + v8, + v6 +], +v10 = { + "kind": "LinkedField", + "alias": null, + "name": "owner", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v9 +}, +v11 = { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null +}, +v12 = [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } +], +v13 = { + "kind": "ScalarField", + "alias": null, + "name": "number", + "args": null, + "storageKey": null +}, +v14 = { + "kind": "ScalarField", + "alias": null, + "name": "isCrossRepository", + "args": null, + "storageKey": null +}, +v15 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "commitCursor", + "type": "String" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "commitCount", + "type": "Int" + } +], +v16 = { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null + } + ] +}, +v17 = { + "kind": "ScalarField", + "alias": null, + "name": "cursor", + "args": null, + "storageKey": null +}, +v18 = { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null +}, +v19 = { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null +}, +v20 = { + "kind": "ScalarField", + "alias": null, + "name": "title", + "args": null, + "storageKey": null +}, +v21 = { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null +}, +v22 = [ + v11 +], +v23 = { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v8, + v18, + v6, + { + "kind": "InlineFragment", + "type": "Bot", + "selections": v22 + }, + { + "kind": "InlineFragment", + "type": "User", + "selections": v22 + } + ] +}, +v24 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "timelineCursor", + "type": "String" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "timelineCount", + "type": "Int" + } +], +v25 = [ + v5, + v8, + v18, + v6 +], +v26 = { + "kind": "InlineFragment", + "type": "CrossReferencedEvent", + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "referencedAt", + "args": null, + "storageKey": null + }, + v14, + { + "kind": "LinkedField", + "alias": null, + "name": "actor", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v25 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "source", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + { + "kind": "LinkedField", + "alias": null, + "name": "repository", + "storageKey": null, + "args": null, + "concreteType": "Repository", + "plural": false, + "selections": [ + v7, + v10, + v6, + { + "kind": "ScalarField", + "alias": null, + "name": "isPrivate", + "args": null, + "storageKey": null + } + ] + }, + v6, + { + "kind": "InlineFragment", + "type": "PullRequest", + "selections": [ + v13, + v20, + v11, + { + "kind": "ScalarField", + "alias": "prState", + "name": "state", + "args": null, + "storageKey": null + } + ] + }, + { + "kind": "InlineFragment", + "type": "Issue", + "selections": [ + v13, + v20, + v11, + { + "kind": "ScalarField", + "alias": "issueState", + "name": "state", + "args": null, + "storageKey": null + } + ] + } + ] + } + ] +}, +v27 = { + "kind": "ScalarField", + "alias": null, + "name": "oid", + "args": null, + "storageKey": null +}, +v28 = [ + v27, + v6 +], +v29 = { + "kind": "LinkedField", + "alias": null, + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v28 +}, +v30 = { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null +}, +v31 = [ + v5, + v18, + v8, + v6 +], +v32 = { + "kind": "LinkedField", + "alias": null, + "name": "actor", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v31 +}, +v33 = { + "kind": "InlineFragment", + "type": "IssueComment", + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v31 + }, + v21, + v30, + v11 + ] +}, +v34 = { + "kind": "LinkedField", + "alias": null, + "name": "user", + "storageKey": null, + "args": null, + "concreteType": "User", + "plural": false, + "selections": [ + v8, + v6 + ] +}, +v35 = { + "kind": "InlineFragment", + "type": "Commit", + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": "GitActor", + "plural": false, + "selections": [ + v7, + v34, + v18 + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "committer", + "storageKey": null, + "args": null, + "concreteType": "GitActor", + "plural": false, + "selections": [ + v7, + v18, + v34 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "authoredByCommitter", + "args": null, + "storageKey": null + }, + v27, + { + "kind": "ScalarField", + "alias": null, + "name": "message", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "messageHeadlineHTML", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "commitUrl", + "args": null, + "storageKey": null + } + ] +}; +return { + "kind": "Request", + "operationKind": "query", + "name": "prDetailViewRefetchQuery", + "id": null, + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...prDetailView_issueish_4cAEh0\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "metadata": {}, + "fragment": { + "kind": "Fragment", + "name": "prDetailViewRefetchQuery", + "type": "Query", + "metadata": null, + "argumentDefinitions": v0, + "selections": [ + { + "kind": "LinkedField", + "alias": "repository", + "name": "node", + "storageKey": null, + "args": v1, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "FragmentSpread", + "name": "prDetailView_repository", + "args": [ + v2, + v3 + ] + } + ] + }, + { + "kind": "LinkedField", + "alias": "issueish", + "name": "node", + "storageKey": null, + "args": v4, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "FragmentSpread", + "name": "prDetailView_issueish", + "args": [ + { + "kind": "Variable", + "name": "commitCount", + "variableName": "commitCount", + "type": null + }, + { + "kind": "Variable", + "name": "commitCursor", + "variableName": "commitCursor", + "type": null + }, + v2, + v3 + ] + } + ] + } + ] + }, + "operation": { + "kind": "Operation", + "name": "prDetailViewRefetchQuery", + "argumentDefinitions": v0, + "selections": [ + { + "kind": "LinkedField", + "alias": "repository", + "name": "node", + "storageKey": null, + "args": v1, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v6, + { + "kind": "InlineFragment", + "type": "Repository", + "selections": [ + v7, + v10 + ] + } + ] + }, + { + "kind": "LinkedField", + "alias": "issueish", + "name": "node", + "storageKey": null, + "args": v4, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v6, + v11, + { + "kind": "LinkedField", + "alias": null, + "name": "reactionGroups", + "storageKey": null, + "args": null, + "concreteType": "ReactionGroup", + "plural": true, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "content", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "users", + "storageKey": null, + "args": null, + "concreteType": "ReactingUserConnection", + "plural": false, + "selections": v12 + } + ] + }, + { + "kind": "InlineFragment", + "type": "PullRequest", + "selections": [ + v13, + v14, + { + "kind": "LinkedField", + "alias": null, + "name": "commits", + "storageKey": null, + "args": v15, + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": [ + v16, + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommitEdge", + "plural": true, + "selections": [ + v17, + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommit", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": [ + v6, + { + "kind": "LinkedField", + "alias": null, + "name": "committer", + "storageKey": null, + "args": null, + "concreteType": "GitActor", + "plural": false, + "selections": [ + v18, + v7, + { + "kind": "ScalarField", + "alias": null, + "name": "date", + "args": null, + "storageKey": null + } + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "messageHeadline", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "messageBody", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "abbreviatedOid", + "args": null, + "storageKey": null + }, + v11 + ] + }, + v6, + v5 + ] + } + ] + } + ] + }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "commits", + "args": v15, + "handle": "connection", + "key": "prCommitsView_commits", + "filters": null + }, + { + "kind": "LinkedField", + "alias": "countedCommits", + "name": "commits", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": v12 + }, + { + "kind": "LinkedField", + "alias": "recentCommits", + "name": "commits", + "storageKey": "commits(last:1)", + "args": [ + { + "kind": "Literal", + "name": "last", + "value": 1, + "type": "Int" + } + ], + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommitEdge", + "plural": true, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommit", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "status", + "storageKey": null, + "args": null, + "concreteType": "Status", + "plural": false, + "selections": [ + v19, + { + "kind": "LinkedField", + "alias": null, + "name": "contexts", + "storageKey": null, + "args": null, + "concreteType": "StatusContext", + "plural": true, + "selections": [ + v6, + v19, + { + "kind": "ScalarField", + "alias": null, + "name": "context", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "description", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "targetUrl", + "args": null, + "storageKey": null + } + ] + }, + v6 + ] + }, + v6 + ] + }, + v6 + ] + } + ] + } + ] + }, + v19, + { + "kind": "ScalarField", + "alias": null, + "name": "changedFiles", + "args": null, + "storageKey": null + }, + v20, + v21, + { + "kind": "ScalarField", + "alias": null, + "name": "baseRefName", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "headRefName", + "args": null, + "storageKey": null + }, + v23, + { + "kind": "LinkedField", + "alias": null, + "name": "headRepositoryOwner", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v9 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "repository", + "storageKey": null, + "args": null, + "concreteType": "Repository", + "plural": false, + "selections": [ + v10, + v6 + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "timeline", + "storageKey": null, + "args": v24, + "concreteType": "PullRequestTimelineConnection", + "plural": false, + "selections": [ + v16, + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestTimelineItemEdge", + "plural": true, + "selections": [ + v17, + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v6, + v26, + { + "kind": "InlineFragment", + "type": "CommitCommentThread", + "selections": [ + v29, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": "comments(first:100)", + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100, + "type": "Int" + } + ], + "concreteType": "CommitCommentConnection", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "CommitCommentEdge", + "plural": true, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "CommitComment", + "plural": false, + "selections": [ + v6, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v25 + }, + v29, + v21, + v30, + { + "kind": "ScalarField", + "alias": null, + "name": "path", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "position", + "args": null, + "storageKey": null + } + ] + } + ] + } + ] + } + ] + }, + { + "kind": "InlineFragment", + "type": "HeadRefForcePushedEvent", + "selections": [ + v32, + { + "kind": "LinkedField", + "alias": null, + "name": "beforeCommit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v28 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "afterCommit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v28 + }, + v30 + ] + }, + { + "kind": "InlineFragment", + "type": "MergedEvent", + "selections": [ + v32, + v29, + { + "kind": "ScalarField", + "alias": null, + "name": "mergeRefName", + "args": null, + "storageKey": null + }, + v30 + ] + }, + v33, + v35 + ] + } + ] + } + ] + }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "timeline", + "args": v24, + "handle": "connection", + "key": "prTimelineContainer_timeline", + "filters": null + } + ] + }, + { + "kind": "InlineFragment", + "type": "Issue", + "selections": [ + v19, + v13, + v20, + v21, + v23, + { + "kind": "LinkedField", + "alias": null, + "name": "timeline", + "storageKey": null, + "args": v24, + "concreteType": "IssueTimelineConnection", + "plural": false, + "selections": [ + v16, + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "IssueTimelineItemEdge", + "plural": true, + "selections": [ + v17, + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v6, + v26, + v33, + v35 + ] + } + ] + } + ] + }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "timeline", + "args": v24, + "handle": "connection", + "key": "IssueTimelineController_timeline", + "filters": null + } + ] + } + ] + } + ] + } +}; +})(); +// prettier-ignore +(node/*: any*/).hash = 'ba6fcfcda973b63cbdd79918fb888605'; +module.exports = node; diff --git a/lib/views/__generated__/prDetailView_issueish.graphql.js b/lib/views/__generated__/prDetailView_issueish.graphql.js new file mode 100644 index 0000000000..e31ad83183 --- /dev/null +++ b/lib/views/__generated__/prDetailView_issueish.graphql.js @@ -0,0 +1,322 @@ +/** + * @flow + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteFragment } from 'relay-runtime'; +type issueTimelineController_issue$ref = any; +type prCommitsView_pullRequest$ref = any; +type prStatusesView_pullRequest$ref = any; +type prTimelineController_pullRequest$ref = any; +export type IssueState = "CLOSED" | "OPEN" | "%future added value"; +export type PullRequestState = "CLOSED" | "MERGED" | "OPEN" | "%future added value"; +export type ReactionContent = "CONFUSED" | "HEART" | "HOORAY" | "LAUGH" | "THUMBS_DOWN" | "THUMBS_UP" | "%future added value"; +import type { FragmentReference } from "relay-runtime"; +declare export opaque type prDetailView_issueish$ref: FragmentReference; +export type prDetailView_issueish = {| + +__typename: string, + +id?: string, + +url?: any, + +reactionGroups?: ?$ReadOnlyArray<{| + +content: ReactionContent, + +users: {| + +totalCount: number + |}, + |}>, + +state?: IssueState, + +number?: number, + +title?: string, + +bodyHTML?: any, + +author?: ?{| + +login: string, + +avatarUrl: any, + +url?: any, + |}, + +isCrossRepository?: boolean, + +changedFiles?: number, + +countedCommits?: {| + +totalCount: number + |}, + +baseRefName?: string, + +headRefName?: string, + +$fragmentRefs: issueTimelineController_issue$ref & prCommitsView_pullRequest$ref & prStatusesView_pullRequest$ref & prTimelineController_pullRequest$ref, + +$refType: prDetailView_issueish$ref, +|}; +*/ + + +const node/*: ConcreteFragment*/ = (function(){ +var v0 = { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null +}, +v1 = [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } +], +v2 = { + "kind": "ScalarField", + "alias": null, + "name": "number", + "args": null, + "storageKey": null +}, +v3 = { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null +}, +v4 = { + "kind": "ScalarField", + "alias": null, + "name": "title", + "args": null, + "storageKey": null +}, +v5 = { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null +}, +v6 = [ + v0 +], +v7 = { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null + }, + { + "kind": "InlineFragment", + "type": "Bot", + "selections": v6 + }, + { + "kind": "InlineFragment", + "type": "User", + "selections": v6 + } + ] +}, +v8 = [ + { + "kind": "Variable", + "name": "timelineCount", + "variableName": "timelineCount", + "type": null + }, + { + "kind": "Variable", + "name": "timelineCursor", + "variableName": "timelineCursor", + "type": null + } +]; +return { + "kind": "Fragment", + "name": "prDetailView_issueish", + "type": "IssueOrPullRequest", + "metadata": null, + "argumentDefinitions": [ + { + "kind": "LocalArgument", + "name": "timelineCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "timelineCursor", + "type": "String", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commitCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commitCursor", + "type": "String", + "defaultValue": null + } + ], + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "__typename", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null + }, + v0, + { + "kind": "LinkedField", + "alias": null, + "name": "reactionGroups", + "storageKey": null, + "args": null, + "concreteType": "ReactionGroup", + "plural": true, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "content", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "users", + "storageKey": null, + "args": null, + "concreteType": "ReactingUserConnection", + "plural": false, + "selections": v1 + } + ] + }, + { + "kind": "InlineFragment", + "type": "PullRequest", + "selections": [ + v2, + { + "kind": "ScalarField", + "alias": null, + "name": "isCrossRepository", + "args": null, + "storageKey": null + }, + { + "kind": "FragmentSpread", + "name": "prCommitsView_pullRequest", + "args": [ + { + "kind": "Variable", + "name": "commitCount", + "variableName": "commitCount", + "type": null + }, + { + "kind": "Variable", + "name": "commitCursor", + "variableName": "commitCursor", + "type": null + } + ] + }, + { + "kind": "LinkedField", + "alias": "countedCommits", + "name": "commits", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": v1 + }, + { + "kind": "FragmentSpread", + "name": "prStatusesView_pullRequest", + "args": null + }, + v3, + { + "kind": "ScalarField", + "alias": null, + "name": "changedFiles", + "args": null, + "storageKey": null + }, + v4, + v5, + { + "kind": "ScalarField", + "alias": null, + "name": "baseRefName", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "headRefName", + "args": null, + "storageKey": null + }, + v7, + { + "kind": "FragmentSpread", + "name": "prTimelineController_pullRequest", + "args": v8 + } + ] + }, + { + "kind": "InlineFragment", + "type": "Issue", + "selections": [ + v3, + v2, + v4, + v5, + v7, + { + "kind": "FragmentSpread", + "name": "issueTimelineController_issue", + "args": v8 + } + ] + } + ] +}; +})(); +// prettier-ignore +(node/*: any*/).hash = '21b9e182a721c5b5ae1740c3e8b58f91'; +module.exports = node; diff --git a/lib/views/__generated__/prDetailView_repository.graphql.js b/lib/views/__generated__/prDetailView_repository.graphql.js new file mode 100644 index 0000000000..22a7c77d8b --- /dev/null +++ b/lib/views/__generated__/prDetailView_repository.graphql.js @@ -0,0 +1,67 @@ +/** + * @flow + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteFragment } from 'relay-runtime'; +import type { FragmentReference } from "relay-runtime"; +declare export opaque type prDetailView_repository$ref: FragmentReference; +export type prDetailView_repository = {| + +id: string, + +name: string, + +owner: {| + +login: string + |}, + +$refType: prDetailView_repository$ref, +|}; +*/ + + +const node/*: ConcreteFragment*/ = { + "kind": "Fragment", + "name": "prDetailView_repository", + "type": "Repository", + "metadata": null, + "argumentDefinitions": [], + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "name", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "owner", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null + } + ] + } + ] +}; +// prettier-ignore +(node/*: any*/).hash = '3f3d61ddd6afa1c9e0811c3b5be51bb0'; +module.exports = node; diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 0e3d811b26..92c46d9cd4 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -182,6 +182,7 @@ export class BareIssueDetailView extends React.Component { this.refresher.refreshNow(true); } + // would be good to make this more specific in terms of the source component, etc. recordOpenInBrowserEvent() { addEvent('open-issueish-in-browser', {package: 'github', from: 'issueish-header'}); } @@ -207,7 +208,7 @@ export class BareIssueDetailView extends React.Component { export default createRefetchContainer(BareIssueDetailView, { repository: graphql` - fragment issueishDetailView_repository on Repository { + fragment issueDetailView_repository on Repository { id name owner { @@ -217,7 +218,7 @@ export default createRefetchContainer(BareIssueDetailView, { `, issueish: graphql` - fragment issueishDetailView_issueish on IssueOrPullRequest + fragment issueDetailView_issueish on IssueOrPullRequest @argumentDefinitions( timelineCount: {type: "Int!"}, timelineCursor: {type: "String"}, @@ -269,7 +270,7 @@ export default createRefetchContainer(BareIssueDetailView, { } `, }, graphql` - query issueishDetailViewRefetchQuery + query issueDetailViewRefetchQuery ( $repoId: ID!, $issueishId: ID!, @@ -279,14 +280,14 @@ export default createRefetchContainer(BareIssueDetailView, { $commitCursor: String ) { repository:node(id: $repoId) { - ...issueishDetailView_repository @arguments( + ...issueDetailView_repository @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor ) } issueish:node(id: $issueishId) { - ...issueishDetailView_issueish @arguments( + ...issueDetailView_issueish @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor, commitCount: $commitCount, diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 7808c90929..340590eefd 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -321,7 +321,7 @@ export class BarePullRequestDetailView extends React.Component { export default createRefetchContainer(BarePullRequestDetailView, { repository: graphql` - fragment issueishDetailView_repository on Repository { + fragment prDetailView_repository on Repository { id name owner { @@ -331,7 +331,7 @@ export default createRefetchContainer(BarePullRequestDetailView, { `, issueish: graphql` - fragment issueishDetailView_issueish on IssueOrPullRequest + fragment prDetailView_issueish on IssueOrPullRequest @argumentDefinitions( timelineCount: {type: "Int!"}, timelineCursor: {type: "String"}, @@ -383,7 +383,7 @@ export default createRefetchContainer(BarePullRequestDetailView, { } `, }, graphql` - query issueishDetailViewRefetchQuery + query prDetailViewRefetchQuery ( $repoId: ID!, $issueishId: ID!, @@ -393,14 +393,14 @@ export default createRefetchContainer(BarePullRequestDetailView, { $commitCursor: String ) { repository:node(id: $repoId) { - ...issueishDetailView_repository @arguments( + ...prDetailView_repository @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor ) } issueish:node(id: $issueishId) { - ...issueishDetailView_issueish @arguments( + ...prDetailView_issueish @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor, commitCount: $commitCount, From d4f84c4333f7898300663aebb4a54da00aef1744 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 15:48:32 -0800 Subject: [PATCH 1150/4053] :fire: unused part of the fragment I don't think we need this? But I could be wrong. graphql still confuses me. --- .../issueishDetailContainerQuery.graphql.js | 38 +- .../issueDetailViewRefetchQuery.graphql.js | 1141 ++++------------- .../issueDetailView_issueish.graphql.js | 235 +--- lib/views/issue-detail-view.js | 20 - 4 files changed, 291 insertions(+), 1143 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 7fac7a736c..687124429a 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 547454dd6453e107bd844739888fae07 + * @relayHash c7e6ef594b18ba097059ab69750f8863 */ /* eslint-disable */ @@ -121,36 +121,6 @@ fragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest { } ...issueTimelineController_issue_3D8CP9 } - ... on PullRequest { - isCrossRepository - changedFiles - ...prCommitsView_pullRequest_38TpXw - countedCommits: commits { - totalCount - } - ...prStatusesView_pullRequest - state - number - title - bodyHTML - baseRefName - headRefName - author { - __typename - login - avatarUrl - ... on User { - url - } - ... on Bot { - url - } - ... on Node { - id - } - } - ...prTimelineController_pullRequest_3D8CP9 - } ... on UniformResourceLocatable { url } @@ -1066,7 +1036,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueishDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueishDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueishDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueishDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1621,10 +1591,10 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v22, v13, - v30, v17, + v30, + v22, v32, v12, { diff --git a/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js index da470e3b7d..3eb6fa8f10 100644 --- a/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash b15e84159f798cffa1d97aa4c9cb7ebc + * @relayHash 0852b97ef8d8e53f978f1538930bce64 */ /* eslint-disable */ @@ -40,8 +40,6 @@ query issueDetailViewRefetchQuery( $issueishId: ID! $timelineCount: Int! $timelineCursor: String - $commitCount: Int! - $commitCursor: String ) { repository: node(id: $repoId) { __typename @@ -91,36 +89,6 @@ fragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest { } ...issueTimelineController_issue_3D8CP9 } - ... on PullRequest { - isCrossRepository - changedFiles - ...prCommitsView_pullRequest_38TpXw - countedCommits: commits { - totalCount - } - ...prStatusesView_pullRequest - state - number - title - bodyHTML - baseRefName - headRefName - author { - __typename - login - avatarUrl - ... on User { - url - } - ... on Bot { - url - } - ... on Node { - id - } - } - ...prTimelineController_pullRequest_3D8CP9 - } ... on UniformResourceLocatable { url } @@ -156,93 +124,6 @@ fragment issueTimelineController_issue_3D8CP9 on Issue { } } -fragment prCommitsView_pullRequest_38TpXw on PullRequest { - url - commits(first: $commitCount, after: $commitCursor) { - pageInfo { - endCursor - hasNextPage - } - edges { - cursor - node { - commit { - id - ...prCommitView_item - } - id - __typename - } - } - } -} - -fragment prStatusesView_pullRequest on PullRequest { - id - recentCommits: commits(last: 1) { - edges { - node { - commit { - status { - state - contexts { - id - state - ...prStatusContextView_context - } - id - } - id - } - id - } - } - } -} - -fragment prTimelineController_pullRequest_3D8CP9 on PullRequest { - url - ...headRefForcePushedEventView_issueish - timeline(first: $timelineCount, after: $timelineCursor) { - pageInfo { - endCursor - hasNextPage - } - edges { - cursor - node { - __typename - ...commitsView_nodes - ...issueCommentView_item - ...mergedEventView_item - ...headRefForcePushedEventView_item - ...commitCommentThreadView_item - ...crossReferencedEventsView_nodes - ... on Node { - id - } - } - } - } -} - -fragment headRefForcePushedEventView_issueish on PullRequest { - headRefName - headRepositoryOwner { - __typename - login - id - } - repository { - owner { - __typename - login - id - } - id - } -} - fragment commitsView_nodes on Commit { id author { @@ -269,58 +150,6 @@ fragment issueCommentView_item on IssueComment { url } -fragment mergedEventView_item on MergedEvent { - actor { - __typename - avatarUrl - login - ... on Node { - id - } - } - commit { - oid - id - } - mergeRefName - createdAt -} - -fragment headRefForcePushedEventView_item on HeadRefForcePushedEvent { - actor { - __typename - avatarUrl - login - ... on Node { - id - } - } - beforeCommit { - oid - id - } - afterCommit { - oid - id - } - createdAt -} - -fragment commitCommentThreadView_item on CommitCommentThread { - commit { - oid - id - } - comments(first: 100) { - edges { - node { - id - ...commitCommentView_item - } - } - } -} - fragment crossReferencedEventsView_nodes on CrossReferencedEvent { id referencedAt @@ -388,25 +217,6 @@ fragment crossReferencedEventView_item on CrossReferencedEvent { } } -fragment commitCommentView_item on CommitComment { - author { - __typename - login - avatarUrl - ... on Node { - id - } - } - commit { - oid - id - } - bodyHTML - createdAt - path - position -} - fragment commitView_item on Commit { author { name @@ -430,25 +240,6 @@ fragment commitView_item on Commit { messageHeadlineHTML commitUrl } - -fragment prStatusContextView_context on StatusContext { - context - description - state - targetUrl -} - -fragment prCommitView_item on Commit { - committer { - avatarUrl - name - date - } - messageHeadline - messageBody - abbreviatedOid - url -} */ const node/*: ConcreteRequest*/ = (function(){ @@ -546,12 +337,7 @@ v8 = { "args": null, "storageKey": null }, -v9 = [ - v5, - v8, - v6 -], -v10 = { +v9 = { "kind": "LinkedField", "alias": null, "name": "owner", @@ -559,141 +345,51 @@ v10 = { "args": null, "concreteType": null, "plural": false, - "selections": v9 + "selections": [ + v5, + v8, + v6 + ] }, -v11 = { +v10 = { "kind": "ScalarField", "alias": null, "name": "url", "args": null, "storageKey": null }, -v12 = [ - { - "kind": "ScalarField", - "alias": null, - "name": "totalCount", - "args": null, - "storageKey": null - } -], -v13 = { +v11 = { "kind": "ScalarField", "alias": null, "name": "number", "args": null, "storageKey": null }, -v14 = { +v12 = { "kind": "ScalarField", "alias": null, - "name": "isCrossRepository", + "name": "title", "args": null, "storageKey": null }, -v15 = [ - { - "kind": "Variable", - "name": "after", - "variableName": "commitCursor", - "type": "String" - }, - { - "kind": "Variable", - "name": "first", - "variableName": "commitCount", - "type": "Int" - } -], -v16 = { - "kind": "LinkedField", - "alias": null, - "name": "pageInfo", - "storageKey": null, - "args": null, - "concreteType": "PageInfo", - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "endCursor", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "hasNextPage", - "args": null, - "storageKey": null - } - ] -}, -v17 = { +v13 = { "kind": "ScalarField", "alias": null, - "name": "cursor", + "name": "bodyHTML", "args": null, "storageKey": null }, -v18 = { +v14 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", "args": null, "storageKey": null }, -v19 = { - "kind": "ScalarField", - "alias": null, - "name": "state", - "args": null, - "storageKey": null -}, -v20 = { - "kind": "ScalarField", - "alias": null, - "name": "title", - "args": null, - "storageKey": null -}, -v21 = { - "kind": "ScalarField", - "alias": null, - "name": "bodyHTML", - "args": null, - "storageKey": null -}, -v22 = [ - v11 +v15 = [ + v10 ], -v23 = { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v8, - v18, - v6, - { - "kind": "InlineFragment", - "type": "Bot", - "selections": v22 - }, - { - "kind": "InlineFragment", - "type": "User", - "selections": v22 - } - ] -}, -v24 = [ +v16 = [ { "kind": "Variable", "name": "after", @@ -707,166 +403,7 @@ v24 = [ "type": "Int" } ], -v25 = [ - v5, - v8, - v18, - v6 -], -v26 = { - "kind": "InlineFragment", - "type": "CrossReferencedEvent", - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "referencedAt", - "args": null, - "storageKey": null - }, - v14, - { - "kind": "LinkedField", - "alias": null, - "name": "actor", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v25 - }, - { - "kind": "LinkedField", - "alias": null, - "name": "source", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - { - "kind": "LinkedField", - "alias": null, - "name": "repository", - "storageKey": null, - "args": null, - "concreteType": "Repository", - "plural": false, - "selections": [ - v7, - v10, - v6, - { - "kind": "ScalarField", - "alias": null, - "name": "isPrivate", - "args": null, - "storageKey": null - } - ] - }, - v6, - { - "kind": "InlineFragment", - "type": "PullRequest", - "selections": [ - v13, - v20, - v11, - { - "kind": "ScalarField", - "alias": "prState", - "name": "state", - "args": null, - "storageKey": null - } - ] - }, - { - "kind": "InlineFragment", - "type": "Issue", - "selections": [ - v13, - v20, - v11, - { - "kind": "ScalarField", - "alias": "issueState", - "name": "state", - "args": null, - "storageKey": null - } - ] - } - ] - } - ] -}, -v27 = { - "kind": "ScalarField", - "alias": null, - "name": "oid", - "args": null, - "storageKey": null -}, -v28 = [ - v27, - v6 -], -v29 = { - "kind": "LinkedField", - "alias": null, - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v28 -}, -v30 = { - "kind": "ScalarField", - "alias": null, - "name": "createdAt", - "args": null, - "storageKey": null -}, -v31 = [ - v5, - v18, - v8, - v6 -], -v32 = { - "kind": "LinkedField", - "alias": null, - "name": "actor", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v31 -}, -v33 = { - "kind": "InlineFragment", - "type": "IssueComment", - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v31 - }, - v21, - v30, - v11 - ] -}, -v34 = { +v17 = { "kind": "LinkedField", "alias": null, "name": "user", @@ -878,76 +415,13 @@ v34 = { v8, v6 ] -}, -v35 = { - "kind": "InlineFragment", - "type": "Commit", - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": "GitActor", - "plural": false, - "selections": [ - v7, - v34, - v18 - ] - }, - { - "kind": "LinkedField", - "alias": null, - "name": "committer", - "storageKey": null, - "args": null, - "concreteType": "GitActor", - "plural": false, - "selections": [ - v7, - v18, - v34 - ] - }, - { - "kind": "ScalarField", - "alias": null, - "name": "authoredByCommitter", - "args": null, - "storageKey": null - }, - v27, - { - "kind": "ScalarField", - "alias": null, - "name": "message", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "messageHeadlineHTML", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "commitUrl", - "args": null, - "storageKey": null - } - ] }; return { "kind": "Request", "operationKind": "query", "name": "issueDetailViewRefetchQuery", "id": null, - "text": "query issueDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...issueDetailView_issueish_4cAEh0\n id\n }\n}\n\nfragment issueDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "text": "query issueDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...issueDetailView_issueish_4cAEh0\n id\n }\n}\n\nfragment issueDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1029,7 +503,7 @@ return { "type": "Repository", "selections": [ v7, - v10 + v9 ] } ] @@ -1045,7 +519,7 @@ return { "selections": [ v5, v6, - v11, + v10, { "kind": "LinkedField", "alias": null, @@ -1070,487 +544,318 @@ return { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v12 - } - ] - }, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } + ] + } + ] + }, { "kind": "InlineFragment", - "type": "PullRequest", + "type": "Issue", "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null + }, + v11, + v12, v13, - v14, { "kind": "LinkedField", "alias": null, - "name": "commits", + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v8, + v14, + v6, + { + "kind": "InlineFragment", + "type": "Bot", + "selections": v15 + }, + { + "kind": "InlineFragment", + "type": "User", + "selections": v15 + } + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "timeline", "storageKey": null, - "args": v15, - "concreteType": "PullRequestCommitConnection", + "args": v16, + "concreteType": "IssueTimelineConnection", "plural": false, "selections": [ - v16, + { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null + } + ] + }, { "kind": "LinkedField", "alias": null, "name": "edges", "storageKey": null, "args": null, - "concreteType": "PullRequestCommitEdge", + "concreteType": "IssueTimelineItemEdge", "plural": true, "selections": [ - v17, + { + "kind": "ScalarField", + "alias": null, + "name": "cursor", + "args": null, + "storageKey": null + }, { "kind": "LinkedField", "alias": null, "name": "node", "storageKey": null, "args": null, - "concreteType": "PullRequestCommit", + "concreteType": null, "plural": false, "selections": [ + v5, + v6, { - "kind": "LinkedField", - "alias": null, - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, + "kind": "InlineFragment", + "type": "CrossReferencedEvent", "selections": [ - v6, - { - "kind": "LinkedField", - "alias": null, - "name": "committer", - "storageKey": null, - "args": null, - "concreteType": "GitActor", - "plural": false, - "selections": [ - v18, - v7, - { - "kind": "ScalarField", - "alias": null, - "name": "date", - "args": null, - "storageKey": null - } - ] - }, { "kind": "ScalarField", "alias": null, - "name": "messageHeadline", + "name": "referencedAt", "args": null, "storageKey": null }, { "kind": "ScalarField", "alias": null, - "name": "messageBody", + "name": "isCrossRepository", "args": null, "storageKey": null }, { - "kind": "ScalarField", + "kind": "LinkedField", "alias": null, - "name": "abbreviatedOid", + "name": "actor", + "storageKey": null, "args": null, - "storageKey": null + "concreteType": null, + "plural": false, + "selections": [ + v5, + v8, + v14, + v6 + ] }, - v11 - ] - }, - v6, - v5 - ] - } - ] - } - ] - }, - { - "kind": "LinkedHandle", - "alias": null, - "name": "commits", - "args": v15, - "handle": "connection", - "key": "prCommitsView_commits", - "filters": null - }, - { - "kind": "LinkedField", - "alias": "countedCommits", - "name": "commits", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommitConnection", - "plural": false, - "selections": v12 - }, - { - "kind": "LinkedField", - "alias": "recentCommits", - "name": "commits", - "storageKey": "commits(last:1)", - "args": [ - { - "kind": "Literal", - "name": "last", - "value": 1, - "type": "Int" - } - ], - "concreteType": "PullRequestCommitConnection", - "plural": false, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "edges", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommitEdge", - "plural": true, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommit", - "plural": false, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": [ { "kind": "LinkedField", "alias": null, - "name": "status", + "name": "source", "storageKey": null, "args": null, - "concreteType": "Status", + "concreteType": null, "plural": false, "selections": [ - v19, + v5, { "kind": "LinkedField", "alias": null, - "name": "contexts", + "name": "repository", "storageKey": null, "args": null, - "concreteType": "StatusContext", - "plural": true, + "concreteType": "Repository", + "plural": false, "selections": [ + v7, + v9, v6, - v19, { "kind": "ScalarField", "alias": null, - "name": "context", + "name": "isPrivate", "args": null, "storageKey": null - }, + } + ] + }, + v6, + { + "kind": "InlineFragment", + "type": "PullRequest", + "selections": [ + v11, + v12, + v10, { "kind": "ScalarField", - "alias": null, - "name": "description", + "alias": "prState", + "name": "state", "args": null, "storageKey": null - }, + } + ] + }, + { + "kind": "InlineFragment", + "type": "Issue", + "selections": [ + v11, + v12, + v10, { "kind": "ScalarField", - "alias": null, - "name": "targetUrl", + "alias": "issueState", + "name": "state", "args": null, "storageKey": null } ] - }, - v6 + } ] - }, - v6 + } ] }, - v6 - ] - } - ] - } - ] - }, - v19, - { - "kind": "ScalarField", - "alias": null, - "name": "changedFiles", - "args": null, - "storageKey": null - }, - v20, - v21, - { - "kind": "ScalarField", - "alias": null, - "name": "baseRefName", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "headRefName", - "args": null, - "storageKey": null - }, - v23, - { - "kind": "LinkedField", - "alias": null, - "name": "headRepositoryOwner", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v9 - }, - { - "kind": "LinkedField", - "alias": null, - "name": "repository", - "storageKey": null, - "args": null, - "concreteType": "Repository", - "plural": false, - "selections": [ - v10, - v6 - ] - }, - { - "kind": "LinkedField", - "alias": null, - "name": "timeline", - "storageKey": null, - "args": v24, - "concreteType": "PullRequestTimelineConnection", - "plural": false, - "selections": [ - v16, - { - "kind": "LinkedField", - "alias": null, - "name": "edges", - "storageKey": null, - "args": null, - "concreteType": "PullRequestTimelineItemEdge", - "plural": true, - "selections": [ - v17, - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v6, - v26, { "kind": "InlineFragment", - "type": "CommitCommentThread", + "type": "IssueComment", "selections": [ - v29, { "kind": "LinkedField", "alias": null, - "name": "comments", - "storageKey": "comments(first:100)", - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 100, - "type": "Int" - } - ], - "concreteType": "CommitCommentConnection", + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, "plural": false, "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "edges", - "storageKey": null, - "args": null, - "concreteType": "CommitCommentEdge", - "plural": true, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": "CommitComment", - "plural": false, - "selections": [ - v6, - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v25 - }, - v29, - v21, - v30, - { - "kind": "ScalarField", - "alias": null, - "name": "path", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "position", - "args": null, - "storageKey": null - } - ] - } - ] - } + v5, + v14, + v8, + v6 ] - } + }, + v13, + { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null + }, + v10 ] }, { "kind": "InlineFragment", - "type": "HeadRefForcePushedEvent", + "type": "Commit", "selections": [ - v32, { "kind": "LinkedField", "alias": null, - "name": "beforeCommit", + "name": "author", "storageKey": null, "args": null, - "concreteType": "Commit", + "concreteType": "GitActor", "plural": false, - "selections": v28 + "selections": [ + v7, + v17, + v14 + ] }, { "kind": "LinkedField", "alias": null, - "name": "afterCommit", + "name": "committer", "storageKey": null, "args": null, - "concreteType": "Commit", + "concreteType": "GitActor", "plural": false, - "selections": v28 + "selections": [ + v7, + v14, + v17 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "authoredByCommitter", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "oid", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "message", + "args": null, + "storageKey": null }, - v30 - ] - }, - { - "kind": "InlineFragment", - "type": "MergedEvent", - "selections": [ - v32, - v29, { "kind": "ScalarField", "alias": null, - "name": "mergeRefName", + "name": "messageHeadlineHTML", "args": null, "storageKey": null }, - v30 + { + "kind": "ScalarField", + "alias": null, + "name": "commitUrl", + "args": null, + "storageKey": null + } ] - }, - v33, - v35 - ] - } - ] - } - ] - }, - { - "kind": "LinkedHandle", - "alias": null, - "name": "timeline", - "args": v24, - "handle": "connection", - "key": "prTimelineContainer_timeline", - "filters": null - } - ] - }, - { - "kind": "InlineFragment", - "type": "Issue", - "selections": [ - v19, - v13, - v20, - v21, - v23, - { - "kind": "LinkedField", - "alias": null, - "name": "timeline", - "storageKey": null, - "args": v24, - "concreteType": "IssueTimelineConnection", - "plural": false, - "selections": [ - v16, - { - "kind": "LinkedField", - "alias": null, - "name": "edges", - "storageKey": null, - "args": null, - "concreteType": "IssueTimelineItemEdge", - "plural": true, - "selections": [ - v17, - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v6, - v26, - v33, - v35 + } ] } ] @@ -1561,7 +866,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v24, + "args": v16, "handle": "connection", "key": "IssueTimelineController_timeline", "filters": null diff --git a/lib/views/__generated__/issueDetailView_issueish.graphql.js b/lib/views/__generated__/issueDetailView_issueish.graphql.js index 7d6f04463b..107a859a9e 100644 --- a/lib/views/__generated__/issueDetailView_issueish.graphql.js +++ b/lib/views/__generated__/issueDetailView_issueish.graphql.js @@ -9,11 +9,7 @@ /*:: import type { ConcreteFragment } from 'relay-runtime'; type issueTimelineController_issue$ref = any; -type prCommitsView_pullRequest$ref = any; -type prStatusesView_pullRequest$ref = any; -type prTimelineController_pullRequest$ref = any; export type IssueState = "CLOSED" | "OPEN" | "%future added value"; -export type PullRequestState = "CLOSED" | "MERGED" | "OPEN" | "%future added value"; export type ReactionContent = "CONFUSED" | "HEART" | "HOORAY" | "LAUGH" | "THUMBS_DOWN" | "THUMBS_UP" | "%future added value"; import type { FragmentReference } from "relay-runtime"; declare export opaque type issueDetailView_issueish$ref: FragmentReference; @@ -36,14 +32,7 @@ export type issueDetailView_issueish = {| +avatarUrl: any, +url?: any, |}, - +isCrossRepository?: boolean, - +changedFiles?: number, - +countedCommits?: {| - +totalCount: number - |}, - +baseRefName?: string, - +headRefName?: string, - +$fragmentRefs: issueTimelineController_issue$ref & prCommitsView_pullRequest$ref & prStatusesView_pullRequest$ref & prTimelineController_pullRequest$ref, + +$fragmentRefs: issueTimelineController_issue$ref, +$refType: issueDetailView_issueish$ref, |}; */ @@ -58,93 +47,7 @@ var v0 = { "storageKey": null }, v1 = [ - { - "kind": "ScalarField", - "alias": null, - "name": "totalCount", - "args": null, - "storageKey": null - } -], -v2 = { - "kind": "ScalarField", - "alias": null, - "name": "number", - "args": null, - "storageKey": null -}, -v3 = { - "kind": "ScalarField", - "alias": null, - "name": "state", - "args": null, - "storageKey": null -}, -v4 = { - "kind": "ScalarField", - "alias": null, - "name": "title", - "args": null, - "storageKey": null -}, -v5 = { - "kind": "ScalarField", - "alias": null, - "name": "bodyHTML", - "args": null, - "storageKey": null -}, -v6 = [ v0 -], -v7 = { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "login", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "avatarUrl", - "args": null, - "storageKey": null - }, - { - "kind": "InlineFragment", - "type": "Bot", - "selections": v6 - }, - { - "kind": "InlineFragment", - "type": "User", - "selections": v6 - } - ] -}, -v8 = [ - { - "kind": "Variable", - "name": "timelineCount", - "variableName": "timelineCount", - "type": null - }, - { - "kind": "Variable", - "name": "timelineCursor", - "variableName": "timelineCursor", - "type": null - } ]; return { "kind": "Fragment", @@ -163,18 +66,6 @@ return { "name": "timelineCursor", "type": "String", "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "commitCount", - "type": "Int!", - "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "commitCursor", - "type": "String", - "defaultValue": null } ], "selections": [ @@ -217,100 +108,102 @@ return { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v1 + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } + ] } ] }, { "kind": "InlineFragment", - "type": "PullRequest", + "type": "Issue", "selections": [ - v2, { "kind": "ScalarField", "alias": null, - "name": "isCrossRepository", + "name": "state", "args": null, "storageKey": null }, - { - "kind": "FragmentSpread", - "name": "prCommitsView_pullRequest", - "args": [ - { - "kind": "Variable", - "name": "commitCount", - "variableName": "commitCount", - "type": null - }, - { - "kind": "Variable", - "name": "commitCursor", - "variableName": "commitCursor", - "type": null - } - ] - }, - { - "kind": "LinkedField", - "alias": "countedCommits", - "name": "commits", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommitConnection", - "plural": false, - "selections": v1 - }, - { - "kind": "FragmentSpread", - "name": "prStatusesView_pullRequest", - "args": null - }, - v3, { "kind": "ScalarField", "alias": null, - "name": "changedFiles", + "name": "number", "args": null, "storageKey": null }, - v4, - v5, { "kind": "ScalarField", "alias": null, - "name": "baseRefName", + "name": "title", "args": null, "storageKey": null }, { "kind": "ScalarField", "alias": null, - "name": "headRefName", + "name": "bodyHTML", "args": null, "storageKey": null }, - v7, { - "kind": "FragmentSpread", - "name": "prTimelineController_pullRequest", - "args": v8 - } - ] - }, - { - "kind": "InlineFragment", - "type": "Issue", - "selections": [ - v3, - v2, - v4, - v5, - v7, + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null + }, + { + "kind": "InlineFragment", + "type": "Bot", + "selections": v1 + }, + { + "kind": "InlineFragment", + "type": "User", + "selections": v1 + } + ] + }, { "kind": "FragmentSpread", "name": "issueTimelineController_issue", - "args": v8 + "args": [ + { + "kind": "Variable", + "name": "timelineCount", + "variableName": "timelineCount", + "type": null + }, + { + "kind": "Variable", + "name": "timelineCursor", + "variableName": "timelineCursor", + "type": null + } + ] } ] } @@ -318,5 +211,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '41157daae98ed2eb4b4128690565a282'; +(node/*: any*/).hash = 'e706bcd2c9ec8c5c57f26c39e38d8159'; module.exports = node; diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 92c46d9cd4..3b4ca463f8 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -222,8 +222,6 @@ export default createRefetchContainer(BareIssueDetailView, { @argumentDefinitions( timelineCount: {type: "Int!"}, timelineCursor: {type: "String"}, - commitCount: {type: "Int!"}, - commitCursor: {type: "String"}, ) { __typename @@ -242,24 +240,6 @@ export default createRefetchContainer(BareIssueDetailView, { ...issueTimelineController_issue @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) } - ... on PullRequest { - isCrossRepository - changedFiles - ...prCommitsView_pullRequest @arguments(commitCount: $commitCount, commitCursor: $commitCursor) - countedCommits: commits { - totalCount - } - ...prStatusesView_pullRequest - state number title bodyHTML baseRefName headRefName - author { - login avatarUrl - ... on User { url } - ... on Bot { url } - } - - ...prTimelineController_pullRequest @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) - } - ... on UniformResourceLocatable { url } ... on Reactable { From bef8fac27d9de1b4657f196e3a67165e56079718 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 16:36:40 -0800 Subject: [PATCH 1151/4053] get rid of some of these graphql console errors --- .../issueishDetailContainerQuery.graphql.js | 8 +- ...eishDetailController_repository.graphql.js | 8 +- lib/controllers/issueish-detail-controller.js | 2 +- .../issueDetailViewRefetchQuery.graphql.js | 192 +- .../issueishDetailViewRefetchQuery.graphql.js | 1579 ----------------- .../issueishDetailView_issueish.graphql.js | 322 ---- .../issueishDetailView_repository.graphql.js | 67 - lib/views/issue-detail-view.js | 7 - lib/views/issueish-detail-view.js | 862 ++++----- 9 files changed, 521 insertions(+), 2526 deletions(-) delete mode 100644 lib/views/__generated__/issueishDetailViewRefetchQuery.graphql.js delete mode 100644 lib/views/__generated__/issueishDetailView_issueish.graphql.js delete mode 100644 lib/views/__generated__/issueishDetailView_repository.graphql.js diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 687124429a..660643501f 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash c7e6ef594b18ba097059ab69750f8863 + * @relayHash e8563250a136d6b88c0c24e7e9660e4e */ /* eslint-disable */ @@ -48,7 +48,7 @@ query issueishDetailContainerQuery( } fragment issueishDetailController_repository_1mXVvq on Repository { - ...issueishDetailView_repository + ...issueDetailView_repository name owner { __typename @@ -85,7 +85,7 @@ fragment issueishDetailController_repository_1mXVvq on Repository { } } -fragment issueishDetailView_repository on Repository { +fragment issueDetailView_repository on Repository { id name owner { @@ -1036,7 +1036,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueishDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueishDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", diff --git a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js index 3efd3d66ff..8f5d3bb81d 100644 --- a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js +++ b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js @@ -9,7 +9,7 @@ /*:: import type { ConcreteFragment } from 'relay-runtime'; type issueDetailView_issueish$ref = any; -type issueishDetailView_repository$ref = any; +type issueDetailView_repository$ref = any; type prDetailView_issueish$ref = any; import type { FragmentReference } from "relay-runtime"; declare export opaque type issueishDetailController_repository$ref: FragmentReference; @@ -40,7 +40,7 @@ export type issueishDetailController_repository = {| // value in case none of the concrete values match. +__typename: "%other" |}), - +$fragmentRefs: issueishDetailView_repository$ref, + +$fragmentRefs: issueDetailView_repository$ref, +$refType: issueishDetailController_repository$ref, |}; */ @@ -152,7 +152,7 @@ return { "selections": [ { "kind": "FragmentSpread", - "name": "issueishDetailView_repository", + "name": "issueDetailView_repository", "args": null }, v0, @@ -246,5 +246,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'ab44d7d5d91ffcd4d01a304fe8e33a2b'; +(node/*: any*/).hash = 'fd149181bc7418ba5e93d2dd7a89dac1'; module.exports = node; diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index ad038627eb..56bc46e673 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -224,7 +224,7 @@ export default createFragmentContainer(BareIssueishDetailController, { commitCount: {type: "Int!"}, commitCursor: {type: "String"}, ) { - ...issueishDetailView_repository + ...issueDetailView_repository name owner { login diff --git a/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js index 3eb6fa8f10..ada45fdd44 100644 --- a/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 0852b97ef8d8e53f978f1538930bce64 + * @relayHash e162404175c32a7cee36aebd46b673c9 */ /* eslint-disable */ @@ -16,8 +16,6 @@ export type issueDetailViewRefetchQueryVariables = {| issueishId: string, timelineCount: number, timelineCursor?: ?string, - commitCount: number, - commitCursor?: ?string, |}; export type issueDetailViewRefetchQueryResponse = {| +repository: ?{| @@ -48,7 +46,7 @@ query issueDetailViewRefetchQuery( } issueish: node(id: $issueishId) { __typename - ...issueDetailView_issueish_4cAEh0 + ...issueDetailView_issueish_3D8CP9 id } } @@ -63,7 +61,7 @@ fragment issueDetailView_repository_3D8CP9 on Repository { } } -fragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest { +fragment issueDetailView_issueish_3D8CP9 on IssueOrPullRequest { __typename ... on Node { id @@ -267,18 +265,6 @@ var v0 = [ "name": "timelineCursor", "type": "String", "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "commitCount", - "type": "Int!", - "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "commitCursor", - "type": "String", - "defaultValue": null } ], v1 = [ @@ -289,19 +275,21 @@ v1 = [ "type": "ID!" } ], -v2 = { - "kind": "Variable", - "name": "timelineCount", - "variableName": "timelineCount", - "type": null -}, -v3 = { - "kind": "Variable", - "name": "timelineCursor", - "variableName": "timelineCursor", - "type": null -}, -v4 = [ +v2 = [ + { + "kind": "Variable", + "name": "timelineCount", + "variableName": "timelineCount", + "type": null + }, + { + "kind": "Variable", + "name": "timelineCursor", + "variableName": "timelineCursor", + "type": null + } +], +v3 = [ { "kind": "Variable", "name": "id", @@ -309,35 +297,35 @@ v4 = [ "type": "ID!" } ], -v5 = { +v4 = { "kind": "ScalarField", "alias": null, "name": "__typename", "args": null, "storageKey": null }, -v6 = { +v5 = { "kind": "ScalarField", "alias": null, "name": "id", "args": null, "storageKey": null }, -v7 = { +v6 = { "kind": "ScalarField", "alias": null, "name": "name", "args": null, "storageKey": null }, -v8 = { +v7 = { "kind": "ScalarField", "alias": null, "name": "login", "args": null, "storageKey": null }, -v9 = { +v8 = { "kind": "LinkedField", "alias": null, "name": "owner", @@ -346,50 +334,50 @@ v9 = { "concreteType": null, "plural": false, "selections": [ - v5, - v8, - v6 + v4, + v7, + v5 ] }, -v10 = { +v9 = { "kind": "ScalarField", "alias": null, "name": "url", "args": null, "storageKey": null }, -v11 = { +v10 = { "kind": "ScalarField", "alias": null, "name": "number", "args": null, "storageKey": null }, -v12 = { +v11 = { "kind": "ScalarField", "alias": null, "name": "title", "args": null, "storageKey": null }, -v13 = { +v12 = { "kind": "ScalarField", "alias": null, "name": "bodyHTML", "args": null, "storageKey": null }, -v14 = { +v13 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", "args": null, "storageKey": null }, -v15 = [ - v10 +v14 = [ + v9 ], -v16 = [ +v15 = [ { "kind": "Variable", "name": "after", @@ -403,7 +391,7 @@ v16 = [ "type": "Int" } ], -v17 = { +v16 = { "kind": "LinkedField", "alias": null, "name": "user", @@ -412,8 +400,8 @@ v17 = { "concreteType": "User", "plural": false, "selections": [ - v8, - v6 + v7, + v5 ] }; return { @@ -421,7 +409,7 @@ return { "operationKind": "query", "name": "issueDetailViewRefetchQuery", "id": null, - "text": "query issueDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...issueDetailView_issueish_4cAEh0\n id\n }\n}\n\nfragment issueDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", + "text": "query issueDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...issueDetailView_issueish_3D8CP9\n id\n }\n}\n\nfragment issueDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_3D8CP9 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -442,10 +430,7 @@ return { { "kind": "FragmentSpread", "name": "issueDetailView_repository", - "args": [ - v2, - v3 - ] + "args": v2 } ] }, @@ -454,29 +439,14 @@ return { "alias": "issueish", "name": "node", "storageKey": null, - "args": v4, + "args": v3, "concreteType": null, "plural": false, "selections": [ { "kind": "FragmentSpread", "name": "issueDetailView_issueish", - "args": [ - { - "kind": "Variable", - "name": "commitCount", - "variableName": "commitCount", - "type": null - }, - { - "kind": "Variable", - "name": "commitCursor", - "variableName": "commitCursor", - "type": null - }, - v2, - v3 - ] + "args": v2 } ] } @@ -496,14 +466,14 @@ return { "concreteType": null, "plural": false, "selections": [ + v4, v5, - v6, { "kind": "InlineFragment", "type": "Repository", "selections": [ - v7, - v9 + v6, + v8 ] } ] @@ -513,13 +483,13 @@ return { "alias": "issueish", "name": "node", "storageKey": null, - "args": v4, + "args": v3, "concreteType": null, "plural": false, "selections": [ + v4, v5, - v6, - v10, + v9, { "kind": "LinkedField", "alias": null, @@ -567,9 +537,9 @@ return { "args": null, "storageKey": null }, + v10, v11, v12, - v13, { "kind": "LinkedField", "alias": null, @@ -579,19 +549,19 @@ return { "concreteType": null, "plural": false, "selections": [ + v4, + v7, + v13, v5, - v8, - v14, - v6, { "kind": "InlineFragment", "type": "Bot", - "selections": v15 + "selections": v14 }, { "kind": "InlineFragment", "type": "User", - "selections": v15 + "selections": v14 } ] }, @@ -600,7 +570,7 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v16, + "args": v15, "concreteType": "IssueTimelineConnection", "plural": false, "selections": [ @@ -654,8 +624,8 @@ return { "concreteType": null, "plural": false, "selections": [ + v4, v5, - v6, { "kind": "InlineFragment", "type": "CrossReferencedEvent", @@ -683,10 +653,10 @@ return { "concreteType": null, "plural": false, "selections": [ - v5, - v8, - v14, - v6 + v4, + v7, + v13, + v5 ] }, { @@ -698,7 +668,7 @@ return { "concreteType": null, "plural": false, "selections": [ - v5, + v4, { "kind": "LinkedField", "alias": null, @@ -708,9 +678,9 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v7, - v9, v6, + v8, + v5, { "kind": "ScalarField", "alias": null, @@ -720,14 +690,14 @@ return { } ] }, - v6, + v5, { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v11, - v12, v10, + v11, + v9, { "kind": "ScalarField", "alias": "prState", @@ -741,9 +711,9 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v11, - v12, v10, + v11, + v9, { "kind": "ScalarField", "alias": "issueState", @@ -770,13 +740,13 @@ return { "concreteType": null, "plural": false, "selections": [ - v5, - v14, - v8, - v6 + v4, + v13, + v7, + v5 ] }, - v13, + v12, { "kind": "ScalarField", "alias": null, @@ -784,7 +754,7 @@ return { "args": null, "storageKey": null }, - v10 + v9 ] }, { @@ -800,9 +770,9 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v7, - v17, - v14 + v6, + v16, + v13 ] }, { @@ -814,9 +784,9 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v7, - v14, - v17 + v6, + v13, + v16 ] }, { @@ -866,7 +836,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v16, + "args": v15, "handle": "connection", "key": "IssueTimelineController_timeline", "filters": null @@ -880,5 +850,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '3440598bd522ed93e9e1ca4bbf729225'; +(node/*: any*/).hash = 'ae08e0a6982589986e579be429b2a460'; module.exports = node; diff --git a/lib/views/__generated__/issueishDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/issueishDetailViewRefetchQuery.graphql.js deleted file mode 100644 index 3b5eeaf1e0..0000000000 --- a/lib/views/__generated__/issueishDetailViewRefetchQuery.graphql.js +++ /dev/null @@ -1,1579 +0,0 @@ -/** - * @flow - * @relayHash 2918a11cbaa8f9b4f358a3913dab315c - */ - -/* eslint-disable */ - -'use strict'; - -/*:: -import type { ConcreteRequest } from 'relay-runtime'; -type issueishDetailView_issueish$ref = any; -type issueishDetailView_repository$ref = any; -export type issueishDetailViewRefetchQueryVariables = {| - repoId: string, - issueishId: string, - timelineCount: number, - timelineCursor?: ?string, - commitCount: number, - commitCursor?: ?string, -|}; -export type issueishDetailViewRefetchQueryResponse = {| - +repository: ?{| - +$fragmentRefs: issueishDetailView_repository$ref - |}, - +issueish: ?{| - +$fragmentRefs: issueishDetailView_issueish$ref - |}, -|}; -export type issueishDetailViewRefetchQuery = {| - variables: issueishDetailViewRefetchQueryVariables, - response: issueishDetailViewRefetchQueryResponse, -|}; -*/ - - -/* -query issueishDetailViewRefetchQuery( - $repoId: ID! - $issueishId: ID! - $timelineCount: Int! - $timelineCursor: String - $commitCount: Int! - $commitCursor: String -) { - repository: node(id: $repoId) { - __typename - ...issueishDetailView_repository_3D8CP9 - id - } - issueish: node(id: $issueishId) { - __typename - ...issueishDetailView_issueish_4cAEh0 - id - } -} - -fragment issueishDetailView_repository_3D8CP9 on Repository { - id - name - owner { - __typename - login - id - } -} - -fragment issueishDetailView_issueish_4cAEh0 on IssueOrPullRequest { - __typename - ... on Node { - id - } - ... on Issue { - state - number - title - bodyHTML - author { - __typename - login - avatarUrl - ... on User { - url - } - ... on Bot { - url - } - ... on Node { - id - } - } - ...issueTimelineController_issue_3D8CP9 - } - ... on PullRequest { - isCrossRepository - changedFiles - ...prCommitsView_pullRequest_38TpXw - countedCommits: commits { - totalCount - } - ...prStatusesView_pullRequest - state - number - title - bodyHTML - baseRefName - headRefName - author { - __typename - login - avatarUrl - ... on User { - url - } - ... on Bot { - url - } - ... on Node { - id - } - } - ...prTimelineController_pullRequest_3D8CP9 - } - ... on UniformResourceLocatable { - url - } - ... on Reactable { - reactionGroups { - content - users { - totalCount - } - } - } -} - -fragment issueTimelineController_issue_3D8CP9 on Issue { - url - timeline(first: $timelineCount, after: $timelineCursor) { - pageInfo { - endCursor - hasNextPage - } - edges { - cursor - node { - __typename - ...commitsView_nodes - ...issueCommentView_item - ...crossReferencedEventsView_nodes - ... on Node { - id - } - } - } - } -} - -fragment prCommitsView_pullRequest_38TpXw on PullRequest { - url - commits(first: $commitCount, after: $commitCursor) { - pageInfo { - endCursor - hasNextPage - } - edges { - cursor - node { - commit { - id - ...prCommitView_item - } - id - __typename - } - } - } -} - -fragment prStatusesView_pullRequest on PullRequest { - id - recentCommits: commits(last: 1) { - edges { - node { - commit { - status { - state - contexts { - id - state - ...prStatusContextView_context - } - id - } - id - } - id - } - } - } -} - -fragment prTimelineController_pullRequest_3D8CP9 on PullRequest { - url - ...headRefForcePushedEventView_issueish - timeline(first: $timelineCount, after: $timelineCursor) { - pageInfo { - endCursor - hasNextPage - } - edges { - cursor - node { - __typename - ...commitsView_nodes - ...issueCommentView_item - ...mergedEventView_item - ...headRefForcePushedEventView_item - ...commitCommentThreadView_item - ...crossReferencedEventsView_nodes - ... on Node { - id - } - } - } - } -} - -fragment headRefForcePushedEventView_issueish on PullRequest { - headRefName - headRepositoryOwner { - __typename - login - id - } - repository { - owner { - __typename - login - id - } - id - } -} - -fragment commitsView_nodes on Commit { - id - author { - name - user { - login - id - } - } - ...commitView_item -} - -fragment issueCommentView_item on IssueComment { - author { - __typename - avatarUrl - login - ... on Node { - id - } - } - bodyHTML - createdAt - url -} - -fragment mergedEventView_item on MergedEvent { - actor { - __typename - avatarUrl - login - ... on Node { - id - } - } - commit { - oid - id - } - mergeRefName - createdAt -} - -fragment headRefForcePushedEventView_item on HeadRefForcePushedEvent { - actor { - __typename - avatarUrl - login - ... on Node { - id - } - } - beforeCommit { - oid - id - } - afterCommit { - oid - id - } - createdAt -} - -fragment commitCommentThreadView_item on CommitCommentThread { - commit { - oid - id - } - comments(first: 100) { - edges { - node { - id - ...commitCommentView_item - } - } - } -} - -fragment crossReferencedEventsView_nodes on CrossReferencedEvent { - id - referencedAt - isCrossRepository - actor { - __typename - login - avatarUrl - ... on Node { - id - } - } - source { - __typename - ... on RepositoryNode { - repository { - name - owner { - __typename - login - id - } - id - } - } - ... on Node { - id - } - } - ...crossReferencedEventView_item -} - -fragment crossReferencedEventView_item on CrossReferencedEvent { - id - isCrossRepository - source { - __typename - ... on Issue { - number - title - url - issueState: state - } - ... on PullRequest { - number - title - url - prState: state - } - ... on RepositoryNode { - repository { - name - isPrivate - owner { - __typename - login - id - } - id - } - } - ... on Node { - id - } - } -} - -fragment commitCommentView_item on CommitComment { - author { - __typename - login - avatarUrl - ... on Node { - id - } - } - commit { - oid - id - } - bodyHTML - createdAt - path - position -} - -fragment commitView_item on Commit { - author { - name - avatarUrl - user { - login - id - } - } - committer { - name - avatarUrl - user { - login - id - } - } - authoredByCommitter - oid - message - messageHeadlineHTML - commitUrl -} - -fragment prStatusContextView_context on StatusContext { - context - description - state - targetUrl -} - -fragment prCommitView_item on Commit { - committer { - avatarUrl - name - date - } - messageHeadline - messageBody - abbreviatedOid - url -} -*/ - -const node/*: ConcreteRequest*/ = (function(){ -var v0 = [ - { - "kind": "LocalArgument", - "name": "repoId", - "type": "ID!", - "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "issueishId", - "type": "ID!", - "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "timelineCount", - "type": "Int!", - "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "timelineCursor", - "type": "String", - "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "commitCount", - "type": "Int!", - "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "commitCursor", - "type": "String", - "defaultValue": null - } -], -v1 = [ - { - "kind": "Variable", - "name": "id", - "variableName": "repoId", - "type": "ID!" - } -], -v2 = { - "kind": "Variable", - "name": "timelineCount", - "variableName": "timelineCount", - "type": null -}, -v3 = { - "kind": "Variable", - "name": "timelineCursor", - "variableName": "timelineCursor", - "type": null -}, -v4 = [ - { - "kind": "Variable", - "name": "id", - "variableName": "issueishId", - "type": "ID!" - } -], -v5 = { - "kind": "ScalarField", - "alias": null, - "name": "__typename", - "args": null, - "storageKey": null -}, -v6 = { - "kind": "ScalarField", - "alias": null, - "name": "id", - "args": null, - "storageKey": null -}, -v7 = { - "kind": "ScalarField", - "alias": null, - "name": "name", - "args": null, - "storageKey": null -}, -v8 = { - "kind": "ScalarField", - "alias": null, - "name": "login", - "args": null, - "storageKey": null -}, -v9 = [ - v5, - v8, - v6 -], -v10 = { - "kind": "LinkedField", - "alias": null, - "name": "owner", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v9 -}, -v11 = { - "kind": "ScalarField", - "alias": null, - "name": "url", - "args": null, - "storageKey": null -}, -v12 = [ - { - "kind": "ScalarField", - "alias": null, - "name": "totalCount", - "args": null, - "storageKey": null - } -], -v13 = { - "kind": "ScalarField", - "alias": null, - "name": "number", - "args": null, - "storageKey": null -}, -v14 = { - "kind": "ScalarField", - "alias": null, - "name": "isCrossRepository", - "args": null, - "storageKey": null -}, -v15 = [ - { - "kind": "Variable", - "name": "after", - "variableName": "commitCursor", - "type": "String" - }, - { - "kind": "Variable", - "name": "first", - "variableName": "commitCount", - "type": "Int" - } -], -v16 = { - "kind": "LinkedField", - "alias": null, - "name": "pageInfo", - "storageKey": null, - "args": null, - "concreteType": "PageInfo", - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "endCursor", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "hasNextPage", - "args": null, - "storageKey": null - } - ] -}, -v17 = { - "kind": "ScalarField", - "alias": null, - "name": "cursor", - "args": null, - "storageKey": null -}, -v18 = { - "kind": "ScalarField", - "alias": null, - "name": "avatarUrl", - "args": null, - "storageKey": null -}, -v19 = { - "kind": "ScalarField", - "alias": null, - "name": "state", - "args": null, - "storageKey": null -}, -v20 = { - "kind": "ScalarField", - "alias": null, - "name": "title", - "args": null, - "storageKey": null -}, -v21 = { - "kind": "ScalarField", - "alias": null, - "name": "bodyHTML", - "args": null, - "storageKey": null -}, -v22 = [ - v11 -], -v23 = { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v8, - v18, - v6, - { - "kind": "InlineFragment", - "type": "Bot", - "selections": v22 - }, - { - "kind": "InlineFragment", - "type": "User", - "selections": v22 - } - ] -}, -v24 = [ - { - "kind": "Variable", - "name": "after", - "variableName": "timelineCursor", - "type": "String" - }, - { - "kind": "Variable", - "name": "first", - "variableName": "timelineCount", - "type": "Int" - } -], -v25 = [ - v5, - v8, - v18, - v6 -], -v26 = { - "kind": "InlineFragment", - "type": "CrossReferencedEvent", - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "referencedAt", - "args": null, - "storageKey": null - }, - v14, - { - "kind": "LinkedField", - "alias": null, - "name": "actor", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v25 - }, - { - "kind": "LinkedField", - "alias": null, - "name": "source", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - { - "kind": "LinkedField", - "alias": null, - "name": "repository", - "storageKey": null, - "args": null, - "concreteType": "Repository", - "plural": false, - "selections": [ - v7, - v10, - v6, - { - "kind": "ScalarField", - "alias": null, - "name": "isPrivate", - "args": null, - "storageKey": null - } - ] - }, - v6, - { - "kind": "InlineFragment", - "type": "PullRequest", - "selections": [ - v13, - v20, - v11, - { - "kind": "ScalarField", - "alias": "prState", - "name": "state", - "args": null, - "storageKey": null - } - ] - }, - { - "kind": "InlineFragment", - "type": "Issue", - "selections": [ - v13, - v20, - v11, - { - "kind": "ScalarField", - "alias": "issueState", - "name": "state", - "args": null, - "storageKey": null - } - ] - } - ] - } - ] -}, -v27 = { - "kind": "ScalarField", - "alias": null, - "name": "oid", - "args": null, - "storageKey": null -}, -v28 = [ - v27, - v6 -], -v29 = { - "kind": "LinkedField", - "alias": null, - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v28 -}, -v30 = { - "kind": "ScalarField", - "alias": null, - "name": "createdAt", - "args": null, - "storageKey": null -}, -v31 = [ - v5, - v18, - v8, - v6 -], -v32 = { - "kind": "LinkedField", - "alias": null, - "name": "actor", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v31 -}, -v33 = { - "kind": "InlineFragment", - "type": "IssueComment", - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v31 - }, - v21, - v30, - v11 - ] -}, -v34 = { - "kind": "LinkedField", - "alias": null, - "name": "user", - "storageKey": null, - "args": null, - "concreteType": "User", - "plural": false, - "selections": [ - v8, - v6 - ] -}, -v35 = { - "kind": "InlineFragment", - "type": "Commit", - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": "GitActor", - "plural": false, - "selections": [ - v7, - v34, - v18 - ] - }, - { - "kind": "LinkedField", - "alias": null, - "name": "committer", - "storageKey": null, - "args": null, - "concreteType": "GitActor", - "plural": false, - "selections": [ - v7, - v18, - v34 - ] - }, - { - "kind": "ScalarField", - "alias": null, - "name": "authoredByCommitter", - "args": null, - "storageKey": null - }, - v27, - { - "kind": "ScalarField", - "alias": null, - "name": "message", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "messageHeadlineHTML", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "commitUrl", - "args": null, - "storageKey": null - } - ] -}; -return { - "kind": "Request", - "operationKind": "query", - "name": "issueishDetailViewRefetchQuery", - "id": null, - "text": "query issueishDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueishDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...issueishDetailView_issueish_4cAEh0\n id\n }\n}\n\nfragment issueishDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueishDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", - "metadata": {}, - "fragment": { - "kind": "Fragment", - "name": "issueishDetailViewRefetchQuery", - "type": "Query", - "metadata": null, - "argumentDefinitions": v0, - "selections": [ - { - "kind": "LinkedField", - "alias": "repository", - "name": "node", - "storageKey": null, - "args": v1, - "concreteType": null, - "plural": false, - "selections": [ - { - "kind": "FragmentSpread", - "name": "issueishDetailView_repository", - "args": [ - v2, - v3 - ] - } - ] - }, - { - "kind": "LinkedField", - "alias": "issueish", - "name": "node", - "storageKey": null, - "args": v4, - "concreteType": null, - "plural": false, - "selections": [ - { - "kind": "FragmentSpread", - "name": "issueishDetailView_issueish", - "args": [ - { - "kind": "Variable", - "name": "commitCount", - "variableName": "commitCount", - "type": null - }, - { - "kind": "Variable", - "name": "commitCursor", - "variableName": "commitCursor", - "type": null - }, - v2, - v3 - ] - } - ] - } - ] - }, - "operation": { - "kind": "Operation", - "name": "issueishDetailViewRefetchQuery", - "argumentDefinitions": v0, - "selections": [ - { - "kind": "LinkedField", - "alias": "repository", - "name": "node", - "storageKey": null, - "args": v1, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v6, - { - "kind": "InlineFragment", - "type": "Repository", - "selections": [ - v7, - v10 - ] - } - ] - }, - { - "kind": "LinkedField", - "alias": "issueish", - "name": "node", - "storageKey": null, - "args": v4, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v6, - v11, - { - "kind": "LinkedField", - "alias": null, - "name": "reactionGroups", - "storageKey": null, - "args": null, - "concreteType": "ReactionGroup", - "plural": true, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "content", - "args": null, - "storageKey": null - }, - { - "kind": "LinkedField", - "alias": null, - "name": "users", - "storageKey": null, - "args": null, - "concreteType": "ReactingUserConnection", - "plural": false, - "selections": v12 - } - ] - }, - { - "kind": "InlineFragment", - "type": "PullRequest", - "selections": [ - v13, - v14, - { - "kind": "LinkedField", - "alias": null, - "name": "commits", - "storageKey": null, - "args": v15, - "concreteType": "PullRequestCommitConnection", - "plural": false, - "selections": [ - v16, - { - "kind": "LinkedField", - "alias": null, - "name": "edges", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommitEdge", - "plural": true, - "selections": [ - v17, - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommit", - "plural": false, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": [ - v6, - { - "kind": "LinkedField", - "alias": null, - "name": "committer", - "storageKey": null, - "args": null, - "concreteType": "GitActor", - "plural": false, - "selections": [ - v18, - v7, - { - "kind": "ScalarField", - "alias": null, - "name": "date", - "args": null, - "storageKey": null - } - ] - }, - { - "kind": "ScalarField", - "alias": null, - "name": "messageHeadline", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "messageBody", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "abbreviatedOid", - "args": null, - "storageKey": null - }, - v11 - ] - }, - v6, - v5 - ] - } - ] - } - ] - }, - { - "kind": "LinkedHandle", - "alias": null, - "name": "commits", - "args": v15, - "handle": "connection", - "key": "prCommitsView_commits", - "filters": null - }, - { - "kind": "LinkedField", - "alias": "countedCommits", - "name": "commits", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommitConnection", - "plural": false, - "selections": v12 - }, - { - "kind": "LinkedField", - "alias": "recentCommits", - "name": "commits", - "storageKey": "commits(last:1)", - "args": [ - { - "kind": "Literal", - "name": "last", - "value": 1, - "type": "Int" - } - ], - "concreteType": "PullRequestCommitConnection", - "plural": false, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "edges", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommitEdge", - "plural": true, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommit", - "plural": false, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "status", - "storageKey": null, - "args": null, - "concreteType": "Status", - "plural": false, - "selections": [ - v19, - { - "kind": "LinkedField", - "alias": null, - "name": "contexts", - "storageKey": null, - "args": null, - "concreteType": "StatusContext", - "plural": true, - "selections": [ - v6, - v19, - { - "kind": "ScalarField", - "alias": null, - "name": "context", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "description", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "targetUrl", - "args": null, - "storageKey": null - } - ] - }, - v6 - ] - }, - v6 - ] - }, - v6 - ] - } - ] - } - ] - }, - v19, - { - "kind": "ScalarField", - "alias": null, - "name": "changedFiles", - "args": null, - "storageKey": null - }, - v20, - v21, - { - "kind": "ScalarField", - "alias": null, - "name": "baseRefName", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "headRefName", - "args": null, - "storageKey": null - }, - v23, - { - "kind": "LinkedField", - "alias": null, - "name": "headRepositoryOwner", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v9 - }, - { - "kind": "LinkedField", - "alias": null, - "name": "repository", - "storageKey": null, - "args": null, - "concreteType": "Repository", - "plural": false, - "selections": [ - v10, - v6 - ] - }, - { - "kind": "LinkedField", - "alias": null, - "name": "timeline", - "storageKey": null, - "args": v24, - "concreteType": "PullRequestTimelineConnection", - "plural": false, - "selections": [ - v16, - { - "kind": "LinkedField", - "alias": null, - "name": "edges", - "storageKey": null, - "args": null, - "concreteType": "PullRequestTimelineItemEdge", - "plural": true, - "selections": [ - v17, - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v6, - v26, - { - "kind": "InlineFragment", - "type": "CommitCommentThread", - "selections": [ - v29, - { - "kind": "LinkedField", - "alias": null, - "name": "comments", - "storageKey": "comments(first:100)", - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 100, - "type": "Int" - } - ], - "concreteType": "CommitCommentConnection", - "plural": false, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "edges", - "storageKey": null, - "args": null, - "concreteType": "CommitCommentEdge", - "plural": true, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": "CommitComment", - "plural": false, - "selections": [ - v6, - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v25 - }, - v29, - v21, - v30, - { - "kind": "ScalarField", - "alias": null, - "name": "path", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "position", - "args": null, - "storageKey": null - } - ] - } - ] - } - ] - } - ] - }, - { - "kind": "InlineFragment", - "type": "HeadRefForcePushedEvent", - "selections": [ - v32, - { - "kind": "LinkedField", - "alias": null, - "name": "beforeCommit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v28 - }, - { - "kind": "LinkedField", - "alias": null, - "name": "afterCommit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v28 - }, - v30 - ] - }, - { - "kind": "InlineFragment", - "type": "MergedEvent", - "selections": [ - v32, - v29, - { - "kind": "ScalarField", - "alias": null, - "name": "mergeRefName", - "args": null, - "storageKey": null - }, - v30 - ] - }, - v33, - v35 - ] - } - ] - } - ] - }, - { - "kind": "LinkedHandle", - "alias": null, - "name": "timeline", - "args": v24, - "handle": "connection", - "key": "prTimelineContainer_timeline", - "filters": null - } - ] - }, - { - "kind": "InlineFragment", - "type": "Issue", - "selections": [ - v19, - v13, - v20, - v21, - v23, - { - "kind": "LinkedField", - "alias": null, - "name": "timeline", - "storageKey": null, - "args": v24, - "concreteType": "IssueTimelineConnection", - "plural": false, - "selections": [ - v16, - { - "kind": "LinkedField", - "alias": null, - "name": "edges", - "storageKey": null, - "args": null, - "concreteType": "IssueTimelineItemEdge", - "plural": true, - "selections": [ - v17, - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v6, - v26, - v33, - v35 - ] - } - ] - } - ] - }, - { - "kind": "LinkedHandle", - "alias": null, - "name": "timeline", - "args": v24, - "handle": "connection", - "key": "IssueTimelineController_timeline", - "filters": null - } - ] - } - ] - } - ] - } -}; -})(); -// prettier-ignore -(node/*: any*/).hash = '5bd2024f45eb948618868b403f1c29f9'; -module.exports = node; diff --git a/lib/views/__generated__/issueishDetailView_issueish.graphql.js b/lib/views/__generated__/issueishDetailView_issueish.graphql.js deleted file mode 100644 index af53c2eff7..0000000000 --- a/lib/views/__generated__/issueishDetailView_issueish.graphql.js +++ /dev/null @@ -1,322 +0,0 @@ -/** - * @flow - */ - -/* eslint-disable */ - -'use strict'; - -/*:: -import type { ConcreteFragment } from 'relay-runtime'; -type issueTimelineController_issue$ref = any; -type prCommitsView_pullRequest$ref = any; -type prStatusesView_pullRequest$ref = any; -type prTimelineController_pullRequest$ref = any; -export type IssueState = "CLOSED" | "OPEN" | "%future added value"; -export type PullRequestState = "CLOSED" | "MERGED" | "OPEN" | "%future added value"; -export type ReactionContent = "CONFUSED" | "HEART" | "HOORAY" | "LAUGH" | "THUMBS_DOWN" | "THUMBS_UP" | "%future added value"; -import type { FragmentReference } from "relay-runtime"; -declare export opaque type issueishDetailView_issueish$ref: FragmentReference; -export type issueishDetailView_issueish = {| - +__typename: string, - +id?: string, - +url?: any, - +reactionGroups?: ?$ReadOnlyArray<{| - +content: ReactionContent, - +users: {| - +totalCount: number - |}, - |}>, - +state?: IssueState, - +number?: number, - +title?: string, - +bodyHTML?: any, - +author?: ?{| - +login: string, - +avatarUrl: any, - +url?: any, - |}, - +isCrossRepository?: boolean, - +changedFiles?: number, - +countedCommits?: {| - +totalCount: number - |}, - +baseRefName?: string, - +headRefName?: string, - +$fragmentRefs: issueTimelineController_issue$ref & prCommitsView_pullRequest$ref & prStatusesView_pullRequest$ref & prTimelineController_pullRequest$ref, - +$refType: issueishDetailView_issueish$ref, -|}; -*/ - - -const node/*: ConcreteFragment*/ = (function(){ -var v0 = { - "kind": "ScalarField", - "alias": null, - "name": "url", - "args": null, - "storageKey": null -}, -v1 = [ - { - "kind": "ScalarField", - "alias": null, - "name": "totalCount", - "args": null, - "storageKey": null - } -], -v2 = { - "kind": "ScalarField", - "alias": null, - "name": "number", - "args": null, - "storageKey": null -}, -v3 = { - "kind": "ScalarField", - "alias": null, - "name": "state", - "args": null, - "storageKey": null -}, -v4 = { - "kind": "ScalarField", - "alias": null, - "name": "title", - "args": null, - "storageKey": null -}, -v5 = { - "kind": "ScalarField", - "alias": null, - "name": "bodyHTML", - "args": null, - "storageKey": null -}, -v6 = [ - v0 -], -v7 = { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "login", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "avatarUrl", - "args": null, - "storageKey": null - }, - { - "kind": "InlineFragment", - "type": "Bot", - "selections": v6 - }, - { - "kind": "InlineFragment", - "type": "User", - "selections": v6 - } - ] -}, -v8 = [ - { - "kind": "Variable", - "name": "timelineCount", - "variableName": "timelineCount", - "type": null - }, - { - "kind": "Variable", - "name": "timelineCursor", - "variableName": "timelineCursor", - "type": null - } -]; -return { - "kind": "Fragment", - "name": "issueishDetailView_issueish", - "type": "IssueOrPullRequest", - "metadata": null, - "argumentDefinitions": [ - { - "kind": "LocalArgument", - "name": "timelineCount", - "type": "Int!", - "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "timelineCursor", - "type": "String", - "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "commitCount", - "type": "Int!", - "defaultValue": null - }, - { - "kind": "LocalArgument", - "name": "commitCursor", - "type": "String", - "defaultValue": null - } - ], - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "__typename", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "id", - "args": null, - "storageKey": null - }, - v0, - { - "kind": "LinkedField", - "alias": null, - "name": "reactionGroups", - "storageKey": null, - "args": null, - "concreteType": "ReactionGroup", - "plural": true, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "content", - "args": null, - "storageKey": null - }, - { - "kind": "LinkedField", - "alias": null, - "name": "users", - "storageKey": null, - "args": null, - "concreteType": "ReactingUserConnection", - "plural": false, - "selections": v1 - } - ] - }, - { - "kind": "InlineFragment", - "type": "PullRequest", - "selections": [ - v2, - { - "kind": "ScalarField", - "alias": null, - "name": "isCrossRepository", - "args": null, - "storageKey": null - }, - { - "kind": "FragmentSpread", - "name": "prCommitsView_pullRequest", - "args": [ - { - "kind": "Variable", - "name": "commitCount", - "variableName": "commitCount", - "type": null - }, - { - "kind": "Variable", - "name": "commitCursor", - "variableName": "commitCursor", - "type": null - } - ] - }, - { - "kind": "LinkedField", - "alias": "countedCommits", - "name": "commits", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommitConnection", - "plural": false, - "selections": v1 - }, - { - "kind": "FragmentSpread", - "name": "prStatusesView_pullRequest", - "args": null - }, - v3, - { - "kind": "ScalarField", - "alias": null, - "name": "changedFiles", - "args": null, - "storageKey": null - }, - v4, - v5, - { - "kind": "ScalarField", - "alias": null, - "name": "baseRefName", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "headRefName", - "args": null, - "storageKey": null - }, - v7, - { - "kind": "FragmentSpread", - "name": "prTimelineController_pullRequest", - "args": v8 - } - ] - }, - { - "kind": "InlineFragment", - "type": "Issue", - "selections": [ - v3, - v2, - v4, - v5, - v7, - { - "kind": "FragmentSpread", - "name": "issueTimelineController_issue", - "args": v8 - } - ] - } - ] -}; -})(); -// prettier-ignore -(node/*: any*/).hash = 'c24859a915333972a63ccb57c0c937f0'; -module.exports = node; diff --git a/lib/views/__generated__/issueishDetailView_repository.graphql.js b/lib/views/__generated__/issueishDetailView_repository.graphql.js deleted file mode 100644 index 7276c2e12e..0000000000 --- a/lib/views/__generated__/issueishDetailView_repository.graphql.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @flow - */ - -/* eslint-disable */ - -'use strict'; - -/*:: -import type { ConcreteFragment } from 'relay-runtime'; -import type { FragmentReference } from "relay-runtime"; -declare export opaque type issueishDetailView_repository$ref: FragmentReference; -export type issueishDetailView_repository = {| - +id: string, - +name: string, - +owner: {| - +login: string - |}, - +$refType: issueishDetailView_repository$ref, -|}; -*/ - - -const node/*: ConcreteFragment*/ = { - "kind": "Fragment", - "name": "issueishDetailView_repository", - "type": "Repository", - "metadata": null, - "argumentDefinitions": [], - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "id", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "name", - "args": null, - "storageKey": null - }, - { - "kind": "LinkedField", - "alias": null, - "name": "owner", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "login", - "args": null, - "storageKey": null - } - ] - } - ] -}; -// prettier-ignore -(node/*: any*/).hash = '816e09e12a5fbb89558b0ae0bfdc76af'; -module.exports = node; diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 3b4ca463f8..04343d636f 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -37,9 +37,6 @@ export class BareIssueDetailView extends React.Component { __typename: PropTypes.string.isRequired, id: PropTypes.string.isRequired, title: PropTypes.string, - countedCommits: PropTypes.shape({ - totalCount: PropTypes.number.isRequired, - }).isRequired, isCrossRepository: PropTypes.bool, changedFiles: PropTypes.number.isRequired, url: PropTypes.string.isRequired, @@ -256,8 +253,6 @@ export default createRefetchContainer(BareIssueDetailView, { $issueishId: ID!, $timelineCount: Int!, $timelineCursor: String, - $commitCount: Int!, - $commitCursor: String ) { repository:node(id: $repoId) { ...issueDetailView_repository @arguments( @@ -270,8 +265,6 @@ export default createRefetchContainer(BareIssueDetailView, { ...issueDetailView_issueish @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor, - commitCount: $commitCount, - commitCursor: $commitCursor ) } } diff --git a/lib/views/issueish-detail-view.js b/lib/views/issueish-detail-view.js index 55d5a148e7..a975d2f4f5 100644 --- a/lib/views/issueish-detail-view.js +++ b/lib/views/issueish-detail-view.js @@ -1,431 +1,431 @@ -import React, {Fragment} from 'react'; -import {graphql, createRefetchContainer} from 'react-relay'; -import PropTypes from 'prop-types'; -import cx from 'classnames'; -import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; - -import IssueTimelineController from '../controllers/issue-timeline-controller'; -import PrTimelineContainer from '../controllers/pr-timeline-controller'; -import PrStatusesView from '../views/pr-statuses-view'; -import Octicon from '../atom/octicon'; -import IssueishBadge from '../views/issueish-badge'; -import GithubDotcomMarkdown from '../views/github-dotcom-markdown'; -import PeriodicRefresher from '../periodic-refresher'; -import {EnableableOperationPropType} from '../prop-types'; -import {autobind} from '../helpers'; -import {addEvent} from '../reporter-proxy'; -import PrCommitsView from '../views/pr-commits-view'; - -const reactionTypeToEmoji = { - THUMBS_UP: '👍', - THUMBS_DOWN: '👎', - LAUGH: '😆', - HOORAY: '🎉', - CONFUSED: '😕', - HEART: '❤️', -}; - -function createCheckoutState(name) { - return function(cases) { - return cases[name] || cases.default; - }; -} - -export const checkoutStates = { - HIDDEN: createCheckoutState('hidden'), - DISABLED: createCheckoutState('disabled'), - BUSY: createCheckoutState('busy'), - CURRENT: createCheckoutState('current'), -}; - -export class BareIssueishDetailView extends React.Component { - static propTypes = { - relay: PropTypes.shape({ - refetch: PropTypes.func.isRequired, - }), - switchToIssueish: PropTypes.func.isRequired, - checkoutOp: EnableableOperationPropType.isRequired, - repository: PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - owner: PropTypes.shape({ - login: PropTypes.string, - }), - }), - issueish: PropTypes.shape({ - __typename: PropTypes.string.isRequired, - id: PropTypes.string.isRequired, - title: PropTypes.string, - countedCommits: PropTypes.shape({ - totalCount: PropTypes.number.isRequired, - }).isRequired, - isCrossRepository: PropTypes.bool, - changedFiles: PropTypes.number.isRequired, - url: PropTypes.string.isRequired, - bodyHTML: PropTypes.string, - number: PropTypes.number, - state: PropTypes.oneOf([ - 'OPEN', 'CLOSED', 'MERGED', - ]).isRequired, - author: PropTypes.shape({ - login: PropTypes.string.isRequired, - avatarUrl: PropTypes.string.isRequired, - url: PropTypes.string.isRequired, - }).isRequired, - reactionGroups: PropTypes.arrayOf( - PropTypes.shape({ - content: PropTypes.string.isRequired, - users: PropTypes.shape({ - totalCount: PropTypes.number.isRequired, - }).isRequired, - }), - ).isRequired, - }).isRequired, - } - - state = { - refreshing: false, - } - - constructor(props) { - super(props); - autobind(this, 'handleRefreshClick', 'refresh', 'renderEmojiReactions', 'renderIssueBody', 'renderPullRequestBody'); - } - - componentDidMount() { - this.refresher = new PeriodicRefresher(BareIssueishDetailView, { - interval: () => 5 * 60 * 1000, - getCurrentId: () => this.props.issueish.id, - refresh: this.refresh, - minimumIntervalPerId: 2 * 60 * 1000, - }); - // auto-refresh disabled for now until pagination is handled - // this.refresher.start(); - } - - componentWillUnmount() { - this.refresher.destroy(); - } - - renderPrMetadata(issueish, repo) { - return ( -
- - {issueish.author.login} wants to merge{' '} - {issueish.countedCommits.totalCount} commits and{' '} - {issueish.changedFiles} changed files into{' '} - {issueish.isCrossRepository ? - `${repo.owner.login}/${issueish.baseRefName}` : issueish.baseRefName} from{' '} - {issueish.isCrossRepository ? - `${issueish.author.login}/${issueish.headRefName}` : issueish.headRefName} - -
- ); - } - - renderIssueBody(issueish, childProps) { - return ( - - No description provided.'} - switchToIssueish={this.props.switchToIssueish} - /> - {this.renderEmojiReactions(issueish)} - - - ); - } - - renderPullRequestBody(issueish, childProps) { - return ( - - - - Overview - - - Build Status - - - - Commits - - - {/* 'Files Changed' and 'Reviews' tabs to be added in the future. */} - - {/* overview */} - - No description provided.'} - switchToIssueish={this.props.switchToIssueish} - /> - {this.renderEmojiReactions(issueish)} - - - - - {/* build status */} - -
- -
-
- - {/* commits */} - - - -
- ); - } - - renderEmojiReactions(issueish) { - return ( -
- {issueish.reactionGroups.map(group => ( - group.users.totalCount > 0 - ? - {reactionTypeToEmoji[group.content]}   {group.users.totalCount} - - : null - ))} -
- ); - } - - render() { - const repo = this.props.repository; - const issueish = this.props.issueish; - const isPr = issueish.__typename === 'PullRequest'; - const childProps = { - issue: issueish.__typename === 'Issue' ? issueish : null, - pullRequest: issueish.__typename === 'PullRequest' ? issueish : null, - }; - // todo(tt, 9/2018): it could enhance readability to extract header rendering into - // 2 functions: one for rendering an issue header, and one for rendering a pr header. - // however, the tradeoff there is having some repetitive code. - return ( -
-
- -
-
- -
- -
-
- {isPr && this.renderPrMetadata(issueish, repo)} -
- -
- {this.renderCheckoutButton()} -
-
-
- {isPr ? this.renderPullRequestBody(issueish, childProps) : this.renderIssueBody(issueish, childProps)} - - - -
-
- ); - } - - renderCheckoutButton() { - const {checkoutOp} = this.props; - let extraClass = null; - let buttonText = 'Checkout'; - let buttonTitle = null; - - if (!checkoutOp.isEnabled()) { - buttonTitle = checkoutOp.getMessage(); - const reason = checkoutOp.why(); - if (reason({hidden: true, default: false})) { - return null; - } - - buttonText = reason({ - current: 'Checked out', - default: 'Checkout', - }); - - extraClass = 'github-IssueishDetailView-checkoutButton--' + reason({ - disabled: 'disabled', - busy: 'busy', - current: 'current', - }); - } - - const classNames = cx('btn', 'btn-primary', 'github-IssueishDetailView-checkoutButton', extraClass); - return ( - - ); - } - - handleRefreshClick(e) { - e.preventDefault(); - this.refresher.refreshNow(true); - } - - recordOpenInBrowserEvent() { - addEvent('open-issueish-in-browser', {package: 'github', from: 'issueish-header'}); - } - - refresh() { - if (this.state.refreshing) { - return; - } - - this.setState({refreshing: true}); - this.props.relay.refetch({ - repoId: this.props.repository.id, - issueishId: this.props.issueish.id, - timelineCount: 100, - timelineCursor: null, - commitCount: 100, - commitCursor: null, - }, null, () => { - this.setState({refreshing: false}); - }, {force: true}); - } -} - -export default createRefetchContainer(BareIssueishDetailView, { - repository: graphql` - fragment issueishDetailView_repository on Repository { - id - name - owner { - login - } - } - `, - - issueish: graphql` - fragment issueishDetailView_issueish on IssueOrPullRequest - @argumentDefinitions( - timelineCount: {type: "Int!"}, - timelineCursor: {type: "String"}, - commitCount: {type: "Int!"}, - commitCursor: {type: "String"}, - ) { - __typename - - ... on Node { - id - } - - ... on Issue { - state number title bodyHTML - author { - login avatarUrl - ... on User { url } - ... on Bot { url } - } - - ...issueTimelineController_issue @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) - } - - ... on PullRequest { - isCrossRepository - changedFiles - ...prCommitsView_pullRequest @arguments(commitCount: $commitCount, commitCursor: $commitCursor) - countedCommits: commits { - totalCount - } - ...prStatusesView_pullRequest - state number title bodyHTML baseRefName headRefName - author { - login avatarUrl - ... on User { url } - ... on Bot { url } - } - - ...prTimelineController_pullRequest @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) - } - - ... on UniformResourceLocatable { url } - - ... on Reactable { - reactionGroups { - content users { totalCount } - } - } - } - `, -}, graphql` - query issueishDetailViewRefetchQuery - ( - $repoId: ID!, - $issueishId: ID!, - $timelineCount: Int!, - $timelineCursor: String, - $commitCount: Int!, - $commitCursor: String - ) { - repository:node(id: $repoId) { - ...issueishDetailView_repository @arguments( - timelineCount: $timelineCount, - timelineCursor: $timelineCursor - ) - } - - issueish:node(id: $issueishId) { - ...issueishDetailView_issueish @arguments( - timelineCount: $timelineCount, - timelineCursor: $timelineCursor, - commitCount: $commitCount, - commitCursor: $commitCursor - ) - } - } -`); +// import React, {Fragment} from 'react'; +// import {graphql, createRefetchContainer} from 'react-relay'; +// import PropTypes from 'prop-types'; +// import cx from 'classnames'; +// import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; +// +// import IssueTimelineController from '../controllers/issue-timeline-controller'; +// import PrTimelineContainer from '../controllers/pr-timeline-controller'; +// import PrStatusesView from '../views/pr-statuses-view'; +// import Octicon from '../atom/octicon'; +// import IssueishBadge from '../views/issueish-badge'; +// import GithubDotcomMarkdown from '../views/github-dotcom-markdown'; +// import PeriodicRefresher from '../periodic-refresher'; +// import {EnableableOperationPropType} from '../prop-types'; +// import {autobind} from '../helpers'; +// import {addEvent} from '../reporter-proxy'; +// import PrCommitsView from '../views/pr-commits-view'; +// +// const reactionTypeToEmoji = { +// THUMBS_UP: '👍', +// THUMBS_DOWN: '👎', +// LAUGH: '😆', +// HOORAY: '🎉', +// CONFUSED: '😕', +// HEART: '❤️', +// }; +// +// function createCheckoutState(name) { +// return function(cases) { +// return cases[name] || cases.default; +// }; +// } +// +// export const checkoutStates = { +// HIDDEN: createCheckoutState('hidden'), +// DISABLED: createCheckoutState('disabled'), +// BUSY: createCheckoutState('busy'), +// CURRENT: createCheckoutState('current'), +// }; +// +// export class BareIssueishDetailView extends React.Component { +// static propTypes = { +// relay: PropTypes.shape({ +// refetch: PropTypes.func.isRequired, +// }), +// switchToIssueish: PropTypes.func.isRequired, +// checkoutOp: EnableableOperationPropType.isRequired, +// repository: PropTypes.shape({ +// id: PropTypes.string.isRequired, +// name: PropTypes.string.isRequired, +// owner: PropTypes.shape({ +// login: PropTypes.string, +// }), +// }), +// issueish: PropTypes.shape({ +// __typename: PropTypes.string.isRequired, +// id: PropTypes.string.isRequired, +// title: PropTypes.string, +// countedCommits: PropTypes.shape({ +// totalCount: PropTypes.number.isRequired, +// }).isRequired, +// isCrossRepository: PropTypes.bool, +// changedFiles: PropTypes.number.isRequired, +// url: PropTypes.string.isRequired, +// bodyHTML: PropTypes.string, +// number: PropTypes.number, +// state: PropTypes.oneOf([ +// 'OPEN', 'CLOSED', 'MERGED', +// ]).isRequired, +// author: PropTypes.shape({ +// login: PropTypes.string.isRequired, +// avatarUrl: PropTypes.string.isRequired, +// url: PropTypes.string.isRequired, +// }).isRequired, +// reactionGroups: PropTypes.arrayOf( +// PropTypes.shape({ +// content: PropTypes.string.isRequired, +// users: PropTypes.shape({ +// totalCount: PropTypes.number.isRequired, +// }).isRequired, +// }), +// ).isRequired, +// }).isRequired, +// } +// +// state = { +// refreshing: false, +// } +// +// constructor(props) { +// super(props); +// autobind(this, 'handleRefreshClick', 'refresh', 'renderEmojiReactions', 'renderIssueBody', 'renderPullRequestBody'); +// } +// +// componentDidMount() { +// this.refresher = new PeriodicRefresher(BareIssueishDetailView, { +// interval: () => 5 * 60 * 1000, +// getCurrentId: () => this.props.issueish.id, +// refresh: this.refresh, +// minimumIntervalPerId: 2 * 60 * 1000, +// }); +// // auto-refresh disabled for now until pagination is handled +// // this.refresher.start(); +// } +// +// componentWillUnmount() { +// this.refresher.destroy(); +// } +// +// renderPrMetadata(issueish, repo) { +// return ( +//
+// +// {issueish.author.login} wants to merge{' '} +// {issueish.countedCommits.totalCount} commits and{' '} +// {issueish.changedFiles} changed files into{' '} +// {issueish.isCrossRepository ? +// `${repo.owner.login}/${issueish.baseRefName}` : issueish.baseRefName} from{' '} +// {issueish.isCrossRepository ? +// `${issueish.author.login}/${issueish.headRefName}` : issueish.headRefName} +// +//
+// ); +// } +// +// renderIssueBody(issueish, childProps) { +// return ( +// +// No description provided.'} +// switchToIssueish={this.props.switchToIssueish} +// /> +// {this.renderEmojiReactions(issueish)} +// +// +// ); +// } +// +// renderPullRequestBody(issueish, childProps) { +// return ( +// +// +// +// Overview +// +// +// Build Status +// +// +// +// Commits +// +// +// {/* 'Files Changed' and 'Reviews' tabs to be added in the future. */} +// +// {/* overview */} +// +// No description provided.'} +// switchToIssueish={this.props.switchToIssueish} +// /> +// {this.renderEmojiReactions(issueish)} +// +// +// +// +// {/* build status */} +// +//
+// +//
+//
+// +// {/* commits */} +// +// +// +//
+// ); +// } +// +// renderEmojiReactions(issueish) { +// return ( +//
+// {issueish.reactionGroups.map(group => ( +// group.users.totalCount > 0 +// ? +// {reactionTypeToEmoji[group.content]}   {group.users.totalCount} +// +// : null +// ))} +//
+// ); +// } +// +// render() { +// const repo = this.props.repository; +// const issueish = this.props.issueish; +// const isPr = issueish.__typename === 'PullRequest'; +// const childProps = { +// issue: issueish.__typename === 'Issue' ? issueish : null, +// pullRequest: issueish.__typename === 'PullRequest' ? issueish : null, +// }; +// // todo(tt, 9/2018): it could enhance readability to extract header rendering into +// // 2 functions: one for rendering an issue header, and one for rendering a pr header. +// // however, the tradeoff there is having some repetitive code. +// return ( +//
+//
+// +//
+//
+// +//
+// +//
+//
+// {isPr && this.renderPrMetadata(issueish, repo)} +//
+// +//
+// {this.renderCheckoutButton()} +//
+//
+//
+// {isPr ? this.renderPullRequestBody(issueish, childProps) : this.renderIssueBody(issueish, childProps)} +// +// +// +//
+//
+// ); +// } +// +// renderCheckoutButton() { +// const {checkoutOp} = this.props; +// let extraClass = null; +// let buttonText = 'Checkout'; +// let buttonTitle = null; +// +// if (!checkoutOp.isEnabled()) { +// buttonTitle = checkoutOp.getMessage(); +// const reason = checkoutOp.why(); +// if (reason({hidden: true, default: false})) { +// return null; +// } +// +// buttonText = reason({ +// current: 'Checked out', +// default: 'Checkout', +// }); +// +// extraClass = 'github-IssueishDetailView-checkoutButton--' + reason({ +// disabled: 'disabled', +// busy: 'busy', +// current: 'current', +// }); +// } +// +// const classNames = cx('btn', 'btn-primary', 'github-IssueishDetailView-checkoutButton', extraClass); +// return ( +// +// ); +// } +// +// handleRefreshClick(e) { +// e.preventDefault(); +// this.refresher.refreshNow(true); +// } +// +// recordOpenInBrowserEvent() { +// addEvent('open-issueish-in-browser', {package: 'github', from: 'issueish-header'}); +// } +// +// refresh() { +// if (this.state.refreshing) { +// return; +// } +// +// this.setState({refreshing: true}); +// this.props.relay.refetch({ +// repoId: this.props.repository.id, +// issueishId: this.props.issueish.id, +// timelineCount: 100, +// timelineCursor: null, +// commitCount: 100, +// commitCursor: null, +// }, null, () => { +// this.setState({refreshing: false}); +// }, {force: true}); +// } +// } +// +// export default createRefetchContainer(BareIssueishDetailView, { +// repository: graphql` +// fragment issueishDetailView_repository on Repository { +// id +// name +// owner { +// login +// } +// } +// `, +// +// issueish: graphql` +// fragment issueishDetailView_issueish on IssueOrPullRequest +// @argumentDefinitions( +// timelineCount: {type: "Int!"}, +// timelineCursor: {type: "String"}, +// commitCount: {type: "Int!"}, +// commitCursor: {type: "String"}, +// ) { +// __typename +// +// ... on Node { +// id +// } +// +// ... on Issue { +// state number title bodyHTML +// author { +// login avatarUrl +// ... on User { url } +// ... on Bot { url } +// } +// +// ...issueTimelineController_issue @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) +// } +// +// ... on PullRequest { +// isCrossRepository +// changedFiles +// ...prCommitsView_pullRequest @arguments(commitCount: $commitCount, commitCursor: $commitCursor) +// countedCommits: commits { +// totalCount +// } +// ...prStatusesView_pullRequest +// state number title bodyHTML baseRefName headRefName +// author { +// login avatarUrl +// ... on User { url } +// ... on Bot { url } +// } +// +// ...prTimelineController_pullRequest @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) +// } +// +// ... on UniformResourceLocatable { url } +// +// ... on Reactable { +// reactionGroups { +// content users { totalCount } +// } +// } +// } +// `, +// }, graphql` +// query issueishDetailViewRefetchQuery +// ( +// $repoId: ID!, +// $issueishId: ID!, +// $timelineCount: Int!, +// $timelineCursor: String, +// $commitCount: Int!, +// $commitCursor: String +// ) { +// repository:node(id: $repoId) { +// ...issueishDetailView_repository @arguments( +// timelineCount: $timelineCount, +// timelineCursor: $timelineCursor +// ) +// } +// +// issueish:node(id: $issueishId) { +// ...issueishDetailView_issueish @arguments( +// timelineCount: $timelineCount, +// timelineCursor: $timelineCursor, +// commitCount: $commitCount, +// commitCursor: $commitCursor +// ) +// } +// } +// `); From 245bc051002fa2b12f264c8584777709961cdb8a Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 16:39:19 -0800 Subject: [PATCH 1152/4053] woohoo, no more graphql errors --- .../issueishDetailContainerQuery.graphql.js | 15 +++++++++++++-- ...issueishDetailController_repository.graphql.js | 10 ++++++++-- lib/controllers/issueish-detail-controller.js | 5 +++++ lib/views/issue-detail-view.js | 4 +--- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 660643501f..926426ec52 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash e8563250a136d6b88c0c24e7e9660e4e + * @relayHash ac2c809c9af012ee05b82f0b4817c27c */ /* eslint-disable */ @@ -55,6 +55,7 @@ fragment issueishDetailController_repository_1mXVvq on Repository { login id } + ...prDetailView_repository issueish: issueOrPullRequest(number: $issueishNumber) { __typename ... on Issue { @@ -95,6 +96,16 @@ fragment issueDetailView_repository on Repository { } } +fragment prDetailView_repository on Repository { + id + name + owner { + __typename + login + id + } +} + fragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest { __typename ... on Node { @@ -1036,7 +1047,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n ...prDetailView_repository\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", diff --git a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js index 8f5d3bb81d..0ea84cd2cb 100644 --- a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js +++ b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js @@ -11,6 +11,7 @@ import type { ConcreteFragment } from 'relay-runtime'; type issueDetailView_issueish$ref = any; type issueDetailView_repository$ref = any; type prDetailView_issueish$ref = any; +type prDetailView_repository$ref = any; import type { FragmentReference } from "relay-runtime"; declare export opaque type issueishDetailController_repository$ref: FragmentReference; export type issueishDetailController_repository = {| @@ -40,7 +41,7 @@ export type issueishDetailController_repository = {| // value in case none of the concrete values match. +__typename: "%other" |}), - +$fragmentRefs: issueDetailView_repository$ref, + +$fragmentRefs: issueDetailView_repository$ref & prDetailView_repository$ref, +$refType: issueishDetailController_repository$ref, |}; */ @@ -157,6 +158,11 @@ return { }, v0, v1, + { + "kind": "FragmentSpread", + "name": "prDetailView_repository", + "args": null + }, { "kind": "LinkedField", "alias": "issueish", @@ -246,5 +252,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'fd149181bc7418ba5e93d2dd7a89dac1'; +(node/*: any*/).hash = 'e7188ae281bc123becd5d15e5d293981'; module.exports = node; diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 56bc46e673..5b55926d33 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -229,6 +229,11 @@ export default createFragmentContainer(BareIssueishDetailController, { owner { login } + ...prDetailView_repository + name + owner { + login + } issueish: issueOrPullRequest(number: $issueishNumber) { __typename ... on Issue { diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 04343d636f..c6ce4df3f4 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -37,13 +37,11 @@ export class BareIssueDetailView extends React.Component { __typename: PropTypes.string.isRequired, id: PropTypes.string.isRequired, title: PropTypes.string, - isCrossRepository: PropTypes.bool, - changedFiles: PropTypes.number.isRequired, url: PropTypes.string.isRequired, bodyHTML: PropTypes.string, number: PropTypes.number, state: PropTypes.oneOf([ - 'OPEN', 'CLOSED', 'MERGED', + 'OPEN', 'CLOSED', ]).isRequired, author: PropTypes.shape({ login: PropTypes.string.isRequired, From c1a32ccfe1b0c5467860c0d7d2aeca5741147819 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 17:14:57 -0800 Subject: [PATCH 1153/4053] extract emoji reaction rendering into its own component --- lib/views/emoji-reactions-view.js | 40 +++++++++++++++++++++++++++++++ lib/views/issue-detail-view.js | 29 +++------------------- lib/views/pr-detail-view.js | 29 +++------------------- 3 files changed, 46 insertions(+), 52 deletions(-) create mode 100644 lib/views/emoji-reactions-view.js diff --git a/lib/views/emoji-reactions-view.js b/lib/views/emoji-reactions-view.js new file mode 100644 index 0000000000..240d55fd42 --- /dev/null +++ b/lib/views/emoji-reactions-view.js @@ -0,0 +1,40 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import cx from 'classnames'; + +const reactionTypeToEmoji = { + THUMBS_UP: '👍', + THUMBS_DOWN: '👎', + LAUGH: '😆', + HOORAY: '🎉', + CONFUSED: '😕', + HEART: '❤️', +}; + +export default class EmojiReactionsView extends React.Component { + static propTypes = { + reactionGroups: PropTypes.arrayOf( + PropTypes.shape({ + content: PropTypes.string.isRequired, + users: PropTypes.shape({ + totalCount: PropTypes.number.isRequired, + }).isRequired, + }), + ).isRequired, + } + + render() { + return ( +
+ {this.props.reactionGroups.map(group => ( + group.users.totalCount > 0 + ? + {reactionTypeToEmoji[group.content]}   {group.users.totalCount} + + : null + ))} +
+ ); + } +} diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index c6ce4df3f4..020f07dd97 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -7,19 +7,11 @@ import IssueTimelineController from '../controllers/issue-timeline-controller'; import Octicon from '../atom/octicon'; import IssueishBadge from '../views/issueish-badge'; import GithubDotcomMarkdown from '../views/github-dotcom-markdown'; +import EmojiReactionsView from '../views/emoji-reactions-view'; import PeriodicRefresher from '../periodic-refresher'; import {autobind} from '../helpers'; import {addEvent} from '../reporter-proxy'; -const reactionTypeToEmoji = { - THUMBS_UP: '👍', - THUMBS_DOWN: '👎', - LAUGH: '😆', - HOORAY: '🎉', - CONFUSED: '😕', - HEART: '❤️', -}; - export class BareIssueDetailView extends React.Component { static propTypes = { relay: PropTypes.shape({ @@ -65,7 +57,7 @@ export class BareIssueDetailView extends React.Component { constructor(props) { super(props); - autobind(this, 'handleRefreshClick', 'refresh', 'renderEmojiReactions', 'renderIssueBody'); + autobind(this, 'handleRefreshClick', 'refresh', 'renderIssueBody'); } componentDidMount() { @@ -90,7 +82,7 @@ export class BareIssueDetailView extends React.Component { html={issueish.bodyHTML || 'No description provided.'} switchToIssueish={this.props.switchToIssueish} /> - {this.renderEmojiReactions(issueish)} + - {issueish.reactionGroups.map(group => ( - group.users.totalCount > 0 - ? - {reactionTypeToEmoji[group.content]}   {group.users.totalCount} - - : null - ))} -
- ); - } - render() { const repo = this.props.repository; const issueish = this.props.issueish; diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 340590eefd..fd94084a32 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -9,21 +9,13 @@ import PrStatusesView from '../views/pr-statuses-view'; import Octicon from '../atom/octicon'; import IssueishBadge from '../views/issueish-badge'; import GithubDotcomMarkdown from '../views/github-dotcom-markdown'; +import EmojiReactionsView from '../views/emoji-reactions-view'; import PeriodicRefresher from '../periodic-refresher'; import {EnableableOperationPropType} from '../prop-types'; import {autobind} from '../helpers'; import {addEvent} from '../reporter-proxy'; import PrCommitsView from '../views/pr-commits-view'; -const reactionTypeToEmoji = { - THUMBS_UP: '👍', - THUMBS_DOWN: '👎', - LAUGH: '😆', - HOORAY: '🎉', - CONFUSED: '😕', - HEART: '❤️', -}; - function createCheckoutState(name) { return function(cases) { return cases[name] || cases.default; @@ -88,7 +80,7 @@ export class BarePullRequestDetailView extends React.Component { constructor(props) { super(props); - autobind(this, 'handleRefreshClick', 'refresh', 'renderEmojiReactions', 'renderPullRequestBody'); + autobind(this, 'handleRefreshClick', 'refresh', 'renderPullRequestBody'); } componentDidMount() { @@ -150,7 +142,7 @@ export class BarePullRequestDetailView extends React.Component { html={issueish.bodyHTML || 'No description provided.'} switchToIssueish={this.props.switchToIssueish} /> - {this.renderEmojiReactions(issueish)} + - {issueish.reactionGroups.map(group => ( - group.users.totalCount > 0 - ? - {reactionTypeToEmoji[group.content]}   {group.users.totalCount} - - : null - ))} -
- ); - } - render() { const repo = this.props.repository; const issueish = this.props.issueish; From 6ab8e396b65383b155c68f6e468a2345f3a1ba69 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 17:37:46 -0800 Subject: [PATCH 1154/4053] nuke `IssueishDetailView`, whee --- lib/views/issueish-detail-view.js | 431 ------------------------ test/views/issueish-detail-view.test.js | 280 --------------- 2 files changed, 711 deletions(-) delete mode 100644 lib/views/issueish-detail-view.js delete mode 100644 test/views/issueish-detail-view.test.js diff --git a/lib/views/issueish-detail-view.js b/lib/views/issueish-detail-view.js deleted file mode 100644 index a975d2f4f5..0000000000 --- a/lib/views/issueish-detail-view.js +++ /dev/null @@ -1,431 +0,0 @@ -// import React, {Fragment} from 'react'; -// import {graphql, createRefetchContainer} from 'react-relay'; -// import PropTypes from 'prop-types'; -// import cx from 'classnames'; -// import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; -// -// import IssueTimelineController from '../controllers/issue-timeline-controller'; -// import PrTimelineContainer from '../controllers/pr-timeline-controller'; -// import PrStatusesView from '../views/pr-statuses-view'; -// import Octicon from '../atom/octicon'; -// import IssueishBadge from '../views/issueish-badge'; -// import GithubDotcomMarkdown from '../views/github-dotcom-markdown'; -// import PeriodicRefresher from '../periodic-refresher'; -// import {EnableableOperationPropType} from '../prop-types'; -// import {autobind} from '../helpers'; -// import {addEvent} from '../reporter-proxy'; -// import PrCommitsView from '../views/pr-commits-view'; -// -// const reactionTypeToEmoji = { -// THUMBS_UP: '👍', -// THUMBS_DOWN: '👎', -// LAUGH: '😆', -// HOORAY: '🎉', -// CONFUSED: '😕', -// HEART: '❤️', -// }; -// -// function createCheckoutState(name) { -// return function(cases) { -// return cases[name] || cases.default; -// }; -// } -// -// export const checkoutStates = { -// HIDDEN: createCheckoutState('hidden'), -// DISABLED: createCheckoutState('disabled'), -// BUSY: createCheckoutState('busy'), -// CURRENT: createCheckoutState('current'), -// }; -// -// export class BareIssueishDetailView extends React.Component { -// static propTypes = { -// relay: PropTypes.shape({ -// refetch: PropTypes.func.isRequired, -// }), -// switchToIssueish: PropTypes.func.isRequired, -// checkoutOp: EnableableOperationPropType.isRequired, -// repository: PropTypes.shape({ -// id: PropTypes.string.isRequired, -// name: PropTypes.string.isRequired, -// owner: PropTypes.shape({ -// login: PropTypes.string, -// }), -// }), -// issueish: PropTypes.shape({ -// __typename: PropTypes.string.isRequired, -// id: PropTypes.string.isRequired, -// title: PropTypes.string, -// countedCommits: PropTypes.shape({ -// totalCount: PropTypes.number.isRequired, -// }).isRequired, -// isCrossRepository: PropTypes.bool, -// changedFiles: PropTypes.number.isRequired, -// url: PropTypes.string.isRequired, -// bodyHTML: PropTypes.string, -// number: PropTypes.number, -// state: PropTypes.oneOf([ -// 'OPEN', 'CLOSED', 'MERGED', -// ]).isRequired, -// author: PropTypes.shape({ -// login: PropTypes.string.isRequired, -// avatarUrl: PropTypes.string.isRequired, -// url: PropTypes.string.isRequired, -// }).isRequired, -// reactionGroups: PropTypes.arrayOf( -// PropTypes.shape({ -// content: PropTypes.string.isRequired, -// users: PropTypes.shape({ -// totalCount: PropTypes.number.isRequired, -// }).isRequired, -// }), -// ).isRequired, -// }).isRequired, -// } -// -// state = { -// refreshing: false, -// } -// -// constructor(props) { -// super(props); -// autobind(this, 'handleRefreshClick', 'refresh', 'renderEmojiReactions', 'renderIssueBody', 'renderPullRequestBody'); -// } -// -// componentDidMount() { -// this.refresher = new PeriodicRefresher(BareIssueishDetailView, { -// interval: () => 5 * 60 * 1000, -// getCurrentId: () => this.props.issueish.id, -// refresh: this.refresh, -// minimumIntervalPerId: 2 * 60 * 1000, -// }); -// // auto-refresh disabled for now until pagination is handled -// // this.refresher.start(); -// } -// -// componentWillUnmount() { -// this.refresher.destroy(); -// } -// -// renderPrMetadata(issueish, repo) { -// return ( -//
-// -// {issueish.author.login} wants to merge{' '} -// {issueish.countedCommits.totalCount} commits and{' '} -// {issueish.changedFiles} changed files into{' '} -// {issueish.isCrossRepository ? -// `${repo.owner.login}/${issueish.baseRefName}` : issueish.baseRefName} from{' '} -// {issueish.isCrossRepository ? -// `${issueish.author.login}/${issueish.headRefName}` : issueish.headRefName} -// -//
-// ); -// } -// -// renderIssueBody(issueish, childProps) { -// return ( -// -// No description provided.'} -// switchToIssueish={this.props.switchToIssueish} -// /> -// {this.renderEmojiReactions(issueish)} -// -// -// ); -// } -// -// renderPullRequestBody(issueish, childProps) { -// return ( -// -// -// -// Overview -// -// -// Build Status -// -// -// -// Commits -// -// -// {/* 'Files Changed' and 'Reviews' tabs to be added in the future. */} -// -// {/* overview */} -// -// No description provided.'} -// switchToIssueish={this.props.switchToIssueish} -// /> -// {this.renderEmojiReactions(issueish)} -// -// -// -// -// {/* build status */} -// -//
-// -//
-//
-// -// {/* commits */} -// -// -// -//
-// ); -// } -// -// renderEmojiReactions(issueish) { -// return ( -//
-// {issueish.reactionGroups.map(group => ( -// group.users.totalCount > 0 -// ? -// {reactionTypeToEmoji[group.content]}   {group.users.totalCount} -// -// : null -// ))} -//
-// ); -// } -// -// render() { -// const repo = this.props.repository; -// const issueish = this.props.issueish; -// const isPr = issueish.__typename === 'PullRequest'; -// const childProps = { -// issue: issueish.__typename === 'Issue' ? issueish : null, -// pullRequest: issueish.__typename === 'PullRequest' ? issueish : null, -// }; -// // todo(tt, 9/2018): it could enhance readability to extract header rendering into -// // 2 functions: one for rendering an issue header, and one for rendering a pr header. -// // however, the tradeoff there is having some repetitive code. -// return ( -//
-//
-// -//
-//
-// -//
-// -//
-//
-// {isPr && this.renderPrMetadata(issueish, repo)} -//
-// -//
-// {this.renderCheckoutButton()} -//
-//
-//
-// {isPr ? this.renderPullRequestBody(issueish, childProps) : this.renderIssueBody(issueish, childProps)} -// -// -// -//
-//
-// ); -// } -// -// renderCheckoutButton() { -// const {checkoutOp} = this.props; -// let extraClass = null; -// let buttonText = 'Checkout'; -// let buttonTitle = null; -// -// if (!checkoutOp.isEnabled()) { -// buttonTitle = checkoutOp.getMessage(); -// const reason = checkoutOp.why(); -// if (reason({hidden: true, default: false})) { -// return null; -// } -// -// buttonText = reason({ -// current: 'Checked out', -// default: 'Checkout', -// }); -// -// extraClass = 'github-IssueishDetailView-checkoutButton--' + reason({ -// disabled: 'disabled', -// busy: 'busy', -// current: 'current', -// }); -// } -// -// const classNames = cx('btn', 'btn-primary', 'github-IssueishDetailView-checkoutButton', extraClass); -// return ( -// -// ); -// } -// -// handleRefreshClick(e) { -// e.preventDefault(); -// this.refresher.refreshNow(true); -// } -// -// recordOpenInBrowserEvent() { -// addEvent('open-issueish-in-browser', {package: 'github', from: 'issueish-header'}); -// } -// -// refresh() { -// if (this.state.refreshing) { -// return; -// } -// -// this.setState({refreshing: true}); -// this.props.relay.refetch({ -// repoId: this.props.repository.id, -// issueishId: this.props.issueish.id, -// timelineCount: 100, -// timelineCursor: null, -// commitCount: 100, -// commitCursor: null, -// }, null, () => { -// this.setState({refreshing: false}); -// }, {force: true}); -// } -// } -// -// export default createRefetchContainer(BareIssueishDetailView, { -// repository: graphql` -// fragment issueishDetailView_repository on Repository { -// id -// name -// owner { -// login -// } -// } -// `, -// -// issueish: graphql` -// fragment issueishDetailView_issueish on IssueOrPullRequest -// @argumentDefinitions( -// timelineCount: {type: "Int!"}, -// timelineCursor: {type: "String"}, -// commitCount: {type: "Int!"}, -// commitCursor: {type: "String"}, -// ) { -// __typename -// -// ... on Node { -// id -// } -// -// ... on Issue { -// state number title bodyHTML -// author { -// login avatarUrl -// ... on User { url } -// ... on Bot { url } -// } -// -// ...issueTimelineController_issue @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) -// } -// -// ... on PullRequest { -// isCrossRepository -// changedFiles -// ...prCommitsView_pullRequest @arguments(commitCount: $commitCount, commitCursor: $commitCursor) -// countedCommits: commits { -// totalCount -// } -// ...prStatusesView_pullRequest -// state number title bodyHTML baseRefName headRefName -// author { -// login avatarUrl -// ... on User { url } -// ... on Bot { url } -// } -// -// ...prTimelineController_pullRequest @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) -// } -// -// ... on UniformResourceLocatable { url } -// -// ... on Reactable { -// reactionGroups { -// content users { totalCount } -// } -// } -// } -// `, -// }, graphql` -// query issueishDetailViewRefetchQuery -// ( -// $repoId: ID!, -// $issueishId: ID!, -// $timelineCount: Int!, -// $timelineCursor: String, -// $commitCount: Int!, -// $commitCursor: String -// ) { -// repository:node(id: $repoId) { -// ...issueishDetailView_repository @arguments( -// timelineCount: $timelineCount, -// timelineCursor: $timelineCursor -// ) -// } -// -// issueish:node(id: $issueishId) { -// ...issueishDetailView_issueish @arguments( -// timelineCount: $timelineCount, -// timelineCursor: $timelineCursor, -// commitCount: $commitCount, -// commitCursor: $commitCursor -// ) -// } -// } -// `); diff --git a/test/views/issueish-detail-view.test.js b/test/views/issueish-detail-view.test.js deleted file mode 100644 index b129d80c60..0000000000 --- a/test/views/issueish-detail-view.test.js +++ /dev/null @@ -1,280 +0,0 @@ -import React from 'react'; -import {shallow} from 'enzyme'; -import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; - -import {BareIssueishDetailView, checkoutStates} from '../../lib/views/issueish-detail-view'; -import {issueishDetailViewProps} from '../fixtures/props/issueish-pane-props'; -import EnableableOperation from '../../lib/models/enableable-operation'; -import * as reporterProxy from '../../lib/reporter-proxy'; - -describe('IssueishDetailView', function() { - function buildApp(opts, overrideProps = {}) { - return ; - } - - it('renders pull request information', function() { - const commitCount = 11; - const fileCount = 22; - const baseRefName = 'master'; - const headRefName = 'tt/heck-yes'; - const wrapper = shallow(buildApp({ - repositoryName: 'repo', - ownerLogin: 'user0', - - issueishKind: 'PullRequest', - issueishTitle: 'PR title', - issueishBaseRef: baseRefName, - issueishHeadRef: headRefName, - issueishBodyHTML: 'stuff', - issueishAuthorLogin: 'author0', - issueishAuthorAvatarURL: 'https://avatars3.githubusercontent.com/u/1', - issueishNumber: 100, - issueishState: 'MERGED', - issueishCommitCount: commitCount, - issueishChangedFileCount: fileCount, - issueishReactions: [{content: 'THUMBS_UP', count: 10}, {content: 'THUMBS_DOWN', count: 5}, {content: 'LAUGH', count: 0}], - })); - - const badge = wrapper.find('IssueishBadge'); - assert.strictEqual(badge.prop('type'), 'PullRequest'); - assert.strictEqual(badge.prop('state'), 'MERGED'); - - const link = wrapper.find('a.github-IssueishDetailView-headerLink'); - assert.strictEqual(link.text(), 'user0/repo#100'); - assert.strictEqual(link.prop('href'), 'https://github.com/user0/repo/pull/100'); - - assert.isTrue(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); - - assert.isDefined(wrapper.find('Relay(BarePrStatusesView)[displayType="check"]').prop('pullRequest')); - - const avatarLink = wrapper.find('.github-IssueishDetailView-avatar'); - assert.strictEqual(avatarLink.prop('href'), 'https://github.com/author0'); - const avatar = avatarLink.find('img'); - assert.strictEqual(avatar.prop('src'), 'https://avatars3.githubusercontent.com/u/1'); - assert.strictEqual(avatar.prop('title'), 'author0'); - - assert.strictEqual(wrapper.find('.github-IssueishDetailView-title').text(), 'PR title'); - - assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => n.prop('html') === 'stuff')); - - const reactionGroups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); - assert.lengthOf(reactionGroups.findWhere(n => /👍/u.test(n.text()) && /\b10\b/.test(n.text())), 1); - assert.lengthOf(reactionGroups.findWhere(n => /👎/u.test(n.text()) && /\b5\b/.test(n.text())), 1); - assert.isFalse(reactionGroups.someWhere(n => /😆/u.test(n.text()))); - - assert.isNull(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); - assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); - assert.isNotNull(wrapper.find('Relay(BarePrStatusesView)[displayType="full"]').prop('pullRequest')); - - assert.strictEqual(wrapper.find('.github-IssueishDetailView-commitCount').text(), `${commitCount} commits`); - assert.strictEqual(wrapper.find('.github-IssueishDetailView-fileCount').text(), `${fileCount} changed files`); - - assert.strictEqual(wrapper.find('.github-IssueishDetailView-baseRefName').text(), baseRefName); - assert.strictEqual(wrapper.find('.github-IssueishDetailView-headRefName').text(), headRefName); - }); - - it('renders tabs', function() { - const wrapper = shallow(buildApp({})); - - assert.lengthOf(wrapper.find(Tabs), 1); - assert.lengthOf(wrapper.find(TabList), 1); - - const tabs = wrapper.find(Tab).getElements(); - assert.lengthOf(tabs, 3); - - const tab0Children = tabs[0].props.children; - assert.deepEqual(tab0Children[0].props, {icon: 'info', className: 'github-IssueishDetailView-tab-icon'}); - assert.deepEqual(tab0Children[1], 'Overview'); - - const tab1Children = tabs[1].props.children; - assert.deepEqual(tab1Children[0].props, {icon: 'checklist', className: 'github-IssueishDetailView-tab-icon'}); - assert.deepEqual(tab1Children[1], 'Build Status'); - - const tab2Children = tabs[2].props.children; - assert.deepEqual(tab2Children[0].props, {icon: 'git-commit', className: 'github-IssueishDetailView-tab-icon'}); - assert.deepEqual(tab2Children[1], 'Commits'); - - assert.lengthOf(wrapper.find(TabPanel), 3); - }); - - it('renders pull request information for cross repository PR', function() { - const baseRefName = 'master'; - const headRefName = 'tt-heck-yes'; - const ownerLogin = 'user0'; - const authorLogin = 'author0'; - const wrapper = shallow(buildApp({ - ownerLogin, - issueishBaseRef: baseRefName, - issueishHeadRef: headRefName, - issueishAuthorLogin: authorLogin, - issueishCrossRepository: true, - })); - - assert.strictEqual(wrapper.find('.github-IssueishDetailView-baseRefName').text(), `${ownerLogin}/${baseRefName}`); - assert.strictEqual(wrapper.find('.github-IssueishDetailView-headRefName').text(), `${authorLogin}/${headRefName}`); - }); - - it('renders issue information', function() { - const wrapper = shallow(buildApp({ - repositoryName: 'repo', - ownerLogin: 'user1', - - issueishKind: 'Issue', - issueishTitle: 'Issue title', - issueishBodyHTML: 'nope', - issueishAuthorLogin: 'author1', - issueishAuthorAvatarURL: 'https://avatars3.githubusercontent.com/u/2', - issueishNumber: 200, - issueishState: 'CLOSED', - issueishReactions: [{content: 'THUMBS_UP', count: 6}, {content: 'THUMBS_DOWN', count: 0}, {content: 'LAUGH', count: 2}], - }, { - checkoutOp: new EnableableOperation(() => {}).disable(checkoutStates.HIDDEN, 'An issue'), - })); - - const badge = wrapper.find('IssueishBadge'); - assert.strictEqual(badge.prop('type'), 'Issue'); - assert.strictEqual(badge.prop('state'), 'CLOSED'); - - const link = wrapper.find('a.github-IssueishDetailView-headerLink'); - assert.strictEqual(link.text(), 'user1/repo#200'); - assert.strictEqual(link.prop('href'), 'https://github.com/user1/repo/issues/200'); - - assert.isFalse(wrapper.find('Relay(PrStatuses)').exists()); - assert.isFalse(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); - - const avatarLink = wrapper.find('.github-IssueishDetailView-avatar'); - assert.strictEqual(avatarLink.prop('href'), 'https://github.com/author1'); - const avatar = avatarLink.find('img'); - assert.strictEqual(avatar.prop('src'), 'https://avatars3.githubusercontent.com/u/2'); - assert.strictEqual(avatar.prop('title'), 'author1'); - - assert.strictEqual(wrapper.find('.github-IssueishDetailView-title').text(), 'Issue title'); - - assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => n.prop('html') === 'nope')); - - const reactionGroups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); - assert.lengthOf(reactionGroups.findWhere(n => /👍/u.test(n.text()) && /\b6\b/.test(n.text())), 1); - assert.isFalse(reactionGroups.someWhere(n => /👎/u.test(n.text()))); - assert.lengthOf(reactionGroups.findWhere(n => /😆/u.test(n.text()) && /\b2\b/.test(n.text())), 1); - - assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); - assert.isNull(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); - }); - - it('renders a placeholder issueish body', function() { - const wrapper = shallow(buildApp({issueishBodyHTML: null})); - assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => /No description/.test(n.prop('html')))); - }); - - it('refreshes on click', function() { - let callback = null; - const relayRefetch = sinon.stub().callsFake((_0, _1, cb) => { - callback = cb; - }); - const wrapper = shallow(buildApp({relayRefetch}, {})); - - wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); - assert.isTrue(wrapper.find('Octicon[icon="repo-sync"]').hasClass('refreshing')); - - callback(); - wrapper.update(); - - assert.isFalse(wrapper.find('Octicon[icon="repo-sync"]').hasClass('refreshing')); - }); - - it('disregardes a double refresh', function() { - let callback = null; - const relayRefetch = sinon.stub().callsFake((_0, _1, cb) => { - callback = cb; - }); - const wrapper = shallow(buildApp({relayRefetch}, {})); - - wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); - assert.strictEqual(relayRefetch.callCount, 1); - - wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); - assert.strictEqual(relayRefetch.callCount, 1); - - callback(); - wrapper.update(); - - wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); - assert.strictEqual(relayRefetch.callCount, 2); - }); - - it('configures the refresher with a 5 minute polling interval', function() { - const wrapper = shallow(buildApp({})); - - assert.strictEqual(wrapper.instance().refresher.options.interval(), 5 * 60 * 1000); - }); - - it('destroys its refresher on unmount', function() { - const wrapper = shallow(buildApp({})); - - const refresher = wrapper.instance().refresher; - sinon.spy(refresher, 'destroy'); - - wrapper.unmount(); - - assert.isTrue(refresher.destroy.called); - }); - - describe('Checkout button', function() { - it('triggers its operation callback on click', function() { - const cb = sinon.spy(); - const checkoutOp = new EnableableOperation(cb); - const wrapper = shallow(buildApp({}, {checkoutOp})); - - const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); - assert.strictEqual(button.text(), 'Checkout'); - button.simulate('click'); - assert.isTrue(cb.called); - }); - - it('renders as disabled with hover text set to the disablement message', function() { - const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.DISABLED, 'message'); - const wrapper = shallow(buildApp({}, {checkoutOp})); - - const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); - assert.isTrue(button.prop('disabled')); - assert.strictEqual(button.text(), 'Checkout'); - assert.strictEqual(button.prop('title'), 'message'); - }); - - it('changes the button text when disabled because the PR is the current branch', function() { - const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.CURRENT, 'message'); - const wrapper = shallow(buildApp({}, {checkoutOp})); - - const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); - assert.isTrue(button.prop('disabled')); - assert.strictEqual(button.text(), 'Checked out'); - assert.strictEqual(button.prop('title'), 'message'); - }); - - it('renders hidden', function() { - const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.HIDDEN, 'message'); - const wrapper = shallow(buildApp({}, {checkoutOp})); - - assert.isFalse(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); - }); - }); - - describe('clicking link to view issueish link', function() { - it('records an event', function() { - const wrapper = shallow(buildApp({ - repositoryName: 'repo', - ownerLogin: 'user0', - issueishNumber: 100, - })); - - sinon.stub(reporterProxy, 'addEvent'); - - const link = wrapper.find('a.github-IssueishDetailView-headerLink'); - assert.strictEqual(link.text(), 'user0/repo#100'); - assert.strictEqual(link.prop('href'), 'https://github.com/user0/repo/pull/100'); - link.simulate('click'); - - assert.isTrue(reporterProxy.addEvent.calledWith('open-issueish-in-browser', {package: 'github', from: 'issueish-header'})); - }); - }); -}); From 56763caaad3f90817b4ac26620e49abbfa2926b6 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 20 Nov 2018 17:38:57 -0800 Subject: [PATCH 1155/4053] add tests for new components --- test/views/issue-detail-view.test.js | 282 +++++++++++++++++++++++++++ test/views/pr-detail-view.test.js | 280 ++++++++++++++++++++++++++ 2 files changed, 562 insertions(+) create mode 100644 test/views/issue-detail-view.test.js create mode 100644 test/views/pr-detail-view.test.js diff --git a/test/views/issue-detail-view.test.js b/test/views/issue-detail-view.test.js new file mode 100644 index 0000000000..18b156d8fa --- /dev/null +++ b/test/views/issue-detail-view.test.js @@ -0,0 +1,282 @@ +import React from 'react'; +import {shallow} from 'enzyme'; +import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; + +// probably don't need this long term. +import {checkoutStates} from '../../lib/views/pr-detail-view'; +import {BareIssueDetailView} from '../../lib/views/issue-detail-view'; +import {issueishDetailViewProps} from '../fixtures/props/issueish-pane-props'; +import EnableableOperation from '../../lib/models/enableable-operation'; +import * as reporterProxy from '../../lib/reporter-proxy'; + +describe('IssueDetailView', function() { + function buildApp(opts, overrideProps = {}) { + return ; + } + + it('renders pull request information', function() { + const commitCount = 11; + const fileCount = 22; + const baseRefName = 'master'; + const headRefName = 'tt/heck-yes'; + const wrapper = shallow(buildApp({ + repositoryName: 'repo', + ownerLogin: 'user0', + + issueishKind: 'PullRequest', + issueishTitle: 'PR title', + issueishBaseRef: baseRefName, + issueishHeadRef: headRefName, + issueishBodyHTML: 'stuff', + issueishAuthorLogin: 'author0', + issueishAuthorAvatarURL: 'https://avatars3.githubusercontent.com/u/1', + issueishNumber: 100, + issueishState: 'MERGED', + issueishCommitCount: commitCount, + issueishChangedFileCount: fileCount, + issueishReactions: [{content: 'THUMBS_UP', count: 10}, {content: 'THUMBS_DOWN', count: 5}, {content: 'LAUGH', count: 0}], + })); + + const badge = wrapper.find('IssueishBadge'); + assert.strictEqual(badge.prop('type'), 'PullRequest'); + assert.strictEqual(badge.prop('state'), 'MERGED'); + + const link = wrapper.find('a.github-IssueishDetailView-headerLink'); + assert.strictEqual(link.text(), 'user0/repo#100'); + assert.strictEqual(link.prop('href'), 'https://github.com/user0/repo/pull/100'); + + assert.isTrue(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); + + assert.isDefined(wrapper.find('Relay(BarePrStatusesView)[displayType="check"]').prop('pullRequest')); + + const avatarLink = wrapper.find('.github-IssueishDetailView-avatar'); + assert.strictEqual(avatarLink.prop('href'), 'https://github.com/author0'); + const avatar = avatarLink.find('img'); + assert.strictEqual(avatar.prop('src'), 'https://avatars3.githubusercontent.com/u/1'); + assert.strictEqual(avatar.prop('title'), 'author0'); + + assert.strictEqual(wrapper.find('.github-IssueishDetailView-title').text(), 'PR title'); + + assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => n.prop('html') === 'stuff')); + + const reactionGroups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); + assert.lengthOf(reactionGroups.findWhere(n => /👍/u.test(n.text()) && /\b10\b/.test(n.text())), 1); + assert.lengthOf(reactionGroups.findWhere(n => /👎/u.test(n.text()) && /\b5\b/.test(n.text())), 1); + assert.isFalse(reactionGroups.someWhere(n => /😆/u.test(n.text()))); + + assert.isNull(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); + assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); + assert.isNotNull(wrapper.find('Relay(BarePrStatusesView)[displayType="full"]').prop('pullRequest')); + + assert.strictEqual(wrapper.find('.github-IssueishDetailView-commitCount').text(), `${commitCount} commits`); + assert.strictEqual(wrapper.find('.github-IssueishDetailView-fileCount').text(), `${fileCount} changed files`); + + assert.strictEqual(wrapper.find('.github-IssueishDetailView-baseRefName').text(), baseRefName); + assert.strictEqual(wrapper.find('.github-IssueishDetailView-headRefName').text(), headRefName); + }); + + it('renders tabs', function() { + const wrapper = shallow(buildApp({})); + + assert.lengthOf(wrapper.find(Tabs), 1); + assert.lengthOf(wrapper.find(TabList), 1); + + const tabs = wrapper.find(Tab).getElements(); + assert.lengthOf(tabs, 3); + + const tab0Children = tabs[0].props.children; + assert.deepEqual(tab0Children[0].props, {icon: 'info', className: 'github-IssueishDetailView-tab-icon'}); + assert.deepEqual(tab0Children[1], 'Overview'); + + const tab1Children = tabs[1].props.children; + assert.deepEqual(tab1Children[0].props, {icon: 'checklist', className: 'github-IssueishDetailView-tab-icon'}); + assert.deepEqual(tab1Children[1], 'Build Status'); + + const tab2Children = tabs[2].props.children; + assert.deepEqual(tab2Children[0].props, {icon: 'git-commit', className: 'github-IssueishDetailView-tab-icon'}); + assert.deepEqual(tab2Children[1], 'Commits'); + + assert.lengthOf(wrapper.find(TabPanel), 3); + }); + + it('renders pull request information for cross repository PR', function() { + const baseRefName = 'master'; + const headRefName = 'tt-heck-yes'; + const ownerLogin = 'user0'; + const authorLogin = 'author0'; + const wrapper = shallow(buildApp({ + ownerLogin, + issueishBaseRef: baseRefName, + issueishHeadRef: headRefName, + issueishAuthorLogin: authorLogin, + issueishCrossRepository: true, + })); + + assert.strictEqual(wrapper.find('.github-IssueishDetailView-baseRefName').text(), `${ownerLogin}/${baseRefName}`); + assert.strictEqual(wrapper.find('.github-IssueishDetailView-headRefName').text(), `${authorLogin}/${headRefName}`); + }); + + it('renders issue information', function() { + const wrapper = shallow(buildApp({ + repositoryName: 'repo', + ownerLogin: 'user1', + + issueishKind: 'Issue', + issueishTitle: 'Issue title', + issueishBodyHTML: 'nope', + issueishAuthorLogin: 'author1', + issueishAuthorAvatarURL: 'https://avatars3.githubusercontent.com/u/2', + issueishNumber: 200, + issueishState: 'CLOSED', + issueishReactions: [{content: 'THUMBS_UP', count: 6}, {content: 'THUMBS_DOWN', count: 0}, {content: 'LAUGH', count: 2}], + }, { + checkoutOp: new EnableableOperation(() => {}).disable(checkoutStates.HIDDEN, 'An issue'), + })); + + const badge = wrapper.find('IssueishBadge'); + assert.strictEqual(badge.prop('type'), 'Issue'); + assert.strictEqual(badge.prop('state'), 'CLOSED'); + + const link = wrapper.find('a.github-IssueishDetailView-headerLink'); + assert.strictEqual(link.text(), 'user1/repo#200'); + assert.strictEqual(link.prop('href'), 'https://github.com/user1/repo/issues/200'); + + assert.isFalse(wrapper.find('Relay(PrStatuses)').exists()); + assert.isFalse(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); + + const avatarLink = wrapper.find('.github-IssueishDetailView-avatar'); + assert.strictEqual(avatarLink.prop('href'), 'https://github.com/author1'); + const avatar = avatarLink.find('img'); + assert.strictEqual(avatar.prop('src'), 'https://avatars3.githubusercontent.com/u/2'); + assert.strictEqual(avatar.prop('title'), 'author1'); + + assert.strictEqual(wrapper.find('.github-IssueishDetailView-title').text(), 'Issue title'); + + assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => n.prop('html') === 'nope')); + + const reactionGroups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); + assert.lengthOf(reactionGroups.findWhere(n => /👍/u.test(n.text()) && /\b6\b/.test(n.text())), 1); + assert.isFalse(reactionGroups.someWhere(n => /👎/u.test(n.text()))); + assert.lengthOf(reactionGroups.findWhere(n => /😆/u.test(n.text()) && /\b2\b/.test(n.text())), 1); + + assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); + assert.isNull(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); + }); + + it('renders a placeholder issueish body', function() { + const wrapper = shallow(buildApp({issueishBodyHTML: null})); + assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => /No description/.test(n.prop('html')))); + }); + + it('refreshes on click', function() { + let callback = null; + const relayRefetch = sinon.stub().callsFake((_0, _1, cb) => { + callback = cb; + }); + const wrapper = shallow(buildApp({relayRefetch}, {})); + + wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); + assert.isTrue(wrapper.find('Octicon[icon="repo-sync"]').hasClass('refreshing')); + + callback(); + wrapper.update(); + + assert.isFalse(wrapper.find('Octicon[icon="repo-sync"]').hasClass('refreshing')); + }); + + it('disregardes a double refresh', function() { + let callback = null; + const relayRefetch = sinon.stub().callsFake((_0, _1, cb) => { + callback = cb; + }); + const wrapper = shallow(buildApp({relayRefetch}, {})); + + wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); + assert.strictEqual(relayRefetch.callCount, 1); + + wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); + assert.strictEqual(relayRefetch.callCount, 1); + + callback(); + wrapper.update(); + + wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); + assert.strictEqual(relayRefetch.callCount, 2); + }); + + it('configures the refresher with a 5 minute polling interval', function() { + const wrapper = shallow(buildApp({})); + + assert.strictEqual(wrapper.instance().refresher.options.interval(), 5 * 60 * 1000); + }); + + it('destroys its refresher on unmount', function() { + const wrapper = shallow(buildApp({})); + + const refresher = wrapper.instance().refresher; + sinon.spy(refresher, 'destroy'); + + wrapper.unmount(); + + assert.isTrue(refresher.destroy.called); + }); + + describe('Checkout button', function() { + it('triggers its operation callback on click', function() { + const cb = sinon.spy(); + const checkoutOp = new EnableableOperation(cb); + const wrapper = shallow(buildApp({}, {checkoutOp})); + + const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); + assert.strictEqual(button.text(), 'Checkout'); + button.simulate('click'); + assert.isTrue(cb.called); + }); + + it('renders as disabled with hover text set to the disablement message', function() { + const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.DISABLED, 'message'); + const wrapper = shallow(buildApp({}, {checkoutOp})); + + const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); + assert.isTrue(button.prop('disabled')); + assert.strictEqual(button.text(), 'Checkout'); + assert.strictEqual(button.prop('title'), 'message'); + }); + + it('changes the button text when disabled because the PR is the current branch', function() { + const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.CURRENT, 'message'); + const wrapper = shallow(buildApp({}, {checkoutOp})); + + const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); + assert.isTrue(button.prop('disabled')); + assert.strictEqual(button.text(), 'Checked out'); + assert.strictEqual(button.prop('title'), 'message'); + }); + + it('renders hidden', function() { + const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.HIDDEN, 'message'); + const wrapper = shallow(buildApp({}, {checkoutOp})); + + assert.isFalse(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); + }); + }); + + describe('clicking link to view issueish link', function() { + it('records an event', function() { + const wrapper = shallow(buildApp({ + repositoryName: 'repo', + ownerLogin: 'user0', + issueishNumber: 100, + })); + + sinon.stub(reporterProxy, 'addEvent'); + + const link = wrapper.find('a.github-IssueishDetailView-headerLink'); + assert.strictEqual(link.text(), 'user0/repo#100'); + assert.strictEqual(link.prop('href'), 'https://github.com/user0/repo/pull/100'); + link.simulate('click'); + + assert.isTrue(reporterProxy.addEvent.calledWith('open-issueish-in-browser', {package: 'github', from: 'issueish-header'})); + }); + }); +}); diff --git a/test/views/pr-detail-view.test.js b/test/views/pr-detail-view.test.js new file mode 100644 index 0000000000..1a3be0a222 --- /dev/null +++ b/test/views/pr-detail-view.test.js @@ -0,0 +1,280 @@ +import React from 'react'; +import {shallow} from 'enzyme'; +import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; + +import {BarePullRequestDetailView, checkoutStates} from '../../lib/views/pr-detail-view'; +import {issueishDetailViewProps} from '../fixtures/props/issueish-pane-props'; +import EnableableOperation from '../../lib/models/enableable-operation'; +import * as reporterProxy from '../../lib/reporter-proxy'; + +describe('PullRequestDetailView', function() { + function buildApp(opts, overrideProps = {}) { + return ; + } + + it('renders pull request information', function() { + const commitCount = 11; + const fileCount = 22; + const baseRefName = 'master'; + const headRefName = 'tt/heck-yes'; + const wrapper = shallow(buildApp({ + repositoryName: 'repo', + ownerLogin: 'user0', + + issueishKind: 'PullRequest', + issueishTitle: 'PR title', + issueishBaseRef: baseRefName, + issueishHeadRef: headRefName, + issueishBodyHTML: 'stuff', + issueishAuthorLogin: 'author0', + issueishAuthorAvatarURL: 'https://avatars3.githubusercontent.com/u/1', + issueishNumber: 100, + issueishState: 'MERGED', + issueishCommitCount: commitCount, + issueishChangedFileCount: fileCount, + issueishReactions: [{content: 'THUMBS_UP', count: 10}, {content: 'THUMBS_DOWN', count: 5}, {content: 'LAUGH', count: 0}], + })); + + const badge = wrapper.find('IssueishBadge'); + assert.strictEqual(badge.prop('type'), 'PullRequest'); + assert.strictEqual(badge.prop('state'), 'MERGED'); + + const link = wrapper.find('a.github-IssueishDetailView-headerLink'); + assert.strictEqual(link.text(), 'user0/repo#100'); + assert.strictEqual(link.prop('href'), 'https://github.com/user0/repo/pull/100'); + + assert.isTrue(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); + + assert.isDefined(wrapper.find('Relay(BarePrStatusesView)[displayType="check"]').prop('pullRequest')); + + const avatarLink = wrapper.find('.github-IssueishDetailView-avatar'); + assert.strictEqual(avatarLink.prop('href'), 'https://github.com/author0'); + const avatar = avatarLink.find('img'); + assert.strictEqual(avatar.prop('src'), 'https://avatars3.githubusercontent.com/u/1'); + assert.strictEqual(avatar.prop('title'), 'author0'); + + assert.strictEqual(wrapper.find('.github-IssueishDetailView-title').text(), 'PR title'); + + assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => n.prop('html') === 'stuff')); + + const reactionGroups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); + assert.lengthOf(reactionGroups.findWhere(n => /👍/u.test(n.text()) && /\b10\b/.test(n.text())), 1); + assert.lengthOf(reactionGroups.findWhere(n => /👎/u.test(n.text()) && /\b5\b/.test(n.text())), 1); + assert.isFalse(reactionGroups.someWhere(n => /😆/u.test(n.text()))); + + assert.isNull(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); + assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); + assert.isNotNull(wrapper.find('Relay(BarePrStatusesView)[displayType="full"]').prop('pullRequest')); + + assert.strictEqual(wrapper.find('.github-IssueishDetailView-commitCount').text(), `${commitCount} commits`); + assert.strictEqual(wrapper.find('.github-IssueishDetailView-fileCount').text(), `${fileCount} changed files`); + + assert.strictEqual(wrapper.find('.github-IssueishDetailView-baseRefName').text(), baseRefName); + assert.strictEqual(wrapper.find('.github-IssueishDetailView-headRefName').text(), headRefName); + }); + + it('renders tabs', function() { + const wrapper = shallow(buildApp({})); + + assert.lengthOf(wrapper.find(Tabs), 1); + assert.lengthOf(wrapper.find(TabList), 1); + + const tabs = wrapper.find(Tab).getElements(); + assert.lengthOf(tabs, 3); + + const tab0Children = tabs[0].props.children; + assert.deepEqual(tab0Children[0].props, {icon: 'info', className: 'github-IssueishDetailView-tab-icon'}); + assert.deepEqual(tab0Children[1], 'Overview'); + + const tab1Children = tabs[1].props.children; + assert.deepEqual(tab1Children[0].props, {icon: 'checklist', className: 'github-IssueishDetailView-tab-icon'}); + assert.deepEqual(tab1Children[1], 'Build Status'); + + const tab2Children = tabs[2].props.children; + assert.deepEqual(tab2Children[0].props, {icon: 'git-commit', className: 'github-IssueishDetailView-tab-icon'}); + assert.deepEqual(tab2Children[1], 'Commits'); + + assert.lengthOf(wrapper.find(TabPanel), 3); + }); + + it('renders pull request information for cross repository PR', function() { + const baseRefName = 'master'; + const headRefName = 'tt-heck-yes'; + const ownerLogin = 'user0'; + const authorLogin = 'author0'; + const wrapper = shallow(buildApp({ + ownerLogin, + issueishBaseRef: baseRefName, + issueishHeadRef: headRefName, + issueishAuthorLogin: authorLogin, + issueishCrossRepository: true, + })); + + assert.strictEqual(wrapper.find('.github-IssueishDetailView-baseRefName').text(), `${ownerLogin}/${baseRefName}`); + assert.strictEqual(wrapper.find('.github-IssueishDetailView-headRefName').text(), `${authorLogin}/${headRefName}`); + }); + + it('renders issue information', function() { + const wrapper = shallow(buildApp({ + repositoryName: 'repo', + ownerLogin: 'user1', + + issueishKind: 'Issue', + issueishTitle: 'Issue title', + issueishBodyHTML: 'nope', + issueishAuthorLogin: 'author1', + issueishAuthorAvatarURL: 'https://avatars3.githubusercontent.com/u/2', + issueishNumber: 200, + issueishState: 'CLOSED', + issueishReactions: [{content: 'THUMBS_UP', count: 6}, {content: 'THUMBS_DOWN', count: 0}, {content: 'LAUGH', count: 2}], + }, { + checkoutOp: new EnableableOperation(() => {}).disable(checkoutStates.HIDDEN, 'An issue'), + })); + + const badge = wrapper.find('IssueishBadge'); + assert.strictEqual(badge.prop('type'), 'Issue'); + assert.strictEqual(badge.prop('state'), 'CLOSED'); + + const link = wrapper.find('a.github-IssueishDetailView-headerLink'); + assert.strictEqual(link.text(), 'user1/repo#200'); + assert.strictEqual(link.prop('href'), 'https://github.com/user1/repo/issues/200'); + + assert.isFalse(wrapper.find('Relay(PrStatuses)').exists()); + assert.isFalse(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); + + const avatarLink = wrapper.find('.github-IssueishDetailView-avatar'); + assert.strictEqual(avatarLink.prop('href'), 'https://github.com/author1'); + const avatar = avatarLink.find('img'); + assert.strictEqual(avatar.prop('src'), 'https://avatars3.githubusercontent.com/u/2'); + assert.strictEqual(avatar.prop('title'), 'author1'); + + assert.strictEqual(wrapper.find('.github-IssueishDetailView-title').text(), 'Issue title'); + + assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => n.prop('html') === 'nope')); + + const reactionGroups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); + assert.lengthOf(reactionGroups.findWhere(n => /👍/u.test(n.text()) && /\b6\b/.test(n.text())), 1); + assert.isFalse(reactionGroups.someWhere(n => /👎/u.test(n.text()))); + assert.lengthOf(reactionGroups.findWhere(n => /😆/u.test(n.text()) && /\b2\b/.test(n.text())), 1); + + assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); + assert.isNull(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); + }); + + it('renders a placeholder issueish body', function() { + const wrapper = shallow(buildApp({issueishBodyHTML: null})); + assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => /No description/.test(n.prop('html')))); + }); + + it('refreshes on click', function() { + let callback = null; + const relayRefetch = sinon.stub().callsFake((_0, _1, cb) => { + callback = cb; + }); + const wrapper = shallow(buildApp({relayRefetch}, {})); + + wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); + assert.isTrue(wrapper.find('Octicon[icon="repo-sync"]').hasClass('refreshing')); + + callback(); + wrapper.update(); + + assert.isFalse(wrapper.find('Octicon[icon="repo-sync"]').hasClass('refreshing')); + }); + + it('disregardes a double refresh', function() { + let callback = null; + const relayRefetch = sinon.stub().callsFake((_0, _1, cb) => { + callback = cb; + }); + const wrapper = shallow(buildApp({relayRefetch}, {})); + + wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); + assert.strictEqual(relayRefetch.callCount, 1); + + wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); + assert.strictEqual(relayRefetch.callCount, 1); + + callback(); + wrapper.update(); + + wrapper.find('Octicon[icon="repo-sync"]').simulate('click', {preventDefault: () => {}}); + assert.strictEqual(relayRefetch.callCount, 2); + }); + + it('configures the refresher with a 5 minute polling interval', function() { + const wrapper = shallow(buildApp({})); + + assert.strictEqual(wrapper.instance().refresher.options.interval(), 5 * 60 * 1000); + }); + + it('destroys its refresher on unmount', function() { + const wrapper = shallow(buildApp({})); + + const refresher = wrapper.instance().refresher; + sinon.spy(refresher, 'destroy'); + + wrapper.unmount(); + + assert.isTrue(refresher.destroy.called); + }); + + describe('Checkout button', function() { + it('triggers its operation callback on click', function() { + const cb = sinon.spy(); + const checkoutOp = new EnableableOperation(cb); + const wrapper = shallow(buildApp({}, {checkoutOp})); + + const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); + assert.strictEqual(button.text(), 'Checkout'); + button.simulate('click'); + assert.isTrue(cb.called); + }); + + it('renders as disabled with hover text set to the disablement message', function() { + const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.DISABLED, 'message'); + const wrapper = shallow(buildApp({}, {checkoutOp})); + + const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); + assert.isTrue(button.prop('disabled')); + assert.strictEqual(button.text(), 'Checkout'); + assert.strictEqual(button.prop('title'), 'message'); + }); + + it('changes the button text when disabled because the PR is the current branch', function() { + const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.CURRENT, 'message'); + const wrapper = shallow(buildApp({}, {checkoutOp})); + + const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); + assert.isTrue(button.prop('disabled')); + assert.strictEqual(button.text(), 'Checked out'); + assert.strictEqual(button.prop('title'), 'message'); + }); + + it('renders hidden', function() { + const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.HIDDEN, 'message'); + const wrapper = shallow(buildApp({}, {checkoutOp})); + + assert.isFalse(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); + }); + }); + + describe('clicking link to view issueish link', function() { + it('records an event', function() { + const wrapper = shallow(buildApp({ + repositoryName: 'repo', + ownerLogin: 'user0', + issueishNumber: 100, + })); + + sinon.stub(reporterProxy, 'addEvent'); + + const link = wrapper.find('a.github-IssueishDetailView-headerLink'); + assert.strictEqual(link.text(), 'user0/repo#100'); + assert.strictEqual(link.prop('href'), 'https://github.com/user0/repo/pull/100'); + link.simulate('click'); + + assert.isTrue(reporterProxy.addEvent.calledWith('open-issueish-in-browser', {package: 'github', from: 'issueish-header'})); + }); + }); +}); From 426e9fab206f9ad032b8894c478412a68527604a Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 19 Nov 2018 11:08:38 +0900 Subject: [PATCH 1156/4053] Rename tab --- lib/items/commit-preview-item.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/items/commit-preview-item.js b/lib/items/commit-preview-item.js index 0f83c4255a..367ffde0f2 100644 --- a/lib/items/commit-preview-item.js +++ b/lib/items/commit-preview-item.js @@ -69,11 +69,11 @@ export default class CommitPreviewItem extends React.Component { } getTitle() { - return 'Commit preview'; + return 'Staged Changes'; } getIconName() { - return 'git-commit'; + return 'tasklist'; } getWorkingDirectory() { From abeed4d912511c9d777280844bec30b928809409 Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 19 Nov 2018 12:27:21 +0900 Subject: [PATCH 1157/4053] Move button into `StagingView` --- styles/commit-view.less | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/styles/commit-view.less b/styles/commit-view.less index 5151292b49..936f53e0bd 100644 --- a/styles/commit-view.less +++ b/styles/commit-view.less @@ -3,7 +3,6 @@ .github-CommitView { padding: @component-padding; - border-top: 1px solid @base-border-color; flex: 0 0 auto; &-editor { @@ -82,7 +81,10 @@ &-buttonWrapper { align-items: center; display: flex; - margin-bottom: 10px; + margin: 0 -@component-padding @component-padding -@component-padding; + padding: @component-padding; + padding-top: 0; + border-bottom: 1px solid @base-border-color; } &-coAuthorEditor { From a465a675b8b95ee05426b3d3bf0ab73297f20e60 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 21 Nov 2018 14:08:24 +0100 Subject: [PATCH 1158/4053] fix tests --- test/controllers/root-controller.test.js | 2 +- test/items/commit-preview-item.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 7edebaab67..3331616797 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -1116,7 +1116,7 @@ describe('RootController', function() { const uri = CommitPreviewItem.buildURI(workdir); const item = await atomEnv.workspace.open(uri); - assert.strictEqual(item.getTitle(), 'Commit preview'); + assert.strictEqual(item.getTitle(), 'Staged Changes'); assert.lengthOf(wrapper.update().find('CommitPreviewItem'), 1); }); diff --git a/test/items/commit-preview-item.test.js b/test/items/commit-preview-item.test.js index f7a12d8bb1..0d66fe4140 100644 --- a/test/items/commit-preview-item.test.js +++ b/test/items/commit-preview-item.test.js @@ -96,8 +96,8 @@ describe('CommitPreviewItem', function() { const wrapper = mount(buildPaneApp()); const item = await open(wrapper); - assert.strictEqual(item.getTitle(), 'Commit preview'); - assert.strictEqual(item.getIconName(), 'git-commit'); + assert.strictEqual(item.getTitle(), 'Staged Changes'); + assert.strictEqual(item.getIconName(), 'tasklist'); }); it('terminates pending state', async function() { From ea149d0a51677607e3fe4e34505acc1e5e23fedb Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 21 Nov 2018 15:13:39 +0100 Subject: [PATCH 1159/4053] Prepare 0.23.0-0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index fdce2d3396..73724f47a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.22.1-0", + "version": "0.23.0-0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b6f52b0e0a..696adf5695 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.22.1-0", + "version": "0.23.0-0", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From 27595d8b976244a94ef789cc337854c7802d7e9d Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 21 Nov 2018 19:01:28 +0100 Subject: [PATCH 1160/4053] Prepare 0.23.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 73724f47a2..3b1189c3a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.23.0-0", + "version": "0.23.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 696adf5695..628a56bc5d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.23.0-0", + "version": "0.23.0", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From 6e0781600cccc3de2cc981f0d43209bf31cf86c8 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 10:09:23 -0800 Subject: [PATCH 1161/4053] Change order of keymap entries so `cmd-enter` is last and gets displayed Previously `ctrl-enter` (which maps to the same command) was displayed and this is masked by another commonly used package called `intentions` (dependency for `linter-ui-default`) --- keymaps/git.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keymaps/git.cson b/keymaps/git.cson index 36bda93362..038e4d942c 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -51,8 +51,8 @@ 'ctrl-/': 'github:toggle-patch-selection-mode' 'cmd-backspace': 'github:discard-selected-lines' 'ctrl-backspace': 'github:discard-selected-lines' - 'cmd-enter': 'core:confirm' 'ctrl-enter': 'core:confirm' + 'cmd-enter': 'core:confirm' 'cmd-right': 'github:surface' 'ctrl-right': 'github:surface' 'cmd-o': 'github:jump-to-file' From 5abde1f2893812b67b5fe0123964b2a519dbd2c1 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 12:43:00 -0800 Subject: [PATCH 1162/4053] Add GSOS#getDiffsForCommit and test --- lib/git-shell-out-strategy.js | 8 ++++++++ test/git-strategies.test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 0e8f303d4c..1addc791b3 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -687,6 +687,14 @@ export default class GitShellOutStrategy { return headCommit; } + async getDiffsForCommit(sha) { + const output = await this.exec([ + 'diff', '--no-prefix', '--no-ext-diff', '--no-renames', `${sha}~`, sha, + ]); + + return parseDiff(output); + } + async getCommits(options = {}) { const {max, ref, includeUnborn} = {max: 1, ref: 'HEAD', includeUnborn: false, ...options}; diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 34a24fb290..574dceadd4 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -158,6 +158,36 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); }); + describe('getDiffsForCommit(sha)', function() { + it('returns the diff for the specified commit sha', async function() { + const workingDirPath = await cloneRepository('multiple-commits'); + const git = createTestStrategy(workingDirPath); + + const diffs = await git.getDiffForCommit('18920c90'); + + assertDeepPropertyVals(diffs, [{ + oldPath: 'file.txt', + newPath: 'file.txt', + oldMode: '100644', + newMode: '100644', + hunks: [ + { + oldStartLine: 1, + oldLineCount: 1, + newStartLine: 1, + newLineCount: 1, + heading: '', + lines: [ + '-one', + '+two', + ], + }, + ], + status: 'modified', + }]); + }); + }); + describe('getCommits()', function() { describe('when no commits exist in the repository', function() { it('returns an array with an unborn ref commit when the include unborn option is passed', async function() { From eb33910ccdbb5888d9fdfd9091b0e864b549f716 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 21 Nov 2018 13:07:15 -0800 Subject: [PATCH 1163/4053] clean up `IssueDetailView` tests --- test/views/issue-detail-view.test.js | 151 +-------------------------- 1 file changed, 3 insertions(+), 148 deletions(-) diff --git a/test/views/issue-detail-view.test.js b/test/views/issue-detail-view.test.js index 18b156d8fa..c4ba427a2a 100644 --- a/test/views/issue-detail-view.test.js +++ b/test/views/issue-detail-view.test.js @@ -1,10 +1,10 @@ import React from 'react'; import {shallow} from 'enzyme'; -import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; // probably don't need this long term. import {checkoutStates} from '../../lib/views/pr-detail-view'; import {BareIssueDetailView} from '../../lib/views/issue-detail-view'; +import EmojiReactionsView from '../../lib/views/emoji-reactions-view'; import {issueishDetailViewProps} from '../fixtures/props/issueish-pane-props'; import EnableableOperation from '../../lib/models/enableable-operation'; import * as reporterProxy from '../../lib/reporter-proxy'; @@ -14,108 +14,6 @@ describe('IssueDetailView', function() { return ; } - it('renders pull request information', function() { - const commitCount = 11; - const fileCount = 22; - const baseRefName = 'master'; - const headRefName = 'tt/heck-yes'; - const wrapper = shallow(buildApp({ - repositoryName: 'repo', - ownerLogin: 'user0', - - issueishKind: 'PullRequest', - issueishTitle: 'PR title', - issueishBaseRef: baseRefName, - issueishHeadRef: headRefName, - issueishBodyHTML: 'stuff', - issueishAuthorLogin: 'author0', - issueishAuthorAvatarURL: 'https://avatars3.githubusercontent.com/u/1', - issueishNumber: 100, - issueishState: 'MERGED', - issueishCommitCount: commitCount, - issueishChangedFileCount: fileCount, - issueishReactions: [{content: 'THUMBS_UP', count: 10}, {content: 'THUMBS_DOWN', count: 5}, {content: 'LAUGH', count: 0}], - })); - - const badge = wrapper.find('IssueishBadge'); - assert.strictEqual(badge.prop('type'), 'PullRequest'); - assert.strictEqual(badge.prop('state'), 'MERGED'); - - const link = wrapper.find('a.github-IssueishDetailView-headerLink'); - assert.strictEqual(link.text(), 'user0/repo#100'); - assert.strictEqual(link.prop('href'), 'https://github.com/user0/repo/pull/100'); - - assert.isTrue(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); - - assert.isDefined(wrapper.find('Relay(BarePrStatusesView)[displayType="check"]').prop('pullRequest')); - - const avatarLink = wrapper.find('.github-IssueishDetailView-avatar'); - assert.strictEqual(avatarLink.prop('href'), 'https://github.com/author0'); - const avatar = avatarLink.find('img'); - assert.strictEqual(avatar.prop('src'), 'https://avatars3.githubusercontent.com/u/1'); - assert.strictEqual(avatar.prop('title'), 'author0'); - - assert.strictEqual(wrapper.find('.github-IssueishDetailView-title').text(), 'PR title'); - - assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => n.prop('html') === 'stuff')); - - const reactionGroups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); - assert.lengthOf(reactionGroups.findWhere(n => /👍/u.test(n.text()) && /\b10\b/.test(n.text())), 1); - assert.lengthOf(reactionGroups.findWhere(n => /👎/u.test(n.text()) && /\b5\b/.test(n.text())), 1); - assert.isFalse(reactionGroups.someWhere(n => /😆/u.test(n.text()))); - - assert.isNull(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); - assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); - assert.isNotNull(wrapper.find('Relay(BarePrStatusesView)[displayType="full"]').prop('pullRequest')); - - assert.strictEqual(wrapper.find('.github-IssueishDetailView-commitCount').text(), `${commitCount} commits`); - assert.strictEqual(wrapper.find('.github-IssueishDetailView-fileCount').text(), `${fileCount} changed files`); - - assert.strictEqual(wrapper.find('.github-IssueishDetailView-baseRefName').text(), baseRefName); - assert.strictEqual(wrapper.find('.github-IssueishDetailView-headRefName').text(), headRefName); - }); - - it('renders tabs', function() { - const wrapper = shallow(buildApp({})); - - assert.lengthOf(wrapper.find(Tabs), 1); - assert.lengthOf(wrapper.find(TabList), 1); - - const tabs = wrapper.find(Tab).getElements(); - assert.lengthOf(tabs, 3); - - const tab0Children = tabs[0].props.children; - assert.deepEqual(tab0Children[0].props, {icon: 'info', className: 'github-IssueishDetailView-tab-icon'}); - assert.deepEqual(tab0Children[1], 'Overview'); - - const tab1Children = tabs[1].props.children; - assert.deepEqual(tab1Children[0].props, {icon: 'checklist', className: 'github-IssueishDetailView-tab-icon'}); - assert.deepEqual(tab1Children[1], 'Build Status'); - - const tab2Children = tabs[2].props.children; - assert.deepEqual(tab2Children[0].props, {icon: 'git-commit', className: 'github-IssueishDetailView-tab-icon'}); - assert.deepEqual(tab2Children[1], 'Commits'); - - assert.lengthOf(wrapper.find(TabPanel), 3); - }); - - it('renders pull request information for cross repository PR', function() { - const baseRefName = 'master'; - const headRefName = 'tt-heck-yes'; - const ownerLogin = 'user0'; - const authorLogin = 'author0'; - const wrapper = shallow(buildApp({ - ownerLogin, - issueishBaseRef: baseRefName, - issueishHeadRef: headRefName, - issueishAuthorLogin: authorLogin, - issueishCrossRepository: true, - })); - - assert.strictEqual(wrapper.find('.github-IssueishDetailView-baseRefName').text(), `${ownerLogin}/${baseRefName}`); - assert.strictEqual(wrapper.find('.github-IssueishDetailView-headRefName').text(), `${authorLogin}/${headRefName}`); - }); - it('renders issue information', function() { const wrapper = shallow(buildApp({ repositoryName: 'repo', @@ -154,16 +52,13 @@ describe('IssueDetailView', function() { assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => n.prop('html') === 'nope')); - const reactionGroups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); - assert.lengthOf(reactionGroups.findWhere(n => /👍/u.test(n.text()) && /\b6\b/.test(n.text())), 1); - assert.isFalse(reactionGroups.someWhere(n => /👎/u.test(n.text()))); - assert.lengthOf(reactionGroups.findWhere(n => /😆/u.test(n.text()) && /\b2\b/.test(n.text())), 1); + assert.lengthOf(wrapper.find(EmojiReactionsView), 1); assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); assert.isNull(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); }); - it('renders a placeholder issueish body', function() { + it('renders a placeholder issue body', function() { const wrapper = shallow(buildApp({issueishBodyHTML: null})); assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => /No description/.test(n.prop('html')))); }); @@ -221,46 +116,6 @@ describe('IssueDetailView', function() { assert.isTrue(refresher.destroy.called); }); - describe('Checkout button', function() { - it('triggers its operation callback on click', function() { - const cb = sinon.spy(); - const checkoutOp = new EnableableOperation(cb); - const wrapper = shallow(buildApp({}, {checkoutOp})); - - const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); - assert.strictEqual(button.text(), 'Checkout'); - button.simulate('click'); - assert.isTrue(cb.called); - }); - - it('renders as disabled with hover text set to the disablement message', function() { - const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.DISABLED, 'message'); - const wrapper = shallow(buildApp({}, {checkoutOp})); - - const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); - assert.isTrue(button.prop('disabled')); - assert.strictEqual(button.text(), 'Checkout'); - assert.strictEqual(button.prop('title'), 'message'); - }); - - it('changes the button text when disabled because the PR is the current branch', function() { - const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.CURRENT, 'message'); - const wrapper = shallow(buildApp({}, {checkoutOp})); - - const button = wrapper.find('.github-IssueishDetailView-checkoutButton'); - assert.isTrue(button.prop('disabled')); - assert.strictEqual(button.text(), 'Checked out'); - assert.strictEqual(button.prop('title'), 'message'); - }); - - it('renders hidden', function() { - const checkoutOp = new EnableableOperation(() => {}).disable(checkoutStates.HIDDEN, 'message'); - const wrapper = shallow(buildApp({}, {checkoutOp})); - - assert.isFalse(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); - }); - }); - describe('clicking link to view issueish link', function() { it('records an event', function() { const wrapper = shallow(buildApp({ From 45d1a45fe4b6549744be2b74a0dcdba3acdf9d8b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 13:07:50 -0800 Subject: [PATCH 1164/4053] Wire up `getCommit` --- lib/models/repository-states/present.js | 11 ++++++++++- lib/models/repository-states/state.js | 4 ++++ lib/models/repository.js | 1 + test/models/repository.test.js | 11 +++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index a9f9691a2a..3741647e9a 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -723,6 +723,15 @@ export default class Present extends State { }); } + getCommit(sha) { + return this.cache.getOrSet(Keys.blob.oneWith(sha), async () => { + const [commitMetaData] = await this.git().getCommits({max: 1, ref: sha}); + // todo: check need for error handling in the case of 0 commit and 1 commit + const fileDiffs = await this.git().getDiffsForCommit(sha); + return {...commitMetaData, fileDiffs}; + }); + } + getRecentCommits(options) { return this.cache.getOrSet(Keys.recentCommits, async () => { const commits = await this.git().getCommits({ref: 'HEAD', ...options}); @@ -1097,7 +1106,7 @@ const Keys = { }, blob: { - oneWith: sha => `blob:${sha}`, + oneWith: sha => new CacheKey(`blob:${sha}`, ['blob']), }, // Common collections of keys and patterns for use with invalidate(). diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 85bb53e3a8..92961a777a 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -298,6 +298,10 @@ export default class State { return Promise.resolve(nullCommit); } + getCommit() { + return Promise.resolve(nullCommit); + } + getRecentCommits() { return Promise.resolve([]); } diff --git a/lib/models/repository.js b/lib/models/repository.js index 9c39d827eb..68202d136e 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -331,6 +331,7 @@ const delegates = [ 'readFileFromIndex', 'getLastCommit', + 'getCommit', 'getRecentCommits', 'getAuthors', diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 834567e9ca..2eaac205e5 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -731,6 +731,17 @@ describe('Repository', function() { }); }); + describe('getCommit(sha)', function() { + it('returns the commit information for the provided sha', async function() { + const workingDirPath = await cloneRepository('multiple-commits'); + const repo = new Repository(workingDirPath); + await repo.getLoadPromise(); + + console.log(await repo.getCommit('18920c90')); + // TODO ... + }); + }); + describe('undoLastCommit()', function() { it('performs a soft reset', async function() { const workingDirPath = await cloneRepository('multiple-commits'); From 3e7bf91db66fe08a37d40129141f9527a3bb5bbc Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 21 Nov 2018 13:16:49 -0800 Subject: [PATCH 1165/4053] clean up some unused dependencies --- test/views/issue-detail-view.test.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/test/views/issue-detail-view.test.js b/test/views/issue-detail-view.test.js index c4ba427a2a..bb303b91fb 100644 --- a/test/views/issue-detail-view.test.js +++ b/test/views/issue-detail-view.test.js @@ -1,15 +1,12 @@ import React from 'react'; import {shallow} from 'enzyme'; -// probably don't need this long term. -import {checkoutStates} from '../../lib/views/pr-detail-view'; import {BareIssueDetailView} from '../../lib/views/issue-detail-view'; import EmojiReactionsView from '../../lib/views/emoji-reactions-view'; import {issueishDetailViewProps} from '../fixtures/props/issueish-pane-props'; -import EnableableOperation from '../../lib/models/enableable-operation'; import * as reporterProxy from '../../lib/reporter-proxy'; -describe('IssueDetailView', function() { +describe.only('IssueDetailView', function() { function buildApp(opts, overrideProps = {}) { return ; } @@ -27,9 +24,7 @@ describe('IssueDetailView', function() { issueishNumber: 200, issueishState: 'CLOSED', issueishReactions: [{content: 'THUMBS_UP', count: 6}, {content: 'THUMBS_DOWN', count: 0}, {content: 'LAUGH', count: 2}], - }, { - checkoutOp: new EnableableOperation(() => {}).disable(checkoutStates.HIDDEN, 'An issue'), - })); + }, {})); const badge = wrapper.find('IssueishBadge'); assert.strictEqual(badge.prop('type'), 'Issue'); From 46aeeb089681e354a82ad019cbb24d4f0d4d97c3 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 21 Nov 2018 13:19:29 -0800 Subject: [PATCH 1166/4053] cleanup PullRequestDetailView tests --- test/views/pr-detail-view.test.js | 53 ++----------------------------- 1 file changed, 2 insertions(+), 51 deletions(-) diff --git a/test/views/pr-detail-view.test.js b/test/views/pr-detail-view.test.js index 1a3be0a222..ded367ef7d 100644 --- a/test/views/pr-detail-view.test.js +++ b/test/views/pr-detail-view.test.js @@ -3,6 +3,7 @@ import {shallow} from 'enzyme'; import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; import {BarePullRequestDetailView, checkoutStates} from '../../lib/views/pr-detail-view'; +import EmojiReactionsView from '../../lib/views/emoji-reactions-view'; import {issueishDetailViewProps} from '../fixtures/props/issueish-pane-props'; import EnableableOperation from '../../lib/models/enableable-operation'; import * as reporterProxy from '../../lib/reporter-proxy'; @@ -57,10 +58,7 @@ describe('PullRequestDetailView', function() { assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => n.prop('html') === 'stuff')); - const reactionGroups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); - assert.lengthOf(reactionGroups.findWhere(n => /👍/u.test(n.text()) && /\b10\b/.test(n.text())), 1); - assert.lengthOf(reactionGroups.findWhere(n => /👎/u.test(n.text()) && /\b5\b/.test(n.text())), 1); - assert.isFalse(reactionGroups.someWhere(n => /😆/u.test(n.text()))); + assert.lengthOf(wrapper.find(EmojiReactionsView), 1); assert.isNull(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); @@ -114,53 +112,6 @@ describe('PullRequestDetailView', function() { assert.strictEqual(wrapper.find('.github-IssueishDetailView-headRefName').text(), `${authorLogin}/${headRefName}`); }); - it('renders issue information', function() { - const wrapper = shallow(buildApp({ - repositoryName: 'repo', - ownerLogin: 'user1', - - issueishKind: 'Issue', - issueishTitle: 'Issue title', - issueishBodyHTML: 'nope', - issueishAuthorLogin: 'author1', - issueishAuthorAvatarURL: 'https://avatars3.githubusercontent.com/u/2', - issueishNumber: 200, - issueishState: 'CLOSED', - issueishReactions: [{content: 'THUMBS_UP', count: 6}, {content: 'THUMBS_DOWN', count: 0}, {content: 'LAUGH', count: 2}], - }, { - checkoutOp: new EnableableOperation(() => {}).disable(checkoutStates.HIDDEN, 'An issue'), - })); - - const badge = wrapper.find('IssueishBadge'); - assert.strictEqual(badge.prop('type'), 'Issue'); - assert.strictEqual(badge.prop('state'), 'CLOSED'); - - const link = wrapper.find('a.github-IssueishDetailView-headerLink'); - assert.strictEqual(link.text(), 'user1/repo#200'); - assert.strictEqual(link.prop('href'), 'https://github.com/user1/repo/issues/200'); - - assert.isFalse(wrapper.find('Relay(PrStatuses)').exists()); - assert.isFalse(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); - - const avatarLink = wrapper.find('.github-IssueishDetailView-avatar'); - assert.strictEqual(avatarLink.prop('href'), 'https://github.com/author1'); - const avatar = avatarLink.find('img'); - assert.strictEqual(avatar.prop('src'), 'https://avatars3.githubusercontent.com/u/2'); - assert.strictEqual(avatar.prop('title'), 'author1'); - - assert.strictEqual(wrapper.find('.github-IssueishDetailView-title').text(), 'Issue title'); - - assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => n.prop('html') === 'nope')); - - const reactionGroups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); - assert.lengthOf(reactionGroups.findWhere(n => /👍/u.test(n.text()) && /\b6\b/.test(n.text())), 1); - assert.isFalse(reactionGroups.someWhere(n => /👎/u.test(n.text()))); - assert.lengthOf(reactionGroups.findWhere(n => /😆/u.test(n.text()) && /\b2\b/.test(n.text())), 1); - - assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); - assert.isNull(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); - }); - it('renders a placeholder issueish body', function() { const wrapper = shallow(buildApp({issueishBodyHTML: null})); assert.isTrue(wrapper.find('GithubDotcomMarkdown').someWhere(n => /No description/.test(n.prop('html')))); From a70d00b640f5f553733eae4cf6916eb86279fd74 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 21 Nov 2018 13:20:17 -0800 Subject: [PATCH 1167/4053] :shirt: etc --- lib/views/pr-detail-view.js | 2 +- test/views/issue-detail-view.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index fd94084a32..81003b9a6b 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -1,4 +1,4 @@ -import React, {Fragment} from 'react'; +import React from 'react'; import {graphql, createRefetchContainer} from 'react-relay'; import PropTypes from 'prop-types'; import cx from 'classnames'; diff --git a/test/views/issue-detail-view.test.js b/test/views/issue-detail-view.test.js index bb303b91fb..23170b1500 100644 --- a/test/views/issue-detail-view.test.js +++ b/test/views/issue-detail-view.test.js @@ -6,7 +6,7 @@ import EmojiReactionsView from '../../lib/views/emoji-reactions-view'; import {issueishDetailViewProps} from '../fixtures/props/issueish-pane-props'; import * as reporterProxy from '../../lib/reporter-proxy'; -describe.only('IssueDetailView', function() { +describe('IssueDetailView', function() { function buildApp(opts, overrideProps = {}) { return ; } From 820fa2798702e17219235c55aef218a542842de2 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 21 Nov 2018 13:33:07 -0800 Subject: [PATCH 1168/4053] add tests for `EmojiReactionsView` --- test/views/emoji-reactions-view.test.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/views/emoji-reactions-view.test.js diff --git a/test/views/emoji-reactions-view.test.js b/test/views/emoji-reactions-view.test.js new file mode 100644 index 0000000000..9c6e31e3b1 --- /dev/null +++ b/test/views/emoji-reactions-view.test.js @@ -0,0 +1,20 @@ +import React from 'react'; +import {shallow} from 'enzyme'; +import EmojiReactionsView from '../../lib/views/emoji-reactions-view'; + +describe('EmojiReactionsView', function() { + let wrapper; + const reactionGroups = [ + {content: 'THUMBS_UP', users: {totalCount: 10}}, + {content: 'THUMBS_DOWN', users: {totalCount: 5}}, + {content: 'LAUGH', users: {totalCount: 0}}]; + beforeEach(function() { + wrapper = shallow(); + }); + it('renders reaction groups', function() { + const groups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); + assert.lengthOf(groups.findWhere(n => /👍/u.test(n.text()) && /\b10\b/.test(n.text())), 1); + assert.lengthOf(groups.findWhere(n => /👎/u.test(n.text()) && /\b5\b/.test(n.text())), 1); + assert.isFalse(groups.someWhere(n => /😆/u.test(n.text()))); + }); +}); From dc4aaa79b0565527be0eba739e6332f367f36df8 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 21 Nov 2018 13:45:22 -0800 Subject: [PATCH 1169/4053] track specific component names in `addEvent` --- lib/views/issue-detail-view.js | 5 ++--- lib/views/pr-detail-view.js | 4 ++-- test/views/issue-detail-view.test.js | 2 +- test/views/pr-detail-view.test.js | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 020f07dd97..24b6e5c01a 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -57,7 +57,7 @@ export class BareIssueDetailView extends React.Component { constructor(props) { super(props); - autobind(this, 'handleRefreshClick', 'refresh', 'renderIssueBody'); + autobind(this, 'handleRefreshClick', 'refresh', 'renderIssueBody', 'recordOpenInBrowserEvent'); } componentDidMount() { @@ -154,9 +154,8 @@ export class BareIssueDetailView extends React.Component { this.refresher.refreshNow(true); } - // would be good to make this more specific in terms of the source component, etc. recordOpenInBrowserEvent() { - addEvent('open-issueish-in-browser', {package: 'github', from: 'issueish-header'}); + addEvent('open-issueish-in-browser', {package: 'github', component: this.constructor.name}); } refresh() { diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 81003b9a6b..3d6d742410 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -80,7 +80,7 @@ export class BarePullRequestDetailView extends React.Component { constructor(props) { super(props); - autobind(this, 'handleRefreshClick', 'refresh', 'renderPullRequestBody'); + autobind(this, 'handleRefreshClick', 'refresh', 'renderPullRequestBody', 'recordOpenInBrowserEvent'); } componentDidMount() { @@ -274,7 +274,7 @@ export class BarePullRequestDetailView extends React.Component { } recordOpenInBrowserEvent() { - addEvent('open-issueish-in-browser', {package: 'github', from: 'issueish-header'}); + addEvent('open-issueish-in-browser', {package: 'github', component: this.constructor.name}); } refresh() { diff --git a/test/views/issue-detail-view.test.js b/test/views/issue-detail-view.test.js index 23170b1500..564e5a4c82 100644 --- a/test/views/issue-detail-view.test.js +++ b/test/views/issue-detail-view.test.js @@ -126,7 +126,7 @@ describe('IssueDetailView', function() { assert.strictEqual(link.prop('href'), 'https://github.com/user0/repo/pull/100'); link.simulate('click'); - assert.isTrue(reporterProxy.addEvent.calledWith('open-issueish-in-browser', {package: 'github', from: 'issueish-header'})); + assert.isTrue(reporterProxy.addEvent.calledWith('open-issueish-in-browser', {package: 'github', component: 'BareIssueDetailView'})); }); }); }); diff --git a/test/views/pr-detail-view.test.js b/test/views/pr-detail-view.test.js index ded367ef7d..a4b7d3a43f 100644 --- a/test/views/pr-detail-view.test.js +++ b/test/views/pr-detail-view.test.js @@ -225,7 +225,7 @@ describe('PullRequestDetailView', function() { assert.strictEqual(link.prop('href'), 'https://github.com/user0/repo/pull/100'); link.simulate('click'); - assert.isTrue(reporterProxy.addEvent.calledWith('open-issueish-in-browser', {package: 'github', from: 'issueish-header'})); + assert.isTrue(reporterProxy.addEvent.calledWith('open-issueish-in-browser', {package: 'github', component: 'BarePullRequestDetailView'})); }); }); }); From dacd2f0a3f3ba1acc2171c5ef2eb860542e9789c Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 14:07:12 -0800 Subject: [PATCH 1170/4053] Add the framework for displaying commit details --- lib/containers/commit-detail-container.js | 42 ++++++ lib/controllers/commit-detail-controller.js | 26 ++++ lib/controllers/root-controller.js | 54 ++++++++ lib/github-package.js | 10 ++ lib/items/commit-detail-item.js | 93 +++++++++++++ lib/views/open-commit-dialog.js | 139 ++++++++++++++++++++ package.json | 3 +- 7 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 lib/containers/commit-detail-container.js create mode 100644 lib/controllers/commit-detail-controller.js create mode 100644 lib/items/commit-detail-item.js create mode 100644 lib/views/open-commit-dialog.js diff --git a/lib/containers/commit-detail-container.js b/lib/containers/commit-detail-container.js new file mode 100644 index 0000000000..b4d91765f6 --- /dev/null +++ b/lib/containers/commit-detail-container.js @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import yubikiri from 'yubikiri'; + +import ObserveModel from '../views/observe-model'; +import LoadingView from '../views/loading-view'; +import CommitDetailController from '../controllers/commit-detail-controller'; + +export default class CommitDetailContainer extends React.Component { + static propTypes = { + repository: PropTypes.object.isRequired, + sha: PropTypes.string.isRequired, + } + + fetchData = repository => { + return yubikiri({ + commit: repository.getCommit(this.props.sha), + }); + } + + render() { + console.log('container'); + return ( + + {this.renderResult} + + ); + } + + renderResult = data => { + if (this.props.repository.isLoading() || data === null) { + return ; + } + + return ( + + ); + } +} diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js new file mode 100644 index 0000000000..f1245cb904 --- /dev/null +++ b/lib/controllers/commit-detail-controller.js @@ -0,0 +1,26 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import MultiFilePatchController from './multi-file-patch-controller'; + +export default class CommitDetailController extends React.Component { + static propTypes = { + repository: PropTypes.object.isRequired, + + workspace: PropTypes.object.isRequired, + commands: PropTypes.object.isRequired, + keymaps: PropTypes.object.isRequired, + tooltips: PropTypes.object.isRequired, + config: PropTypes.object.isRequired, + + destroy: PropTypes.func.isRequired, + } + + render() { + return ( + + ); + } +} diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index ec5d22f536..a720cba46c 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -11,12 +11,14 @@ import Panel from '../atom/panel'; import PaneItem from '../atom/pane-item'; import CloneDialog from '../views/clone-dialog'; import OpenIssueishDialog from '../views/open-issueish-dialog'; +import OpenCommitDialog from '../views/open-commit-dialog'; import InitDialog from '../views/init-dialog'; import CredentialDialog from '../views/credential-dialog'; import Commands, {Command} from '../atom/commands'; import GitTimingsView from '../views/git-timings-view'; import ChangedFileItem from '../items/changed-file-item'; import IssueishDetailItem from '../items/issueish-detail-item'; +import CommitDetailItem from '../items/commit-detail-item'; import CommitPreviewItem from '../items/commit-preview-item'; import GitTabItem from '../items/git-tab-item'; import GitHubTabItem from '../items/github-tab-item'; @@ -139,6 +141,7 @@ export default class RootController extends React.Component { + ); } @@ -244,6 +248,22 @@ export default class RootController extends React.Component { ); } + renderOpenCommitDialog() { + if (!this.state.openCommitDialogActive) { + return null; + } + + return ( + + + + ); + } + renderCredentialDialog() { if (this.state.credentialDialogQuery === null) { return null; @@ -362,6 +382,24 @@ export default class RootController extends React.Component { /> )} + + {({itemHolder, params}) => ( + + )} + {({itemHolder, params}) => ( { + this.setState({openCommitDialogActive: true}); + } + showWaterfallDiagnostics() { this.props.workspace.open(GitTimingsView.buildURI()); } @@ -548,6 +590,18 @@ export default class RootController extends React.Component { this.setState({openIssueishDialogActive: false}); } + acceptOpenCommit = ({sha}) => { + const uri = CommitDetailItem.buildURI(sha); + this.setState({openCommitDialogActive: false}); + this.props.workspace.open(uri).then(() => { + addEvent('open-commit-in-pane', {package: 'github', from: 'dialog'}); + }); + } + + cancelOpenCommit = () => { + this.setState({openCommitDialogActive: false}); + } + surfaceFromFileAtPath = (filePath, stagingStatus) => { const gitTab = this.gitTabTracker.getComponent(); return gitTab && gitTab.focusAndSelectStagingItem(filePath, stagingStatus); diff --git a/lib/github-package.js b/lib/github-package.js index 0da56cd648..a0ed479710 100644 --- a/lib/github-package.js +++ b/lib/github-package.js @@ -389,6 +389,16 @@ export default class GithubPackage { return item; } + createCommitDetailStub({uri}) { + const item = StubItem.create('git-commit-detail', { + title: 'Commit', + }, uri); + if (this.controller) { + this.rerender(); + } + return item; + } + destroyGitTabItem() { if (this.gitTabStubItem) { this.gitTabStubItem.destroy(); diff --git a/lib/items/commit-detail-item.js b/lib/items/commit-detail-item.js new file mode 100644 index 0000000000..564b637144 --- /dev/null +++ b/lib/items/commit-detail-item.js @@ -0,0 +1,93 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Emitter} from 'event-kit'; + +import {WorkdirContextPoolPropType} from '../prop-types'; +import CommitDetailContainer from '../containers/commit-detail-container'; +import RefHolder from '../models/ref-holder'; + +export default class CommitDetailItem extends React.Component { + static propTypes = { + workdirContextPool: WorkdirContextPoolPropType.isRequired, + workingDirectory: PropTypes.string.isRequired, + sha: PropTypes.string.isRequired, + + surfaceToCommitDetailButton: PropTypes.func.isRequired, + } + + static uriPattern = 'atom-github://commit-detail?sha={sha}' + + static buildURI(sha) { + return `atom-github://commit-detail?sha=${encodeURIComponent(sha)}`; + } + + constructor(props) { + super(props); + + console.log('item'); + this.emitter = new Emitter(); + this.isDestroyed = false; + this.hasTerminatedPendingState = false; + this.refInitialFocus = new RefHolder(); + } + + terminatePendingState() { + if (!this.hasTerminatedPendingState) { + this.emitter.emit('did-terminate-pending-state'); + this.hasTerminatedPendingState = true; + } + } + + onDidTerminatePendingState(callback) { + return this.emitter.on('did-terminate-pending-state', callback); + } + + destroy = () => { + /* istanbul ignore else */ + if (!this.isDestroyed) { + this.emitter.emit('did-destroy'); + this.isDestroyed = true; + } + } + + onDidDestroy(callback) { + return this.emitter.on('did-destroy', callback); + } + + render() { + const repository = this.props.workdirContextPool.getContext(this.props.workingDirectory).getRepository(); + + return ( + + ); + } + + getTitle() { + return `Commit: ${this.props.sha}`; + } + + getIconName() { + return 'git-commit'; + } + + getWorkingDirectory() { + return this.props.workingDirectory; + } + + serialize() { + return { + deserializer: 'CommitDetailStub', + uri: CommitDetailItem.buildURI(this.props.sha), + }; + } + + focus() { + this.refInitialFocus.map(focusable => focusable.focus()); + } +} diff --git a/lib/views/open-commit-dialog.js b/lib/views/open-commit-dialog.js new file mode 100644 index 0000000000..6cd2f04d09 --- /dev/null +++ b/lib/views/open-commit-dialog.js @@ -0,0 +1,139 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {CompositeDisposable} from 'event-kit'; + +import Commands, {Command} from '../atom/commands'; +import {autobind} from '../helpers'; + +// const COMMIT_SHA_REGEX = /^(?:https?:\/\/)?github.com\/([^/]+)\/([^/]+)\/(?:issues|pull)\/(\d+)/; + +export default class OpenCommitDialog extends React.Component { + static propTypes = { + commandRegistry: PropTypes.object.isRequired, + didAccept: PropTypes.func, + didCancel: PropTypes.func, + } + + static defaultProps = { + didAccept: () => {}, + didCancel: () => {}, + } + + constructor(props, context) { + super(props, context); + autobind(this, 'accept', 'cancel', 'editorRefs', 'didChangeCommitSha'); + console.log('sdfasdf'); + + this.state = { + cloneDisabled: false, + }; + + this.subs = new CompositeDisposable(); + } + + componentDidMount() { + if (this.commitShaElement) { + setTimeout(() => this.commitShaElement.focus()); + } + } + + render() { + return this.renderDialog(); + } + + renderDialog() { + return ( +
+ + + + +
+ + {this.state.error && {this.state.error}} +
+
+ + +
+
+ ); + } + + accept() { + if (this.getCommitSha().length === 0) { + return; + } + + const parsed = this.parseSha(); + if (!parsed) { + this.setState({ + error: 'That is not a valid commit sha.', + }); + return; + } + const {sha} = parsed; + + this.props.didAccept({sha}); + } + + cancel() { + this.props.didCancel(); + } + + editorRefs(baseName) { + const elementName = `${baseName}Element`; + const modelName = `${baseName}Editor`; + const subName = `${baseName}Subs`; + const changeMethodName = `didChange${baseName[0].toUpperCase()}${baseName.substring(1)}`; + + return element => { + if (!element) { + return; + } + + this[elementName] = element; + const editor = element.getModel(); + if (this[modelName] !== editor) { + this[modelName] = editor; + + if (this[subName]) { + this[subName].dispose(); + this.subs.remove(this[subName]); + } + + this[subName] = editor.onDidChange(this[changeMethodName]); + this.subs.add(this[subName]); + } + }; + } + + didChangeCommitSha() { + this.setState({error: null}); + } + + parseSha() { + const sha = this.getCommitSha(); + // const matches = url.match(ISSUEISH_URL_REGEX); + // if (!matches) { + // return false; + // } + // const [_full, repoOwner, repoName, issueishNumber] = matches; // eslint-disable-line no-unused-vars + return {sha}; + } + + getCommitSha() { + return this.commitShaEditor ? this.commitShaEditor.getText() : ''; + } +} diff --git a/package.json b/package.json index 628a56bc5d..af60df2ba9 100644 --- a/package.json +++ b/package.json @@ -198,6 +198,7 @@ "GitDockItem": "createDockItemStub", "GithubDockItem": "createDockItemStub", "FilePatchControllerStub": "createFilePatchControllerStub", - "CommitPreviewStub": "createCommitPreviewStub" + "CommitPreviewStub": "createCommitPreviewStub", + "CommitDetailStub": "createCommitDetailStub" } } From 88f3b9a1e72df2541a2752ae8317f40c2f0b0908 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 21 Nov 2018 14:12:39 -0800 Subject: [PATCH 1171/4053] :skull: more dead code --- lib/views/issue-detail-view.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 24b6e5c01a..348e4b7285 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -169,8 +169,6 @@ export class BareIssueDetailView extends React.Component { issueishId: this.props.issueish.id, timelineCount: 100, timelineCursor: null, - commitCount: 100, - commitCursor: null, }, null, () => { this.setState({refreshing: false}); }, {force: true}); From 876a3dea2625845d1bf4f180f623b4757c6d2ae2 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 21 Nov 2018 14:38:33 -0800 Subject: [PATCH 1172/4053] fix `IssueishDetailController` tests --- .../issueish-detail-controller.test.js | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/test/controllers/issueish-detail-controller.test.js b/test/controllers/issueish-detail-controller.test.js index f44bda6fc9..8a6390695d 100644 --- a/test/controllers/issueish-detail-controller.test.js +++ b/test/controllers/issueish-detail-controller.test.js @@ -75,38 +75,31 @@ describe('IssueishDetailController', function() { }); describe('checkoutOp', function() { - it('is disabled if the issueish is an issue', function() { - const wrapper = shallow(buildApp({issueishKind: 'Issue'})); - const op = wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp'); - assert.isFalse(op.isEnabled()); - assert.strictEqual(op.getMessage(), 'Cannot check out an issue'); - }); - it('is disabled if the repository is loading or absent', function() { const wrapper = shallow(buildApp({}, {isAbsent: true})); - const op = wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp'); + const op = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); assert.isFalse(op.isEnabled()); assert.strictEqual(op.getMessage(), 'No repository found'); wrapper.setProps({isAbsent: false, isLoading: true}); - const op1 = wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp'); + const op1 = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); assert.isFalse(op1.isEnabled()); assert.strictEqual(op1.getMessage(), 'Loading'); wrapper.setProps({isAbsent: false, isLoading: false, isPresent: false}); - const op2 = wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp'); + const op2 = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); assert.isFalse(op2.isEnabled()); assert.strictEqual(op2.getMessage(), 'No repository found'); }); it('is disabled if the local repository is merging or rebasing', function() { const wrapper = shallow(buildApp({}, {isMerging: true})); - const op0 = wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp'); + const op0 = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); assert.isFalse(op0.isEnabled()); assert.strictEqual(op0.getMessage(), 'Merge in progress'); wrapper.setProps({isMerging: false, isRebasing: true}); - const op1 = wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp'); + const op1 = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); assert.isFalse(op1.isEnabled()); assert.strictEqual(op1.getMessage(), 'Rebase in progress'); }); @@ -129,7 +122,7 @@ describe('IssueishDetailController', function() { remotes, })); - const op = wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp'); + const op = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); assert.isFalse(op.isEnabled()); assert.strictEqual(op.getMessage(), 'Current'); }); @@ -155,7 +148,7 @@ describe('IssueishDetailController', function() { remotes, })); - const op = wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp'); + const op = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); assert.isFalse(op.isEnabled()); assert.strictEqual(op.getMessage(), 'Current'); }); @@ -187,7 +180,7 @@ describe('IssueishDetailController', function() { })); sinon.spy(reporterProxy, 'incrementCounter'); - await wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp').run(); + await wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp').run(); assert.isTrue(addRemote.calledWith('ccc', 'git@github.com:ccc/ddd.git')); assert.isTrue(fetch.calledWith('refs/heads/feature', {remoteName: 'ccc'})); @@ -225,7 +218,7 @@ describe('IssueishDetailController', function() { })); sinon.spy(reporterProxy, 'incrementCounter'); - await wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp').run(); + await wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp').run(); assert.isTrue(fetch.calledWith('refs/heads/clever-name', {remoteName: 'existing'})); assert.isTrue(checkout.calledWith('pr-789/ccc/clever-name', { @@ -268,7 +261,7 @@ describe('IssueishDetailController', function() { })); sinon.spy(reporterProxy, 'incrementCounter'); - await wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp').run(); + await wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp').run(); assert.isTrue(checkout.calledWith('existing')); assert.isTrue(pull.calledWith('refs/heads/yes', {remoteName: 'upstream', ffOnly: true})); @@ -280,7 +273,7 @@ describe('IssueishDetailController', function() { const wrapper = shallow(buildApp({}, {addRemote})); // Should not throw - await wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp').run(); + await wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp').run(); assert.isTrue(addRemote.called); }); @@ -289,7 +282,7 @@ describe('IssueishDetailController', function() { const wrapper = shallow(buildApp({}, {addRemote})); await assert.isRejected( - wrapper.find('Relay(BareIssueishDetailView)').prop('checkoutOp').run(), + wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp').run(), /not handled by the pipeline/, ); assert.isTrue(addRemote.called); From 3f692793e3de8396ffaab4c1d41d983d64bd4e42 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 15:08:30 -0800 Subject: [PATCH 1173/4053] Get CommitDetailItem to actually render! --- lib/containers/commit-detail-container.js | 2 +- lib/controllers/commit-detail-controller.js | 1 + .../multi-file-patch-controller.js | 6 ++-- lib/controllers/root-controller.js | 5 ++- lib/items/commit-detail-item.js | 7 ++-- lib/models/commit.js | 9 +++++ lib/models/repository-states/present.js | 8 +++-- lib/views/hunk-header-view.js | 10 +++--- lib/views/multi-file-patch-view.js | 36 ++++++++++++------- 9 files changed, 54 insertions(+), 30 deletions(-) diff --git a/lib/containers/commit-detail-container.js b/lib/containers/commit-detail-container.js index b4d91765f6..1c5d3022d8 100644 --- a/lib/containers/commit-detail-container.js +++ b/lib/containers/commit-detail-container.js @@ -28,7 +28,7 @@ export default class CommitDetailContainer extends React.Component { } renderResult = data => { - if (this.props.repository.isLoading() || data === null) { + if (this.props.repository.isLoading() || data === null || !data.commit.isPresent()) { return ; } diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index f1245cb904..1eac077294 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -19,6 +19,7 @@ export default class CommitDetailController extends React.Component { render() { return ( ); diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 09944505c5..cdba88def6 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -22,9 +22,9 @@ export default class MultiFilePatchController extends React.Component { config: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, - discardLines: PropTypes.func.isRequired, - undoLastDiscard: PropTypes.func.isRequired, - surface: PropTypes.func.isRequired, + discardLines: PropTypes.func, + undoLastDiscard: PropTypes.func, + surface: PropTypes.func, } constructor(props) { diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index a720cba46c..c57c7a9d4f 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -397,6 +397,8 @@ export default class RootController extends React.Component { keymaps={this.props.keymaps} tooltips={this.props.tooltips} config={this.props.config} + + sha={params.sha} /> )}
@@ -591,7 +593,8 @@ export default class RootController extends React.Component { } acceptOpenCommit = ({sha}) => { - const uri = CommitDetailItem.buildURI(sha); + const workdir = this.props.repository.getWorkingDirectoryPath(); + const uri = CommitDetailItem.buildURI(workdir, sha); this.setState({openCommitDialogActive: false}); this.props.workspace.open(uri).then(() => { addEvent('open-commit-in-pane', {package: 'github', from: 'dialog'}); diff --git a/lib/items/commit-detail-item.js b/lib/items/commit-detail-item.js index 564b637144..90c023129f 100644 --- a/lib/items/commit-detail-item.js +++ b/lib/items/commit-detail-item.js @@ -15,16 +15,15 @@ export default class CommitDetailItem extends React.Component { surfaceToCommitDetailButton: PropTypes.func.isRequired, } - static uriPattern = 'atom-github://commit-detail?sha={sha}' + static uriPattern = 'atom-github://commit-detail?workdir={workingDirectory}&sha={sha}' - static buildURI(sha) { - return `atom-github://commit-detail?sha=${encodeURIComponent(sha)}`; + static buildURI(workingDirectory, sha) { + return `atom-github://commit-detail?workdir=${encodeURIComponent(workingDirectory)}&sha=${encodeURIComponent(sha)}`; } constructor(props) { super(props); - console.log('item'); this.emitter = new Emitter(); this.isDestroyed = false; this.hasTerminatedPendingState = false; diff --git a/lib/models/commit.js b/lib/models/commit.js index 6bfa738b8d..f1fbd72a1c 100644 --- a/lib/models/commit.js +++ b/lib/models/commit.js @@ -13,6 +13,7 @@ export default class Commit { this.messageSubject = messageSubject; this.messageBody = messageBody; this.unbornRef = unbornRef === UNBORN; + this.multiFileDiff = null; } getSha() { @@ -43,6 +44,14 @@ export default class Commit { return `${this.getMessageSubject()}\n\n${this.getMessageBody()}`.trim(); } + setMultiFileDiff(multiFileDiff) { + this.multiFileDiff = multiFileDiff; + } + + getMultiFileDiff() { + return this.multiFileDiff; + } + isUnbornRef() { return this.unbornRef; } diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 3741647e9a..bc28589081 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -725,10 +725,12 @@ export default class Present extends State { getCommit(sha) { return this.cache.getOrSet(Keys.blob.oneWith(sha), async () => { - const [commitMetaData] = await this.git().getCommits({max: 1, ref: sha}); + const [rawCommitMetadata] = await this.git().getCommits({max: 1, ref: sha}); + const commit = new Commit(rawCommitMetadata); // todo: check need for error handling in the case of 0 commit and 1 commit - const fileDiffs = await this.git().getDiffsForCommit(sha); - return {...commitMetaData, fileDiffs}; + const multiFileDiff = await this.git().getDiffsForCommit(sha).then(buildMultiFilePatch); + commit.setMultiFileDiff(multiFileDiff); + return commit; }); } diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index cb72feb36a..8ee5e96dda 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -17,16 +17,16 @@ export default class HunkHeaderView extends React.Component { refTarget: RefHolderPropType.isRequired, hunk: PropTypes.object.isRequired, isSelected: PropTypes.bool.isRequired, - stagingStatus: PropTypes.oneOf(['unstaged', 'staged']).isRequired, + stagingStatus: PropTypes.oneOf(['unstaged', 'staged']), selectionMode: PropTypes.oneOf(['hunk', 'line']).isRequired, - toggleSelectionLabel: PropTypes.string.isRequired, - discardSelectionLabel: PropTypes.string.isRequired, + toggleSelectionLabel: PropTypes.string, + discardSelectionLabel: PropTypes.string, tooltips: PropTypes.object.isRequired, keymaps: PropTypes.object.isRequired, - toggleSelection: PropTypes.func.isRequired, - discardSelection: PropTypes.func.isRequired, + toggleSelection: PropTypes.func, + discardSelection: PropTypes.func, mouseDown: PropTypes.func.isRequired, }; diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 0ff63f97b3..dc922a39d6 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -18,6 +18,7 @@ import HunkHeaderView from './hunk-header-view'; import RefHolder from '../models/ref-holder'; import ChangedFileItem from '../items/changed-file-item'; import CommitPreviewItem from '../items/commit-preview-item'; +import CommitDetailItem from '../items/commit-detail-item'; import File from '../models/patch/file'; const executableText = { @@ -31,7 +32,7 @@ const BLANK_LABEL = () => NBSP_CHARACTER; export default class MultiFilePatchView extends React.Component { static propTypes = { - stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, + stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), isPartiallyStaged: PropTypes.bool, multiFilePatch: MultiFilePatchPropType.isRequired, selectionMode: PropTypes.oneOf(['hunk', 'line']).isRequired, @@ -46,17 +47,17 @@ export default class MultiFilePatchView extends React.Component { tooltips: PropTypes.object.isRequired, config: PropTypes.object.isRequired, - selectedRowsChanged: PropTypes.func.isRequired, + selectedRowsChanged: PropTypes.func, - diveIntoMirrorPatch: PropTypes.func.isRequired, - surface: PropTypes.func.isRequired, - openFile: PropTypes.func.isRequired, - toggleFile: PropTypes.func.isRequired, - toggleRows: PropTypes.func.isRequired, - toggleModeChange: PropTypes.func.isRequired, - toggleSymlinkChange: PropTypes.func.isRequired, - undoLastDiscard: PropTypes.func.isRequired, - discardRows: PropTypes.func.isRequired, + diveIntoMirrorPatch: PropTypes.func, + surface: PropTypes.func, + openFile: PropTypes.func, + toggleFile: PropTypes.func, + toggleRows: PropTypes.func, + toggleModeChange: PropTypes.func, + toggleSymlinkChange: PropTypes.func, + undoLastDiscard: PropTypes.func, + discardRows: PropTypes.func, refInitialFocus: RefHolderPropType, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem]).isRequired, @@ -186,6 +187,15 @@ export default class MultiFilePatchView extends React.Component { } renderCommands() { + if (this.props.itemType === CommitDetailItem) { + return ( + + + + + ); + } + let stageModeCommand = null; let stageSymlinkCommand = null; @@ -205,11 +215,11 @@ export default class MultiFilePatchView extends React.Component { return ( + + - - From 2bdf25a73ce227e96110d57116d81667ad32a581 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 15:13:43 -0800 Subject: [PATCH 1174/4053] Clean up prop errors in the console --- lib/controllers/commit-detail-controller.js | 11 +++++++---- lib/items/commit-detail-item.js | 2 -- lib/views/file-patch-header-view.js | 5 +++-- lib/views/multi-file-patch-view.js | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index 1eac077294..035dea47e2 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -14,14 +14,17 @@ export default class CommitDetailController extends React.Component { config: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, + commit: PropTypes.object.isRequired, } render() { return ( - +
+ +
); } } diff --git a/lib/items/commit-detail-item.js b/lib/items/commit-detail-item.js index 90c023129f..ed8f5f1936 100644 --- a/lib/items/commit-detail-item.js +++ b/lib/items/commit-detail-item.js @@ -11,8 +11,6 @@ export default class CommitDetailItem extends React.Component { workdirContextPool: WorkdirContextPoolPropType.isRequired, workingDirectory: PropTypes.string.isRequired, sha: PropTypes.string.isRequired, - - surfaceToCommitDetailButton: PropTypes.func.isRequired, } static uriPattern = 'atom-github://commit-detail?workdir={workingDirectory}&sha={sha}' diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 8f7db7d8e2..97a54c81d9 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -7,11 +7,12 @@ import cx from 'classnames'; import RefHolder from '../models/ref-holder'; import ChangedFileItem from '../items/changed-file-item'; import CommitPreviewItem from '../items/commit-preview-item'; +import CommitDetailItem from '../items/commit-detail-item'; export default class FilePatchHeaderView extends React.Component { static propTypes = { relPath: PropTypes.string.isRequired, - stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, + stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), isPartiallyStaged: PropTypes.bool, hasHunks: PropTypes.bool.isRequired, hasUndoHistory: PropTypes.bool, @@ -24,7 +25,7 @@ export default class FilePatchHeaderView extends React.Component { openFile: PropTypes.func.isRequired, toggleFile: PropTypes.func.isRequired, - itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem]).isRequired, + itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, }; constructor(props) { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index dc922a39d6..0c81bd6cfd 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -60,7 +60,7 @@ export default class MultiFilePatchView extends React.Component { discardRows: PropTypes.func, refInitialFocus: RefHolderPropType, - itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem]).isRequired, + itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, } constructor(props) { From 31ed5f48c3e94b4c4eabdb7f59b0ede60e74f20e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 15:45:22 -0800 Subject: [PATCH 1175/4053] Display some commit metadata (needs prettifying!) --- lib/controllers/commit-detail-controller.js | 38 ++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index 035dea47e2..40f6ae4ad7 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -1,8 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; +import {emojify} from 'node-emoji'; +import moment from 'moment'; import MultiFilePatchController from './multi-file-patch-controller'; +const avatarAltText = 'committer avatar'; + export default class CommitDetailController extends React.Component { static propTypes = { repository: PropTypes.object.isRequired, @@ -18,13 +22,45 @@ export default class CommitDetailController extends React.Component { } render() { + const commit = this.props.commit; + // const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; + // const {avatarUrl, name, date} = this.props.item.committer; + return (
+
+

+ {emojify(commit.getMessageSubject())} +
+              {emojify(commit.getMessageBody())}
+

+
+ {/* TODO fix image src */} + {avatarAltText} + + {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} + +
+
+
+ {/* TODO fix href */} + + {commit.getSha()} + +
); } + + humanizeTimeSince(date) { + return moment(date).fromNow(); + } } From c5571490dc1f90ea7d2ae3597c7161ae795010b1 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 15:51:53 -0800 Subject: [PATCH 1176/4053] Render co-authors --- lib/controllers/commit-detail-controller.js | 36 ++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index 40f6ae4ad7..b61cdf2177 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -36,10 +36,7 @@ export default class CommitDetailController extends React.Component {
{/* TODO fix image src */} - {avatarAltText} + {this.renderAuthors()} {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} @@ -63,4 +60,35 @@ export default class CommitDetailController extends React.Component { humanizeTimeSince(date) { return moment(date).fromNow(); } + + renderAuthor(email) { + const match = email.match(/^(\d+)\+[^@]+@users.noreply.github.com$/); + + let avatarUrl; + if (match) { + avatarUrl = 'https://avatars.githubusercontent.com/u/' + match[1] + '?s=32'; + } else { + avatarUrl = 'https://avatars.githubusercontent.com/u/e?email=' + encodeURIComponent(email) + '&s=32'; + } + + return ( + {`${email}'s + ); + } + + renderAuthors() { + const coAuthorEmails = this.props.commit.getCoAuthors().map(author => author.email); + const authorEmails = [this.props.commit.getAuthorEmail(), ...coAuthorEmails]; + + return ( + + {authorEmails.map(this.renderAuthor)} + + ); + } } From fffd2cba2a3187b198199df9698cffad70bbc69c Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 15:57:37 -0800 Subject: [PATCH 1177/4053] Render author number count if co-authors present --- lib/controllers/commit-detail-controller.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index b61cdf2177..ef3a3f1e6c 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -61,6 +61,11 @@ export default class CommitDetailController extends React.Component { return moment(date).fromNow(); } + getAuthorInfo() { + const coAuthorCount = this.props.commit.getCoAuthors().length; + return coAuthorCount ? this.props.commit.getAuthorEmail() : `${coAuthorCount + 1} people`; + } + renderAuthor(email) { const match = email.match(/^(\d+)\+[^@]+@users.noreply.github.com$/); From 200f0e790658dd55916861ba83ed2abc8f880aee Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 21 Nov 2018 16:42:01 -0800 Subject: [PATCH 1178/4053] add height so commit shows up --- lib/containers/commit-detail-container.js | 1 - lib/controllers/commit-detail-controller.js | 1 + lib/controllers/commit-preview-controller.js | 1 + lib/controllers/multi-file-patch-controller.js | 1 + lib/views/multi-file-patch-view.js | 3 ++- lib/views/pr-commit-view.js | 1 + 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/containers/commit-detail-container.js b/lib/containers/commit-detail-container.js index 1c5d3022d8..911c73b5f8 100644 --- a/lib/containers/commit-detail-container.js +++ b/lib/containers/commit-detail-container.js @@ -19,7 +19,6 @@ export default class CommitDetailContainer extends React.Component { } render() { - console.log('container'); return ( {this.renderResult} diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index ef3a3f1e6c..f9926d9ef4 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -51,6 +51,7 @@ export default class CommitDetailController extends React.Component {
diff --git a/lib/controllers/commit-preview-controller.js b/lib/controllers/commit-preview-controller.js index f1ce3c988c..ff5f3cf72b 100644 --- a/lib/controllers/commit-preview-controller.js +++ b/lib/controllers/commit-preview-controller.js @@ -23,6 +23,7 @@ export default class CommitPreviewController extends React.Component { return ( ); diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index cdba88def6..667184aae4 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -25,6 +25,7 @@ export default class MultiFilePatchController extends React.Component { discardLines: PropTypes.func, undoLastDiscard: PropTypes.func, surface: PropTypes.func, + autoHeight: PropTypes.bool, } constructor(props) { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 0c81bd6cfd..7e2d775799 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -58,6 +58,7 @@ export default class MultiFilePatchView extends React.Component { toggleSymlinkChange: PropTypes.func, undoLastDiscard: PropTypes.func, discardRows: PropTypes.func, + autoHeight: PropTypes.bool, refInitialFocus: RefHolderPropType, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, @@ -241,7 +242,7 @@ export default class MultiFilePatchView extends React.Component { buffer={this.props.multiFilePatch.getBuffer()} lineNumberGutterVisible={false} autoWidth={false} - autoHeight={false} + autoHeight={this.props.autoHeight} readOnly={true} softWrapped={true} diff --git a/lib/views/pr-commit-view.js b/lib/views/pr-commit-view.js index 88d879a3cb..1d31e5947d 100644 --- a/lib/views/pr-commit-view.js +++ b/lib/views/pr-commit-view.js @@ -38,6 +38,7 @@ export class PrCommitView extends React.Component { } render() { + console.log('zzz'); const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; const {avatarUrl, name, date} = this.props.item.committer; return ( From 70bf46544bc37af6f19442bcf948aa6f374e8272 Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 22 Nov 2018 10:13:16 +0900 Subject: [PATCH 1179/4053] Add scrolling --- styles/commit-detail.less | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 styles/commit-detail.less diff --git a/styles/commit-detail.less b/styles/commit-detail.less new file mode 100644 index 0000000000..03fc322e95 --- /dev/null +++ b/styles/commit-detail.less @@ -0,0 +1,10 @@ +@import "variables"; + +.github-CommitDetail { + + &-root { + // TODO: Remove if CommitDetailView gets moved to a editor decoration + overflow-y: auto; + } + +} From 805d408755edf9157277df104aa0c87f171f8724 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 21 Nov 2018 17:46:26 -0800 Subject: [PATCH 1180/4053] remove `IssueOrPullRequest` from graphql --- .../issueishDetailContainerQuery.graphql.js | 781 +++++++++--------- .../issueDetailViewRefetchQuery.graphql.js | 153 ++-- .../issueDetailView_issueish.graphql.js | 176 ++-- .../prDetailViewRefetchQuery.graphql.js | 710 +++++++--------- .../prDetailView_issueish.graphql.js | 350 ++++---- lib/views/issue-detail-view.js | 2 +- lib/views/pr-detail-view.js | 13 +- 7 files changed, 1002 insertions(+), 1183 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 926426ec52..0e6f2d3818 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash ac2c809c9af012ee05b82f0b4817c27c + * @relayHash 09dccabcb8b4e4183795cdd6a281a62f */ /* eslint-disable */ @@ -106,32 +106,30 @@ fragment prDetailView_repository on Repository { } } -fragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest { +fragment issueDetailView_issueish_4cAEh0 on Issue { __typename ... on Node { id } - ... on Issue { - state - number - title - bodyHTML - author { - __typename - login - avatarUrl - ... on User { - url - } - ... on Bot { - url - } - ... on Node { - id - } + state + number + title + bodyHTML + author { + __typename + login + avatarUrl + ... on User { + url + } + ... on Bot { + url + } + ... on Node { + id } - ...issueTimelineController_issue_3D8CP9 } + ...issueTimelineController_issue_3D8CP9 ... on UniformResourceLocatable { url } @@ -145,62 +143,39 @@ fragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest { } } -fragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest { +fragment prDetailView_issueish_4cAEh0 on PullRequest { __typename ... on Node { id } - ... on Issue { - state - number - title - bodyHTML - author { - __typename - login - avatarUrl - ... on User { - url - } - ... on Bot { - url - } - ... on Node { - id - } - } - ...issueTimelineController_issue_3D8CP9 + isCrossRepository + changedFiles + ...prCommitsView_pullRequest_38TpXw + countedCommits: commits { + totalCount } - ... on PullRequest { - isCrossRepository - changedFiles - ...prCommitsView_pullRequest_38TpXw - countedCommits: commits { - totalCount + ...prStatusesView_pullRequest + state + number + title + bodyHTML + baseRefName + headRefName + author { + __typename + login + avatarUrl + ... on User { + url } - ...prStatusesView_pullRequest - state - number - title - bodyHTML - baseRefName - headRefName - author { - __typename - login - avatarUrl - ... on User { - url - } - ... on Bot { - url - } - ... on Node { - id - } + ... on Bot { + url + } + ... on Node { + id } - ...prTimelineController_pullRequest_3D8CP9 } + ...prTimelineController_pullRequest_3D8CP9 ... on UniformResourceLocatable { url } @@ -214,28 +189,6 @@ fragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest { } } -fragment issueTimelineController_issue_3D8CP9 on Issue { - url - timeline(first: $timelineCount, after: $timelineCursor) { - pageInfo { - endCursor - hasNextPage - } - edges { - cursor - node { - __typename - ...commitsView_nodes - ...issueCommentView_item - ...crossReferencedEventsView_nodes - ... on Node { - id - } - } - } - } -} - fragment prCommitsView_pullRequest_38TpXw on PullRequest { url commits(first: $commitCount, after: $commitCursor) { @@ -529,6 +482,28 @@ fragment prCommitView_item on Commit { abbreviatedOid url } + +fragment issueTimelineController_issue_3D8CP9 on Issue { + url + timeline(first: $timelineCount, after: $timelineCursor) { + pageInfo { + endCursor + hasNextPage + } + edges { + cursor + node { + __typename + ...commitsView_nodes + ...issueCommentView_item + ...crossReferencedEventsView_nodes + ... on Node { + id + } + } + } + } +} */ const node/*: ConcreteRequest*/ = (function(){ @@ -634,6 +609,36 @@ v7 = { "selections": v6 }, v8 = [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } +], +v9 = { + "kind": "ScalarField", + "alias": null, + "name": "title", + "args": null, + "storageKey": null +}, +v10 = { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null +}, +v11 = { + "kind": "ScalarField", + "alias": null, + "name": "isCrossRepository", + "args": null, + "storageKey": null +}, +v12 = [ { "kind": "Variable", "name": "after", @@ -647,7 +652,7 @@ v8 = [ "type": "Int" } ], -v9 = { +v13 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -672,35 +677,70 @@ v9 = { } ] }, -v10 = { +v14 = { "kind": "ScalarField", "alias": null, "name": "cursor", "args": null, "storageKey": null }, -v11 = { +v15 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", "args": null, "storageKey": null }, -v12 = { +v16 = { "kind": "ScalarField", "alias": null, - "name": "url", + "name": "number", "args": null, "storageKey": null }, -v13 = { +v17 = { "kind": "ScalarField", "alias": null, - "name": "title", + "name": "state", + "args": null, + "storageKey": null +}, +v18 = { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", "args": null, "storageKey": null }, -v14 = [ +v19 = [ + v10 +], +v20 = { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v4, + v5, + v15, + v2, + { + "kind": "InlineFragment", + "type": "Bot", + "selections": v19 + }, + { + "kind": "InlineFragment", + "type": "User", + "selections": v19 + } + ] +}, +v21 = [ { "kind": "Variable", "name": "after", @@ -714,27 +754,13 @@ v14 = [ "type": "Int" } ], -v15 = { - "kind": "ScalarField", - "alias": null, - "name": "isCrossRepository", - "args": null, - "storageKey": null -}, -v16 = [ +v22 = [ v4, v5, - v11, + v15, v2 ], -v17 = { - "kind": "ScalarField", - "alias": null, - "name": "number", - "args": null, - "storageKey": null -}, -v18 = { +v23 = { "kind": "InlineFragment", "type": "CrossReferencedEvent", "selections": [ @@ -745,7 +771,7 @@ v18 = { "args": null, "storageKey": null }, - v15, + v11, { "kind": "LinkedField", "alias": null, @@ -754,7 +780,7 @@ v18 = { "args": null, "concreteType": null, "plural": false, - "selections": v16 + "selections": v22 }, { "kind": "LinkedField", @@ -792,9 +818,9 @@ v18 = { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v17, - v13, - v12, + v16, + v9, + v10, { "kind": "ScalarField", "alias": "prState", @@ -808,9 +834,9 @@ v18 = { "kind": "InlineFragment", "type": "Issue", "selections": [ - v17, - v13, - v12, + v16, + v9, + v10, { "kind": "ScalarField", "alias": "issueState", @@ -824,18 +850,18 @@ v18 = { } ] }, -v19 = { +v24 = { "kind": "ScalarField", "alias": null, "name": "oid", "args": null, "storageKey": null }, -v20 = [ - v19, +v25 = [ + v24, v2 ], -v21 = { +v26 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -843,29 +869,22 @@ v21 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v20 -}, -v22 = { - "kind": "ScalarField", - "alias": null, - "name": "bodyHTML", - "args": null, - "storageKey": null + "selections": v25 }, -v23 = { +v27 = { "kind": "ScalarField", "alias": null, "name": "createdAt", "args": null, "storageKey": null }, -v24 = [ +v28 = [ v4, - v11, + v15, v5, v2 ], -v25 = { +v29 = { "kind": "LinkedField", "alias": null, "name": "actor", @@ -873,9 +892,9 @@ v25 = { "args": null, "concreteType": null, "plural": false, - "selections": v24 + "selections": v28 }, -v26 = { +v30 = { "kind": "InlineFragment", "type": "IssueComment", "selections": [ @@ -887,14 +906,14 @@ v26 = { "args": null, "concreteType": null, "plural": false, - "selections": v24 + "selections": v28 }, - v22, - v23, - v12 + v18, + v27, + v10 ] }, -v27 = { +v31 = { "kind": "LinkedField", "alias": null, "name": "user", @@ -907,7 +926,7 @@ v27 = { v2 ] }, -v28 = { +v32 = { "kind": "InlineFragment", "type": "Commit", "selections": [ @@ -921,8 +940,8 @@ v28 = { "plural": false, "selections": [ v3, - v27, - v11 + v31, + v15 ] }, { @@ -935,8 +954,8 @@ v28 = { "plural": false, "selections": [ v3, - v11, - v27 + v15, + v31 ] }, { @@ -946,7 +965,7 @@ v28 = { "args": null, "storageKey": null }, - v19, + v24, { "kind": "ScalarField", "alias": null, @@ -970,50 +989,6 @@ v28 = { } ] }, -v29 = [ - { - "kind": "ScalarField", - "alias": null, - "name": "totalCount", - "args": null, - "storageKey": null - } -], -v30 = { - "kind": "ScalarField", - "alias": null, - "name": "state", - "args": null, - "storageKey": null -}, -v31 = [ - v12 -], -v32 = { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v4, - v5, - v11, - v2, - { - "kind": "InlineFragment", - "type": "Bot", - "selections": v31 - }, - { - "kind": "InlineFragment", - "type": "User", - "selections": v31 - } - ] -}, v33 = { "kind": "LinkedField", "alias": null, @@ -1038,7 +1013,7 @@ v33 = { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v29 + "selections": v8 } ] }; @@ -1047,7 +1022,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n ...prDetailView_repository\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n ...prDetailView_repository\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1146,16 +1121,65 @@ return { "selections": [ { "kind": "LinkedField", - "alias": null, + "alias": "countedCommits", "name": "commits", "storageKey": null, - "args": v8, + "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, + "selections": v8 + }, + v9, + { + "kind": "ScalarField", + "alias": null, + "name": "headRefName", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "headRepository", + "storageKey": null, + "args": null, + "concreteType": "Repository", + "plural": false, "selections": [ - v9, + v3, + v7, + v10, { - "kind": "LinkedField", + "kind": "ScalarField", + "alias": null, + "name": "sshUrl", + "args": null, + "storageKey": null + }, + v2 + ] + }, + v11, + { + "kind": "ScalarField", + "alias": null, + "name": "changedFiles", + "args": null, + "storageKey": null + }, + v10, + { + "kind": "LinkedField", + "alias": null, + "name": "commits", + "storageKey": null, + "args": v12, + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": [ + v13, + { + "kind": "LinkedField", "alias": null, "name": "edges", "storageKey": null, @@ -1163,7 +1187,7 @@ return { "concreteType": "PullRequestCommitEdge", "plural": true, "selections": [ - v10, + v14, { "kind": "LinkedField", "alias": null, @@ -1192,7 +1216,7 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v11, + v15, v3, { "kind": "ScalarField", @@ -1224,7 +1248,7 @@ return { "args": null, "storageKey": null }, - v12 + v10 ] }, v2, @@ -1239,38 +1263,142 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v8, + "args": v12, "handle": "connection", "key": "prCommitsView_commits", "filters": null }, - v13, + v16, + { + "kind": "LinkedField", + "alias": "recentCommits", + "name": "commits", + "storageKey": "commits(last:1)", + "args": [ + { + "kind": "Literal", + "name": "last", + "value": 1, + "type": "Int" + } + ], + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommitEdge", + "plural": true, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommit", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "status", + "storageKey": null, + "args": null, + "concreteType": "Status", + "plural": false, + "selections": [ + v17, + { + "kind": "LinkedField", + "alias": null, + "name": "contexts", + "storageKey": null, + "args": null, + "concreteType": "StatusContext", + "plural": true, + "selections": [ + v2, + v17, + { + "kind": "ScalarField", + "alias": null, + "name": "context", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "description", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "targetUrl", + "args": null, + "storageKey": null + } + ] + }, + v2 + ] + }, + v2 + ] + }, + v2 + ] + } + ] + } + ] + }, + v17, + v18, { "kind": "ScalarField", "alias": null, - "name": "headRefName", + "name": "baseRefName", "args": null, "storageKey": null }, + v20, { "kind": "LinkedField", "alias": null, - "name": "headRepository", + "name": "headRepositoryOwner", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v6 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "repository", "storageKey": null, "args": null, "concreteType": "Repository", "plural": false, "selections": [ - v3, v7, - v12, - { - "kind": "ScalarField", - "alias": null, - "name": "sshUrl", - "args": null, - "storageKey": null - }, v2 ] }, @@ -1279,11 +1407,11 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v14, + "args": v21, "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v9, + v13, { "kind": "LinkedField", "alias": null, @@ -1293,7 +1421,7 @@ return { "concreteType": "PullRequestTimelineItemEdge", "plural": true, "selections": [ - v10, + v14, { "kind": "LinkedField", "alias": null, @@ -1305,12 +1433,12 @@ return { "selections": [ v4, v2, - v18, + v23, { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v21, + v26, { "kind": "LinkedField", "alias": null, @@ -1354,11 +1482,11 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v16 + "selections": v22 }, - v21, - v22, - v23, + v26, + v18, + v27, { "kind": "ScalarField", "alias": null, @@ -1385,7 +1513,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v25, + v29, { "kind": "LinkedField", "alias": null, @@ -1394,7 +1522,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v20 + "selections": v25 }, { "kind": "LinkedField", @@ -1404,17 +1532,17 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v20 + "selections": v25 }, - v23 + v27 ] }, { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v25, - v21, + v29, + v26, { "kind": "ScalarField", "alias": null, @@ -1422,11 +1550,11 @@ return { "args": null, "storageKey": null }, - v23 + v27 ] }, - v26, - v28 + v30, + v32 ] } ] @@ -1437,164 +1565,11 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v14, + "args": v21, "handle": "connection", "key": "prTimelineContainer_timeline", "filters": null }, - v15, - { - "kind": "ScalarField", - "alias": null, - "name": "changedFiles", - "args": null, - "storageKey": null - }, - v12, - v17, - { - "kind": "LinkedField", - "alias": "countedCommits", - "name": "commits", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommitConnection", - "plural": false, - "selections": v29 - }, - { - "kind": "LinkedField", - "alias": "recentCommits", - "name": "commits", - "storageKey": "commits(last:1)", - "args": [ - { - "kind": "Literal", - "name": "last", - "value": 1, - "type": "Int" - } - ], - "concreteType": "PullRequestCommitConnection", - "plural": false, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "edges", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommitEdge", - "plural": true, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommit", - "plural": false, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "status", - "storageKey": null, - "args": null, - "concreteType": "Status", - "plural": false, - "selections": [ - v30, - { - "kind": "LinkedField", - "alias": null, - "name": "contexts", - "storageKey": null, - "args": null, - "concreteType": "StatusContext", - "plural": true, - "selections": [ - v2, - v30, - { - "kind": "ScalarField", - "alias": null, - "name": "context", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "description", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "targetUrl", - "args": null, - "storageKey": null - } - ] - }, - v2 - ] - }, - v2 - ] - }, - v2 - ] - } - ] - } - ] - }, - v30, - v22, - { - "kind": "ScalarField", - "alias": null, - "name": "baseRefName", - "args": null, - "storageKey": null - }, - v32, - { - "kind": "LinkedField", - "alias": null, - "name": "headRepositoryOwner", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v6 - }, - { - "kind": "LinkedField", - "alias": null, - "name": "repository", - "storageKey": null, - "args": null, - "concreteType": "Repository", - "plural": false, - "selections": [ - v7, - v2 - ] - }, v33 ] }, @@ -1602,22 +1577,22 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v13, + v9, + v16, v17, - v30, - v22, - v32, - v12, + v18, + v20, + v10, { "kind": "LinkedField", "alias": null, "name": "timeline", "storageKey": null, - "args": v14, + "args": v21, "concreteType": "IssueTimelineConnection", "plural": false, "selections": [ - v9, + v13, { "kind": "LinkedField", "alias": null, @@ -1627,7 +1602,7 @@ return { "concreteType": "IssueTimelineItemEdge", "plural": true, "selections": [ - v10, + v14, { "kind": "LinkedField", "alias": null, @@ -1639,9 +1614,9 @@ return { "selections": [ v4, v2, - v18, - v26, - v28 + v23, + v30, + v32 ] } ] @@ -1652,7 +1627,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v14, + "args": v21, "handle": "connection", "key": "IssueTimelineController_timeline", "filters": null diff --git a/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js index ada45fdd44..6bb9113ca0 100644 --- a/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash e162404175c32a7cee36aebd46b673c9 + * @relayHash 02f94074fbd53cdf8b87c5e9896a53b7 */ /* eslint-disable */ @@ -61,32 +61,30 @@ fragment issueDetailView_repository_3D8CP9 on Repository { } } -fragment issueDetailView_issueish_3D8CP9 on IssueOrPullRequest { +fragment issueDetailView_issueish_3D8CP9 on Issue { __typename ... on Node { id } - ... on Issue { - state - number - title - bodyHTML - author { - __typename - login - avatarUrl - ... on User { - url - } - ... on Bot { - url - } - ... on Node { - id - } + state + number + title + bodyHTML + author { + __typename + login + avatarUrl + ... on User { + url + } + ... on Bot { + url + } + ... on Node { + id } - ...issueTimelineController_issue_3D8CP9 } + ...issueTimelineController_issue_3D8CP9 ... on UniformResourceLocatable { url } @@ -342,40 +340,40 @@ v8 = { v9 = { "kind": "ScalarField", "alias": null, - "name": "url", + "name": "number", "args": null, "storageKey": null }, v10 = { "kind": "ScalarField", "alias": null, - "name": "number", + "name": "title", "args": null, "storageKey": null }, v11 = { "kind": "ScalarField", "alias": null, - "name": "title", + "name": "bodyHTML", "args": null, "storageKey": null }, v12 = { "kind": "ScalarField", "alias": null, - "name": "bodyHTML", + "name": "avatarUrl", "args": null, "storageKey": null }, v13 = { "kind": "ScalarField", "alias": null, - "name": "avatarUrl", + "name": "url", "args": null, "storageKey": null }, v14 = [ - v9 + v13 ], v15 = [ { @@ -409,7 +407,7 @@ return { "operationKind": "query", "name": "issueDetailViewRefetchQuery", "id": null, - "text": "query issueDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...issueDetailView_issueish_3D8CP9\n id\n }\n}\n\nfragment issueDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_3D8CP9 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", + "text": "query issueDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...issueDetailView_issueish_3D8CP9\n id\n }\n}\n\nfragment issueDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_3D8CP9 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -489,47 +487,11 @@ return { "selections": [ v4, v5, - v9, - { - "kind": "LinkedField", - "alias": null, - "name": "reactionGroups", - "storageKey": null, - "args": null, - "concreteType": "ReactionGroup", - "plural": true, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "content", - "args": null, - "storageKey": null - }, - { - "kind": "LinkedField", - "alias": null, - "name": "users", - "storageKey": null, - "args": null, - "concreteType": "ReactingUserConnection", - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "totalCount", - "args": null, - "storageKey": null - } - ] - } - ] - }, { "kind": "InlineFragment", "type": "Issue", "selections": [ + v4, { "kind": "ScalarField", "alias": null, @@ -537,9 +499,9 @@ return { "args": null, "storageKey": null }, + v9, v10, v11, - v12, { "kind": "LinkedField", "alias": null, @@ -551,7 +513,7 @@ return { "selections": [ v4, v7, - v13, + v12, v5, { "kind": "InlineFragment", @@ -565,6 +527,7 @@ return { } ] }, + v13, { "kind": "LinkedField", "alias": null, @@ -655,7 +618,7 @@ return { "selections": [ v4, v7, - v13, + v12, v5 ] }, @@ -695,9 +658,9 @@ return { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v10, - v11, v9, + v10, + v13, { "kind": "ScalarField", "alias": "prState", @@ -711,9 +674,9 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v10, - v11, v9, + v10, + v13, { "kind": "ScalarField", "alias": "issueState", @@ -741,12 +704,12 @@ return { "plural": false, "selections": [ v4, - v13, + v12, v7, v5 ] }, - v12, + v11, { "kind": "ScalarField", "alias": null, @@ -754,7 +717,7 @@ return { "args": null, "storageKey": null }, - v9 + v13 ] }, { @@ -772,7 +735,7 @@ return { "selections": [ v6, v16, - v13 + v12 ] }, { @@ -785,7 +748,7 @@ return { "plural": false, "selections": [ v6, - v13, + v12, v16 ] }, @@ -840,6 +803,42 @@ return { "handle": "connection", "key": "IssueTimelineController_timeline", "filters": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "reactionGroups", + "storageKey": null, + "args": null, + "concreteType": "ReactionGroup", + "plural": true, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "content", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "users", + "storageKey": null, + "args": null, + "concreteType": "ReactingUserConnection", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } + ] + } + ] } ] } diff --git a/lib/views/__generated__/issueDetailView_issueish.graphql.js b/lib/views/__generated__/issueDetailView_issueish.graphql.js index 107a859a9e..0bd4f26326 100644 --- a/lib/views/__generated__/issueDetailView_issueish.graphql.js +++ b/lib/views/__generated__/issueDetailView_issueish.graphql.js @@ -14,8 +14,16 @@ export type ReactionContent = "CONFUSED" | "HEART" | "HOORAY" | "LAUGH" | "THUMB import type { FragmentReference } from "relay-runtime"; declare export opaque type issueDetailView_issueish$ref: FragmentReference; export type issueDetailView_issueish = {| - +__typename: string, +id?: string, + +state: IssueState, + +number: number, + +title: string, + +bodyHTML: any, + +author: ?{| + +login: string, + +avatarUrl: any, + +url?: any, + |}, +url?: any, +reactionGroups?: ?$ReadOnlyArray<{| +content: ReactionContent, @@ -23,15 +31,7 @@ export type issueDetailView_issueish = {| +totalCount: number |}, |}>, - +state?: IssueState, - +number?: number, - +title?: string, - +bodyHTML?: any, - +author?: ?{| - +login: string, - +avatarUrl: any, - +url?: any, - |}, + +__typename: "Issue", +$fragmentRefs: issueTimelineController_issue$ref, +$refType: issueDetailView_issueish$ref, |}; @@ -52,7 +52,7 @@ v1 = [ return { "kind": "Fragment", "name": "issueDetailView_issueish", - "type": "IssueOrPullRequest", + "type": "Issue", "metadata": null, "argumentDefinitions": [ { @@ -83,125 +83,119 @@ return { "args": null, "storageKey": null }, - v0, + { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "number", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "title", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null + }, { "kind": "LinkedField", "alias": null, - "name": "reactionGroups", + "name": "author", "storageKey": null, "args": null, - "concreteType": "ReactionGroup", - "plural": true, + "concreteType": null, + "plural": false, "selections": [ { "kind": "ScalarField", "alias": null, - "name": "content", + "name": "login", "args": null, "storageKey": null }, - { - "kind": "LinkedField", - "alias": null, - "name": "users", - "storageKey": null, - "args": null, - "concreteType": "ReactingUserConnection", - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "totalCount", - "args": null, - "storageKey": null - } - ] - } - ] - }, - { - "kind": "InlineFragment", - "type": "Issue", - "selections": [ { "kind": "ScalarField", "alias": null, - "name": "state", + "name": "avatarUrl", "args": null, "storageKey": null }, { - "kind": "ScalarField", - "alias": null, - "name": "number", - "args": null, - "storageKey": null + "kind": "InlineFragment", + "type": "Bot", + "selections": v1 }, { - "kind": "ScalarField", - "alias": null, - "name": "title", - "args": null, - "storageKey": null + "kind": "InlineFragment", + "type": "User", + "selections": v1 + } + ] + }, + { + "kind": "FragmentSpread", + "name": "issueTimelineController_issue", + "args": [ + { + "kind": "Variable", + "name": "timelineCount", + "variableName": "timelineCount", + "type": null }, + { + "kind": "Variable", + "name": "timelineCursor", + "variableName": "timelineCursor", + "type": null + } + ] + }, + v0, + { + "kind": "LinkedField", + "alias": null, + "name": "reactionGroups", + "storageKey": null, + "args": null, + "concreteType": "ReactionGroup", + "plural": true, + "selections": [ { "kind": "ScalarField", "alias": null, - "name": "bodyHTML", + "name": "content", "args": null, "storageKey": null }, { "kind": "LinkedField", "alias": null, - "name": "author", + "name": "users", "storageKey": null, "args": null, - "concreteType": null, + "concreteType": "ReactingUserConnection", "plural": false, "selections": [ { "kind": "ScalarField", "alias": null, - "name": "login", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "avatarUrl", + "name": "totalCount", "args": null, "storageKey": null - }, - { - "kind": "InlineFragment", - "type": "Bot", - "selections": v1 - }, - { - "kind": "InlineFragment", - "type": "User", - "selections": v1 - } - ] - }, - { - "kind": "FragmentSpread", - "name": "issueTimelineController_issue", - "args": [ - { - "kind": "Variable", - "name": "timelineCount", - "variableName": "timelineCount", - "type": null - }, - { - "kind": "Variable", - "name": "timelineCursor", - "variableName": "timelineCursor", - "type": null } ] } @@ -211,5 +205,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'e706bcd2c9ec8c5c57f26c39e38d8159'; +(node/*: any*/).hash = '4fbcc89822253448caa73ca5f5871c06'; module.exports = node; diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index 0e4ea05be1..64d60f0047 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 2e4f33f58832e71ea98f6fb4f9477af8 + * @relayHash fd414a0501fa54acfda074aee07b1aa4 */ /* eslint-disable */ @@ -65,62 +65,39 @@ fragment prDetailView_repository_3D8CP9 on Repository { } } -fragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest { +fragment prDetailView_issueish_4cAEh0 on PullRequest { __typename ... on Node { id } - ... on Issue { - state - number - title - bodyHTML - author { - __typename - login - avatarUrl - ... on User { - url - } - ... on Bot { - url - } - ... on Node { - id - } - } - ...issueTimelineController_issue_3D8CP9 + isCrossRepository + changedFiles + ...prCommitsView_pullRequest_38TpXw + countedCommits: commits { + totalCount } - ... on PullRequest { - isCrossRepository - changedFiles - ...prCommitsView_pullRequest_38TpXw - countedCommits: commits { - totalCount + ...prStatusesView_pullRequest + state + number + title + bodyHTML + baseRefName + headRefName + author { + __typename + login + avatarUrl + ... on User { + url } - ...prStatusesView_pullRequest - state - number - title - bodyHTML - baseRefName - headRefName - author { - __typename - login - avatarUrl - ... on User { - url - } - ... on Bot { - url - } - ... on Node { - id - } + ... on Bot { + url + } + ... on Node { + id } - ...prTimelineController_pullRequest_3D8CP9 } + ...prTimelineController_pullRequest_3D8CP9 ... on UniformResourceLocatable { url } @@ -134,28 +111,6 @@ fragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest { } } -fragment issueTimelineController_issue_3D8CP9 on Issue { - url - timeline(first: $timelineCount, after: $timelineCursor) { - pageInfo { - endCursor - hasNextPage - } - edges { - cursor - node { - __typename - ...commitsView_nodes - ...issueCommentView_item - ...crossReferencedEventsView_nodes - ... on Node { - id - } - } - } - } -} - fragment prCommitsView_pullRequest_38TpXw on PullRequest { url commits(first: $commitCount, after: $commitCursor) { @@ -564,34 +519,25 @@ v10 = { v11 = { "kind": "ScalarField", "alias": null, - "name": "url", + "name": "number", "args": null, "storageKey": null }, -v12 = [ - { - "kind": "ScalarField", - "alias": null, - "name": "totalCount", - "args": null, - "storageKey": null - } -], -v13 = { +v12 = { "kind": "ScalarField", "alias": null, - "name": "number", + "name": "isCrossRepository", "args": null, "storageKey": null }, -v14 = { +v13 = { "kind": "ScalarField", "alias": null, - "name": "isCrossRepository", + "name": "url", "args": null, "storageKey": null }, -v15 = [ +v14 = [ { "kind": "Variable", "name": "after", @@ -605,7 +551,7 @@ v15 = [ "type": "Int" } ], -v16 = { +v15 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -630,20 +576,29 @@ v16 = { } ] }, -v17 = { +v16 = { "kind": "ScalarField", "alias": null, "name": "cursor", "args": null, "storageKey": null }, -v18 = { +v17 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", "args": null, "storageKey": null }, +v18 = [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } +], v19 = { "kind": "ScalarField", "alias": null, @@ -666,34 +621,9 @@ v21 = { "storageKey": null }, v22 = [ - v11 + v13 ], -v23 = { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v8, - v18, - v6, - { - "kind": "InlineFragment", - "type": "Bot", - "selections": v22 - }, - { - "kind": "InlineFragment", - "type": "User", - "selections": v22 - } - ] -}, -v24 = [ +v23 = [ { "kind": "Variable", "name": "after", @@ -707,114 +637,24 @@ v24 = [ "type": "Int" } ], -v25 = [ +v24 = [ v5, v8, - v18, + v17, v6 ], -v26 = { - "kind": "InlineFragment", - "type": "CrossReferencedEvent", - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "referencedAt", - "args": null, - "storageKey": null - }, - v14, - { - "kind": "LinkedField", - "alias": null, - "name": "actor", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v25 - }, - { - "kind": "LinkedField", - "alias": null, - "name": "source", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - { - "kind": "LinkedField", - "alias": null, - "name": "repository", - "storageKey": null, - "args": null, - "concreteType": "Repository", - "plural": false, - "selections": [ - v7, - v10, - v6, - { - "kind": "ScalarField", - "alias": null, - "name": "isPrivate", - "args": null, - "storageKey": null - } - ] - }, - v6, - { - "kind": "InlineFragment", - "type": "PullRequest", - "selections": [ - v13, - v20, - v11, - { - "kind": "ScalarField", - "alias": "prState", - "name": "state", - "args": null, - "storageKey": null - } - ] - }, - { - "kind": "InlineFragment", - "type": "Issue", - "selections": [ - v13, - v20, - v11, - { - "kind": "ScalarField", - "alias": "issueState", - "name": "state", - "args": null, - "storageKey": null - } - ] - } - ] - } - ] -}, -v27 = { +v25 = { "kind": "ScalarField", "alias": null, "name": "oid", "args": null, "storageKey": null }, -v28 = [ - v27, +v26 = [ + v25, v6 ], -v29 = { +v27 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -822,22 +662,22 @@ v29 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v28 + "selections": v26 }, -v30 = { +v28 = { "kind": "ScalarField", "alias": null, "name": "createdAt", "args": null, "storageKey": null }, -v31 = [ +v29 = [ v5, - v18, + v17, v8, v6 ], -v32 = { +v30 = { "kind": "LinkedField", "alias": null, "name": "actor", @@ -845,28 +685,9 @@ v32 = { "args": null, "concreteType": null, "plural": false, - "selections": v31 -}, -v33 = { - "kind": "InlineFragment", - "type": "IssueComment", - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v31 - }, - v21, - v30, - v11 - ] + "selections": v29 }, -v34 = { +v31 = { "kind": "LinkedField", "alias": null, "name": "user", @@ -878,76 +699,13 @@ v34 = { v8, v6 ] -}, -v35 = { - "kind": "InlineFragment", - "type": "Commit", - "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": "GitActor", - "plural": false, - "selections": [ - v7, - v34, - v18 - ] - }, - { - "kind": "LinkedField", - "alias": null, - "name": "committer", - "storageKey": null, - "args": null, - "concreteType": "GitActor", - "plural": false, - "selections": [ - v7, - v18, - v34 - ] - }, - { - "kind": "ScalarField", - "alias": null, - "name": "authoredByCommitter", - "args": null, - "storageKey": null - }, - v27, - { - "kind": "ScalarField", - "alias": null, - "name": "message", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "messageHeadlineHTML", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "commitUrl", - "args": null, - "storageKey": null - } - ] }; return { "kind": "Request", "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...prDetailView_issueish_4cAEh0\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...prDetailView_issueish_4cAEh0\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1045,51 +803,31 @@ return { "selections": [ v5, v6, - v11, { - "kind": "LinkedField", - "alias": null, - "name": "reactionGroups", - "storageKey": null, - "args": null, - "concreteType": "ReactionGroup", - "plural": true, + "kind": "InlineFragment", + "type": "PullRequest", "selections": [ + v11, + v5, + v12, { "kind": "ScalarField", "alias": null, - "name": "content", + "name": "changedFiles", "args": null, "storageKey": null }, - { - "kind": "LinkedField", - "alias": null, - "name": "users", - "storageKey": null, - "args": null, - "concreteType": "ReactingUserConnection", - "plural": false, - "selections": v12 - } - ] - }, - { - "kind": "InlineFragment", - "type": "PullRequest", - "selections": [ v13, - v14, { "kind": "LinkedField", "alias": null, "name": "commits", "storageKey": null, - "args": v15, + "args": v14, "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v16, + v15, { "kind": "LinkedField", "alias": null, @@ -1099,7 +837,7 @@ return { "concreteType": "PullRequestCommitEdge", "plural": true, "selections": [ - v17, + v16, { "kind": "LinkedField", "alias": null, @@ -1128,7 +866,7 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v18, + v17, v7, { "kind": "ScalarField", @@ -1160,7 +898,7 @@ return { "args": null, "storageKey": null }, - v11 + v13 ] }, v6, @@ -1175,7 +913,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v15, + "args": v14, "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -1188,7 +926,7 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v12 + "selections": v18 }, { "kind": "LinkedField", @@ -1291,13 +1029,6 @@ return { ] }, v19, - { - "kind": "ScalarField", - "alias": null, - "name": "changedFiles", - "args": null, - "storageKey": null - }, v20, v21, { @@ -1314,7 +1045,31 @@ return { "args": null, "storageKey": null }, - v23, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v8, + v17, + v6, + { + "kind": "InlineFragment", + "type": "Bot", + "selections": v22 + }, + { + "kind": "InlineFragment", + "type": "User", + "selections": v22 + } + ] + }, { "kind": "LinkedField", "alias": null, @@ -1343,11 +1098,11 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v24, + "args": v23, "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v16, + v15, { "kind": "LinkedField", "alias": null, @@ -1357,7 +1112,7 @@ return { "concreteType": "PullRequestTimelineItemEdge", "plural": true, "selections": [ - v17, + v16, { "kind": "LinkedField", "alias": null, @@ -1369,12 +1124,101 @@ return { "selections": [ v5, v6, - v26, + { + "kind": "InlineFragment", + "type": "CrossReferencedEvent", + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "referencedAt", + "args": null, + "storageKey": null + }, + v12, + { + "kind": "LinkedField", + "alias": null, + "name": "actor", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v24 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "source", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + { + "kind": "LinkedField", + "alias": null, + "name": "repository", + "storageKey": null, + "args": null, + "concreteType": "Repository", + "plural": false, + "selections": [ + v7, + v10, + v6, + { + "kind": "ScalarField", + "alias": null, + "name": "isPrivate", + "args": null, + "storageKey": null + } + ] + }, + v6, + { + "kind": "InlineFragment", + "type": "PullRequest", + "selections": [ + v11, + v20, + v13, + { + "kind": "ScalarField", + "alias": "prState", + "name": "state", + "args": null, + "storageKey": null + } + ] + }, + { + "kind": "InlineFragment", + "type": "Issue", + "selections": [ + v11, + v20, + v13, + { + "kind": "ScalarField", + "alias": "issueState", + "name": "state", + "args": null, + "storageKey": null + } + ] + } + ] + } + ] + }, { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v29, + v27, { "kind": "LinkedField", "alias": null, @@ -1418,11 +1262,11 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v25 + "selections": v24 }, - v29, + v27, v21, - v30, + v28, { "kind": "ScalarField", "alias": null, @@ -1449,7 +1293,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v32, + v30, { "kind": "LinkedField", "alias": null, @@ -1458,7 +1302,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v28 + "selections": v26 }, { "kind": "LinkedField", @@ -1468,17 +1312,17 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v28 + "selections": v26 }, - v30 + v28 ] }, { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v32, - v29, + v30, + v27, { "kind": "ScalarField", "alias": null, @@ -1486,11 +1330,91 @@ return { "args": null, "storageKey": null }, - v30 + v28 + ] + }, + { + "kind": "InlineFragment", + "type": "IssueComment", + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v29 + }, + v21, + v28, + v13 ] }, - v33, - v35 + { + "kind": "InlineFragment", + "type": "Commit", + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": "GitActor", + "plural": false, + "selections": [ + v7, + v31, + v17 + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "committer", + "storageKey": null, + "args": null, + "concreteType": "GitActor", + "plural": false, + "selections": [ + v7, + v17, + v31 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "authoredByCommitter", + "args": null, + "storageKey": null + }, + v25, + { + "kind": "ScalarField", + "alias": null, + "name": "message", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "messageHeadlineHTML", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "commitUrl", + "args": null, + "storageKey": null + } + ] + } ] } ] @@ -1501,70 +1425,38 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v24, + "args": v23, "handle": "connection", "key": "prTimelineContainer_timeline", "filters": null - } - ] - }, - { - "kind": "InlineFragment", - "type": "Issue", - "selections": [ - v19, - v13, - v20, - v21, - v23, + }, { "kind": "LinkedField", "alias": null, - "name": "timeline", + "name": "reactionGroups", "storageKey": null, - "args": v24, - "concreteType": "IssueTimelineConnection", - "plural": false, + "args": null, + "concreteType": "ReactionGroup", + "plural": true, "selections": [ - v16, + { + "kind": "ScalarField", + "alias": null, + "name": "content", + "args": null, + "storageKey": null + }, { "kind": "LinkedField", "alias": null, - "name": "edges", + "name": "users", "storageKey": null, "args": null, - "concreteType": "IssueTimelineItemEdge", - "plural": true, - "selections": [ - v17, - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v6, - v26, - v33, - v35 - ] - } - ] + "concreteType": "ReactingUserConnection", + "plural": false, + "selections": v18 } ] - }, - { - "kind": "LinkedHandle", - "alias": null, - "name": "timeline", - "args": v24, - "handle": "connection", - "key": "IssueTimelineController_timeline", - "filters": null } ] } diff --git a/lib/views/__generated__/prDetailView_issueish.graphql.js b/lib/views/__generated__/prDetailView_issueish.graphql.js index e31ad83183..fe77fc3798 100644 --- a/lib/views/__generated__/prDetailView_issueish.graphql.js +++ b/lib/views/__generated__/prDetailView_issueish.graphql.js @@ -8,18 +8,31 @@ /*:: import type { ConcreteFragment } from 'relay-runtime'; -type issueTimelineController_issue$ref = any; type prCommitsView_pullRequest$ref = any; type prStatusesView_pullRequest$ref = any; type prTimelineController_pullRequest$ref = any; -export type IssueState = "CLOSED" | "OPEN" | "%future added value"; export type PullRequestState = "CLOSED" | "MERGED" | "OPEN" | "%future added value"; export type ReactionContent = "CONFUSED" | "HEART" | "HOORAY" | "LAUGH" | "THUMBS_DOWN" | "THUMBS_UP" | "%future added value"; import type { FragmentReference } from "relay-runtime"; declare export opaque type prDetailView_issueish$ref: FragmentReference; export type prDetailView_issueish = {| - +__typename: string, +id?: string, + +isCrossRepository: boolean, + +changedFiles: number, + +countedCommits: {| + +totalCount: number + |}, + +state: PullRequestState, + +number: number, + +title: string, + +bodyHTML: any, + +baseRefName: string, + +headRefName: string, + +author: ?{| + +login: string, + +avatarUrl: any, + +url?: any, + |}, +url?: any, +reactionGroups?: ?$ReadOnlyArray<{| +content: ReactionContent, @@ -27,37 +40,15 @@ export type prDetailView_issueish = {| +totalCount: number |}, |}>, - +state?: IssueState, - +number?: number, - +title?: string, - +bodyHTML?: any, - +author?: ?{| - +login: string, - +avatarUrl: any, - +url?: any, - |}, - +isCrossRepository?: boolean, - +changedFiles?: number, - +countedCommits?: {| - +totalCount: number - |}, - +baseRefName?: string, - +headRefName?: string, - +$fragmentRefs: issueTimelineController_issue$ref & prCommitsView_pullRequest$ref & prStatusesView_pullRequest$ref & prTimelineController_pullRequest$ref, + +__typename: "PullRequest", + +$fragmentRefs: prCommitsView_pullRequest$ref & prStatusesView_pullRequest$ref & prTimelineController_pullRequest$ref, +$refType: prDetailView_issueish$ref, |}; */ const node/*: ConcreteFragment*/ = (function(){ -var v0 = { - "kind": "ScalarField", - "alias": null, - "name": "url", - "args": null, - "storageKey": null -}, -v1 = [ +var v0 = [ { "kind": "ScalarField", "alias": null, @@ -66,90 +57,20 @@ v1 = [ "storageKey": null } ], -v2 = { - "kind": "ScalarField", - "alias": null, - "name": "number", - "args": null, - "storageKey": null -}, -v3 = { +v1 = { "kind": "ScalarField", "alias": null, - "name": "state", - "args": null, - "storageKey": null -}, -v4 = { - "kind": "ScalarField", - "alias": null, - "name": "title", - "args": null, - "storageKey": null -}, -v5 = { - "kind": "ScalarField", - "alias": null, - "name": "bodyHTML", + "name": "url", "args": null, "storageKey": null }, -v6 = [ - v0 -], -v7 = { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "login", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "avatarUrl", - "args": null, - "storageKey": null - }, - { - "kind": "InlineFragment", - "type": "Bot", - "selections": v6 - }, - { - "kind": "InlineFragment", - "type": "User", - "selections": v6 - } - ] -}, -v8 = [ - { - "kind": "Variable", - "name": "timelineCount", - "variableName": "timelineCount", - "type": null - }, - { - "kind": "Variable", - "name": "timelineCursor", - "variableName": "timelineCursor", - "type": null - } +v2 = [ + v1 ]; return { "kind": "Fragment", "name": "prDetailView_issueish", - "type": "IssueOrPullRequest", + "type": "PullRequest", "metadata": null, "argumentDefinitions": [ { @@ -178,6 +99,13 @@ return { } ], "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "number", + "args": null, + "storageKey": null + }, { "kind": "ScalarField", "alias": null, @@ -188,129 +116,171 @@ return { { "kind": "ScalarField", "alias": null, - "name": "id", + "name": "isCrossRepository", "args": null, "storageKey": null }, - v0, { - "kind": "LinkedField", + "kind": "ScalarField", "alias": null, - "name": "reactionGroups", - "storageKey": null, + "name": "changedFiles", "args": null, - "concreteType": "ReactionGroup", - "plural": true, - "selections": [ + "storageKey": null + }, + { + "kind": "FragmentSpread", + "name": "prCommitsView_pullRequest", + "args": [ { - "kind": "ScalarField", - "alias": null, - "name": "content", - "args": null, - "storageKey": null + "kind": "Variable", + "name": "commitCount", + "variableName": "commitCount", + "type": null }, { - "kind": "LinkedField", - "alias": null, - "name": "users", - "storageKey": null, - "args": null, - "concreteType": "ReactingUserConnection", - "plural": false, - "selections": v1 + "kind": "Variable", + "name": "commitCursor", + "variableName": "commitCursor", + "type": null } ] }, { - "kind": "InlineFragment", - "type": "PullRequest", + "kind": "LinkedField", + "alias": "countedCommits", + "name": "commits", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": v0 + }, + { + "kind": "FragmentSpread", + "name": "prStatusesView_pullRequest", + "args": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "title", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "baseRefName", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "headRefName", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, "selections": [ - v2, { "kind": "ScalarField", "alias": null, - "name": "isCrossRepository", + "name": "login", "args": null, "storageKey": null }, - { - "kind": "FragmentSpread", - "name": "prCommitsView_pullRequest", - "args": [ - { - "kind": "Variable", - "name": "commitCount", - "variableName": "commitCount", - "type": null - }, - { - "kind": "Variable", - "name": "commitCursor", - "variableName": "commitCursor", - "type": null - } - ] - }, - { - "kind": "LinkedField", - "alias": "countedCommits", - "name": "commits", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommitConnection", - "plural": false, - "selections": v1 - }, - { - "kind": "FragmentSpread", - "name": "prStatusesView_pullRequest", - "args": null - }, - v3, { "kind": "ScalarField", "alias": null, - "name": "changedFiles", + "name": "avatarUrl", "args": null, "storageKey": null }, - v4, - v5, { - "kind": "ScalarField", - "alias": null, - "name": "baseRefName", - "args": null, - "storageKey": null + "kind": "InlineFragment", + "type": "Bot", + "selections": v2 }, { - "kind": "ScalarField", - "alias": null, - "name": "headRefName", - "args": null, - "storageKey": null + "kind": "InlineFragment", + "type": "User", + "selections": v2 + } + ] + }, + { + "kind": "FragmentSpread", + "name": "prTimelineController_pullRequest", + "args": [ + { + "kind": "Variable", + "name": "timelineCount", + "variableName": "timelineCount", + "type": null }, - v7, { - "kind": "FragmentSpread", - "name": "prTimelineController_pullRequest", - "args": v8 + "kind": "Variable", + "name": "timelineCursor", + "variableName": "timelineCursor", + "type": null } ] }, + v1, { - "kind": "InlineFragment", - "type": "Issue", + "kind": "LinkedField", + "alias": null, + "name": "reactionGroups", + "storageKey": null, + "args": null, + "concreteType": "ReactionGroup", + "plural": true, "selections": [ - v3, - v2, - v4, - v5, - v7, { - "kind": "FragmentSpread", - "name": "issueTimelineController_issue", - "args": v8 + "kind": "ScalarField", + "alias": null, + "name": "content", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "users", + "storageKey": null, + "args": null, + "concreteType": "ReactingUserConnection", + "plural": false, + "selections": v0 } ] } @@ -318,5 +288,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '21b9e182a721c5b5ae1740c3e8b58f91'; +(node/*: any*/).hash = '3f707e83b01af99f75dfc844ef0f32d6'; module.exports = node; diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 348e4b7285..6b40dbc2cf 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -187,7 +187,7 @@ export default createRefetchContainer(BareIssueDetailView, { `, issueish: graphql` - fragment issueDetailView_issueish on IssueOrPullRequest + fragment issueDetailView_issueish on Issue @argumentDefinitions( timelineCount: {type: "Int!"}, timelineCursor: {type: "String"}, diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 3d6d742410..e524be50cc 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -308,7 +308,7 @@ export default createRefetchContainer(BarePullRequestDetailView, { `, issueish: graphql` - fragment prDetailView_issueish on IssueOrPullRequest + fragment prDetailView_issueish on PullRequest @argumentDefinitions( timelineCount: {type: "Int!"}, timelineCursor: {type: "String"}, @@ -321,17 +321,6 @@ export default createRefetchContainer(BarePullRequestDetailView, { id } - ... on Issue { - state number title bodyHTML - author { - login avatarUrl - ... on User { url } - ... on Bot { url } - } - - ...issueTimelineController_issue @arguments(timelineCount: $timelineCount, timelineCursor: $timelineCursor) - } - ... on PullRequest { isCrossRepository changedFiles From 8fbb4fc0651f9f87446f9a3b30312d30ea01451b Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 22 Nov 2018 11:03:46 +0900 Subject: [PATCH 1181/4053] Style CommitDetail header --- lib/controllers/commit-detail-controller.js | 44 ++++++----- styles/commit-detail.less | 87 +++++++++++++++++++++ 2 files changed, 111 insertions(+), 20 deletions(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index f9926d9ef4..7d22993334 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -27,28 +27,32 @@ export default class CommitDetailController extends React.Component { // const {avatarUrl, name, date} = this.props.item.committer; return ( -
-
-

- {emojify(commit.getMessageSubject())} -
-              {emojify(commit.getMessageBody())}
-

-
- {/* TODO fix image src */} - {this.renderAuthors()} - - {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} - +
+
+
+
+

+ {emojify(commit.getMessageSubject())} +

+
+                {emojify(commit.getMessageBody())}
+
+ {/* TODO fix image src */} + {this.renderAuthors()} + + {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} + +
+
+
+ {/* TODO fix href */} + + {commit.getSha()} + +
-
- {/* TODO fix href */} - - {commit.getSha()} - -
Date: Thu, 22 Nov 2018 22:01:51 +0100 Subject: [PATCH 1182/4053] Click on a recent commit to reveal `CommitDetailItem` --- lib/controllers/recent-commits-controller.js | 19 +++++++++++++++++++ lib/views/git-tab-view.js | 2 ++ lib/views/open-commit-dialog.js | 1 - lib/views/recent-commits-view.js | 7 ++++++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index 60ba927619..7e3a824309 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -1,6 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; +import {autobind} from '../helpers'; +import {addEvent} from '../reporter-proxy'; +import CommitDetailItem from '../items/commit-detail-item'; import RecentCommitsView from '../views/recent-commits-view'; export default class RecentCommitsController extends React.Component { @@ -8,6 +11,13 @@ export default class RecentCommitsController extends React.Component { commits: PropTypes.arrayOf(PropTypes.object).isRequired, isLoading: PropTypes.bool.isRequired, undoLastCommit: PropTypes.func.isRequired, + workspace: PropTypes.object.isRequired, + repository: PropTypes.object.isRequired, + } + + constructor(props, context) { + super(props, context); + autobind(this, 'openCommit'); } render() { @@ -16,7 +26,16 @@ export default class RecentCommitsController extends React.Component { commits={this.props.commits} isLoading={this.props.isLoading} undoLastCommit={this.props.undoLastCommit} + openCommit={this.openCommit} /> ); } + + openCommit({sha}) { + const workdir = this.props.repository.getWorkingDirectoryPath(); + const uri = CommitDetailItem.buildURI(workdir, sha); + this.props.workspace.open(uri).then(() => { + addEvent('open-commit-in-pane', {package: 'github', from: 'dialog'}); + }); + } } diff --git a/lib/views/git-tab-view.js b/lib/views/git-tab-view.js index 9899daca09..fb7339869d 100644 --- a/lib/views/git-tab-view.js +++ b/lib/views/git-tab-view.js @@ -196,6 +196,8 @@ export default class GitTabView extends React.Component { commits={this.props.recentCommits} isLoading={this.props.isLoading} undoLastCommit={this.props.undoLastCommit} + workspace={this.props.workspace} + repository={this.props.repository} />
); diff --git a/lib/views/open-commit-dialog.js b/lib/views/open-commit-dialog.js index 6cd2f04d09..47ab2d3ec2 100644 --- a/lib/views/open-commit-dialog.js +++ b/lib/views/open-commit-dialog.js @@ -22,7 +22,6 @@ export default class OpenCommitDialog extends React.Component { constructor(props, context) { super(props, context); autobind(this, 'accept', 'cancel', 'editorRefs', 'didChangeCommitSha'); - console.log('sdfasdf'); this.state = { cloneDisabled: false, diff --git a/lib/views/recent-commits-view.js b/lib/views/recent-commits-view.js index 855fad6515..5ca08ccffe 100644 --- a/lib/views/recent-commits-view.js +++ b/lib/views/recent-commits-view.js @@ -11,6 +11,7 @@ class RecentCommitView extends React.Component { commit: PropTypes.object.isRequired, undoLastCommit: PropTypes.func.isRequired, isMostRecent: PropTypes.bool.isRequired, + openCommit: PropTypes.func.isRequired, }; render() { @@ -18,7 +19,9 @@ class RecentCommitView extends React.Component { const fullMessage = this.props.commit.getFullMessage(); return ( -
  • +
  • {this.renderAuthors()} this.props.openCommit({sha: commit.getSha()})} /> ); })} From 6914f2e21550e2a6ec0efb3c8041584b8145ab00 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 22 Nov 2018 22:36:45 +0100 Subject: [PATCH 1183/4053] telemetry event --- lib/controllers/recent-commits-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index 7e3a824309..e29e9a2dac 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -35,7 +35,7 @@ export default class RecentCommitsController extends React.Component { const workdir = this.props.repository.getWorkingDirectoryPath(); const uri = CommitDetailItem.buildURI(workdir, sha); this.props.workspace.open(uri).then(() => { - addEvent('open-commit-in-pane', {package: 'github', from: 'dialog'}); + addEvent('open-commit-in-pane', {package: 'github', from: 'recent commit'}); }); } } From 5bb615828e3e7b3b81a612d1f4caa4c46f8bbca5 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 22 Nov 2018 22:51:32 +0100 Subject: [PATCH 1184/4053] fix date --- lib/controllers/commit-detail-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index 7d22993334..4d9ecebcc4 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -63,7 +63,7 @@ export default class CommitDetailController extends React.Component { } humanizeTimeSince(date) { - return moment(date).fromNow(); + return moment(date * 1000).fromNow(); } getAuthorInfo() { From 4735badba64a201a6ee46692b5eef97b6293351c Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 17:04:51 +0100 Subject: [PATCH 1185/4053] selected state for each recent commit --- lib/controllers/recent-commits-controller.js | 29 +++++++++++++++++++- lib/views/recent-commits-view.js | 8 +++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index e29e9a2dac..e88ec50959 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -4,7 +4,9 @@ import {autobind} from '../helpers'; import {addEvent} from '../reporter-proxy'; import CommitDetailItem from '../items/commit-detail-item'; +import URIPattern from '../atom/uri-pattern'; import RecentCommitsView from '../views/recent-commits-view'; +import {CompositeDisposable} from 'event-kit'; export default class RecentCommitsController extends React.Component { static propTypes = { @@ -17,7 +19,31 @@ export default class RecentCommitsController extends React.Component { constructor(props, context) { super(props, context); - autobind(this, 'openCommit'); + autobind(this, 'openCommit', 'updateSelectedCommit'); + + this.subscriptions = new CompositeDisposable( + this.props.workspace.onDidChangeActivePaneItem(this.updateSelectedCommit), + ); + this.state = {selectedCommitSha: ''}; + } + + updateSelectedCommit() { + const activeItem = this.props.workspace.getActivePaneItem(); + + const pattern = new URIPattern(decodeURIComponent( + CommitDetailItem.buildURI( + this.props.repository.getWorkingDirectoryPath(), + '{sha}'), + )); + + if (activeItem && activeItem.getURI) { + const match = pattern.matches(activeItem.getURI()); + if (match.ok()) { + const {sha} = match.getParams(); + return new Promise(resolve => this.setState({selectedCommitSha: sha}, resolve)); + } + } + return Promise.resolve(); } render() { @@ -27,6 +53,7 @@ export default class RecentCommitsController extends React.Component { isLoading={this.props.isLoading} undoLastCommit={this.props.undoLastCommit} openCommit={this.openCommit} + selectedCommitSha={this.state.selectedCommitSha} /> ); } diff --git a/lib/views/recent-commits-view.js b/lib/views/recent-commits-view.js index 5ca08ccffe..0f01a6f3f8 100644 --- a/lib/views/recent-commits-view.js +++ b/lib/views/recent-commits-view.js @@ -12,6 +12,7 @@ class RecentCommitView extends React.Component { undoLastCommit: PropTypes.func.isRequired, isMostRecent: PropTypes.bool.isRequired, openCommit: PropTypes.func.isRequired, + isSelected: PropTypes.bool.isRequired, }; render() { @@ -20,7 +21,10 @@ class RecentCommitView extends React.Component { return (
  • {this.renderAuthors()} this.props.openCommit({sha: commit.getSha()})} + isSelected={this.props.selectedCommitSha === commit.getSha()} /> ); })} From 140dc2577a9879282b4a39f358d21364d6467ec7 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 17:05:04 +0100 Subject: [PATCH 1186/4053] visually show selected state --- styles/recent-commits.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/styles/recent-commits.less b/styles/recent-commits.less index 06a3bda3b5..31e0d2034c 100644 --- a/styles/recent-commits.less +++ b/styles/recent-commits.less @@ -87,6 +87,12 @@ color: @text-color-subtle; } + &.is-selected { + // is selected + color: @text-color-selected; + background: @background-color-selected; + } + } From 8a76a617647aef7a6fbd1d0c1f01cefe23b9beb5 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 18:11:21 +0100 Subject: [PATCH 1187/4053] match sha better --- lib/controllers/recent-commits-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index e88ec50959..2e72da64f9 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -38,8 +38,8 @@ export default class RecentCommitsController extends React.Component { if (activeItem && activeItem.getURI) { const match = pattern.matches(activeItem.getURI()); - if (match.ok()) { - const {sha} = match.getParams(); + const {sha} = match.getParams(); + if (match.ok() && sha && sha !== this.state.selectedCommitSha) { return new Promise(resolve => this.setState({selectedCommitSha: sha}, resolve)); } } From 41eb1c39fa895733e5d38be22fa2c4375f9648cc Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 18:11:52 +0100 Subject: [PATCH 1188/4053] stop propagation after undoing last commit --- lib/views/recent-commits-view.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/views/recent-commits-view.js b/lib/views/recent-commits-view.js index 0f01a6f3f8..fd0bea625c 100644 --- a/lib/views/recent-commits-view.js +++ b/lib/views/recent-commits-view.js @@ -35,7 +35,7 @@ class RecentCommitView extends React.Component { {this.props.isMostRecent && ( )} @@ -80,6 +80,11 @@ class RecentCommitView extends React.Component { ); } + + undoLastCommit = event => { + event.stopPropagation(); + this.props.undoLastCommit(); + } } export default class RecentCommitsView extends React.Component { From c9c1c07309b837e071a9766e5f819c2809528567 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 18:45:31 +0100 Subject: [PATCH 1189/4053] disable most of the file patch header functionality when it's in a commit detail item --- lib/controllers/commit-detail-controller.js | 1 + lib/controllers/multi-file-patch-controller.js | 1 + lib/views/file-patch-header-view.js | 7 ++++--- lib/views/multi-file-patch-view.js | 5 ++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index 4d9ecebcc4..ab5ca27953 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -57,6 +57,7 @@ export default class CommitDetailController extends React.Component { multiFilePatch={commit.getMultiFileDiff()} autoHeight={true} {...this.props} + disableStageUnstage={true} />
  • ); diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 667184aae4..139d15a0a3 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -26,6 +26,7 @@ export default class MultiFilePatchController extends React.Component { undoLastDiscard: PropTypes.func, surface: PropTypes.func, autoHeight: PropTypes.bool, + disableStageUnstage: PropTypes.bool, } constructor(props) { diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 97a54c81d9..c76311f353 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -26,6 +26,7 @@ export default class FilePatchHeaderView extends React.Component { toggleFile: PropTypes.func.isRequired, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, + disableStageUnstage: PropTypes.bool, }; constructor(props) { @@ -75,10 +76,10 @@ export default class FilePatchHeaderView extends React.Component { renderButtonGroup() { return ( - {this.renderUndoDiscardButton()} - {this.renderMirrorPatchButton()} + {!this.props.disableStageUnstage && this.renderUndoDiscardButton()} + {!this.props.disableStageUnstage && this.renderMirrorPatchButton()} {this.renderOpenFileButton()} - {this.renderToggleFileButton()} + {!this.props.disableStageUnstage && this.renderToggleFileButton()} ); } diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 7e2d775799..a066c29bac 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -59,7 +59,7 @@ export default class MultiFilePatchView extends React.Component { undoLastDiscard: PropTypes.func, discardRows: PropTypes.func, autoHeight: PropTypes.bool, - + disableStageUnstage: PropTypes.bool, refInitialFocus: RefHolderPropType, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, } @@ -329,6 +329,7 @@ export default class MultiFilePatchView extends React.Component { diveIntoMirrorPatch={() => this.props.diveIntoMirrorPatch(filePatch)} openFile={() => this.didOpenFile({selectedFilePatch: filePatch})} toggleFile={() => this.props.toggleFile(filePatch)} + disableStageUnstage={this.props.disableStageUnstage} /> {this.renderSymlinkChangeMeta(filePatch)} {this.renderExecutableModeChangeMeta(filePatch)} @@ -504,6 +505,8 @@ export default class MultiFilePatchView extends React.Component { toggleSelection={() => this.toggleHunkSelection(hunk, containsSelection)} discardSelection={() => this.discardHunkSelection(hunk, containsSelection)} mouseDown={this.didMouseDownOnHeader} + + disableStageUnstage={this.props.disableStageUnstage} /> From b80ee27e0f145a2be86c41641a6673836732aeb2 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 18:45:44 +0100 Subject: [PATCH 1190/4053] same for hunk header --- lib/views/hunk-header-view.js | 59 +++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index 8ee5e96dda..0c4dd8516d 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -28,11 +28,12 @@ export default class HunkHeaderView extends React.Component { toggleSelection: PropTypes.func, discardSelection: PropTypes.func, mouseDown: PropTypes.func.isRequired, + disableStageUnstage: PropTypes.bool, }; constructor(props) { super(props); - autobind(this, 'didMouseDown'); + autobind(this, 'didMouseDown', 'renderButtons'); this.refDiscardButton = new RefHolder(); } @@ -48,32 +49,44 @@ export default class HunkHeaderView extends React.Component { {this.props.hunk.getHeader().trim()} {this.props.hunk.getSectionHeading().trim()} - - {this.props.stagingStatus === 'unstaged' && ( - -
    ); } + renderButtons() { + if (this.props.disableStageUnstage) { + return null; + } else { + return ( + + + {this.props.stagingStatus === 'unstaged' && ( + +
    @@ -122,4 +127,8 @@ export default class CredentialDialog extends React.Component { focusFirstInput() { (this.usernameInput || this.passwordInput).focus(); } + + toggleShowPassword() { + this.setState({showPassword: !this.state.showPassword}); + } } diff --git a/styles/dialog.less b/styles/dialog.less index 161d8d3293..e5e0e784c9 100644 --- a/styles/dialog.less +++ b/styles/dialog.less @@ -19,7 +19,22 @@ &Label { flex: 1; margin: @github-dialog-spacing; + position: relative; line-height: 2; + + &Button { + position: absolute; + background: transparent; + right: .2em; + bottom: 0; + border: none; + color: #000; + cursor: pointer; + + &:hover { + color: #888; + } + } } &Buttons { From 0870a3545a4074d77cf91f70b86f389e697a8e2d Mon Sep 17 00:00:00 2001 From: Eric Cornelissen Date: Sun, 25 Nov 2018 15:51:01 +0200 Subject: [PATCH 1192/4053] Style show/hide credentials button using the active theme --- styles/dialog.less | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/styles/dialog.less b/styles/dialog.less index e5e0e784c9..ef9df6efbe 100644 --- a/styles/dialog.less +++ b/styles/dialog.less @@ -25,14 +25,14 @@ &Button { position: absolute; background: transparent; - right: .2em; + right: .3em; bottom: 0; border: none; - color: #000; + color: @text-color-subtle; cursor: pointer; &:hover { - color: #888; + color: @text-color-highlight; } } } From dfccca2e5308d8da065fac0308cdee6b24e34c12 Mon Sep 17 00:00:00 2001 From: Eric Cornelissen Date: Sun, 25 Nov 2018 16:59:17 +0200 Subject: [PATCH 1193/4053] Test show/hide credentials button --- test/views/credential-dialog.test.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/views/credential-dialog.test.js b/test/views/credential-dialog.test.js index a98e71613b..86bd080494 100644 --- a/test/views/credential-dialog.test.js +++ b/test/views/credential-dialog.test.js @@ -85,4 +85,24 @@ describe('CredentialDialog', function() { assert.isFalse(wrapper.find('.github-CredentialDialog-remember').exists()); }); }); + + describe('show password', function() { + it('sets the passwords input type to "text" on the first click', function() { + wrapper = mount(app); + + wrapper.find('.github-DialogLabelButton').simulate('click'); + + const passwordInput = wrapper.find('.github-CredentialDialog-Password'); + assert.equal(passwordInput.prop('type'), 'text'); + }); + + it('sets the passwords input type back to "password" on the second click', function() { + wrapper = mount(app); + + wrapper.find('.github-DialogLabelButton').simulate('click').simulate('click'); + + const passwordInput = wrapper.find('.github-CredentialDialog-Password'); + assert.equal(passwordInput.prop('type'), 'password'); + }); + }); }); From 5be7bd69ef05fe59a667eab379375d6367ae9011 Mon Sep 17 00:00:00 2001 From: Benjamin Gray Date: Mon, 26 Nov 2018 21:55:02 +1100 Subject: [PATCH 1194/4053] Only log error if enabled --- lib/models/workdir-cache.js | 12 +++++++----- package.json | 5 +++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/models/workdir-cache.js b/lib/models/workdir-cache.js index 08bbc87385..dcb946dfb0 100644 --- a/lib/models/workdir-cache.js +++ b/lib/models/workdir-cache.js @@ -50,11 +50,13 @@ export default class WorkdirCache { const gitDir = await CompositeGitStrategy.create(startDir).exec(['rev-parse', '--absolute-git-dir']); return this.revParse(path.resolve(gitDir, '..')); } catch (e) { - // eslint-disable-next-line no-console - console.error( - `Unable to locate git workspace root for ${startPath}. Expected if ${startPath} is not in a git repository.`, - e, - ); + if (atom.config.get('github.reportCannotLocateWorkspaceError')) { + // eslint-disable-next-line no-console + console.error( + `Unable to locate git workspace root for ${startPath}. Expected if ${startPath} is not in a git repository.`, + e, + ); + } return null; } } diff --git a/package.json b/package.json index 628a56bc5d..94ea85116d 100644 --- a/package.json +++ b/package.json @@ -189,6 +189,11 @@ "type": "string", "default": "", "description": "Comma-separated list of email addresses to exclude from the co-author selection list" + }, + "reportCannotLocateWorkspaceError": { + "type": "boolean", + "default": "false", + "description": "Log an error to the console if a git repository cannot be located for the opened file " } }, "deserializers": { From 80a923f71d097f682a70627486a9b512e4582e7a Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 26 Nov 2018 14:41:14 +0100 Subject: [PATCH 1195/4053] =?UTF-8?q?=E2=9E=95=20CommitDetailContainer=20t?= =?UTF-8?q?est?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../commit-detail-container.test.js | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 test/containers/commit-detail-container.test.js diff --git a/test/containers/commit-detail-container.test.js b/test/containers/commit-detail-container.test.js new file mode 100644 index 0000000000..c3ad0d4771 --- /dev/null +++ b/test/containers/commit-detail-container.test.js @@ -0,0 +1,72 @@ +import React from 'react'; +import {mount} from 'enzyme'; + +import CommitDetailContainer from '../../lib/containers/commit-detail-container'; +import CommitDetailItem from '../../lib/items/commit-detail-item'; +import {cloneRepository, buildRepository} from '../helpers'; + +describe.only('CommitDetailContainer', function() { + let atomEnv, repository; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + + const workdir = await cloneRepository('multiple-commits'); + repository = await buildRepository(workdir); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(override = {}) { + + const props = { + repository, + sha: '18920c900bfa6e4844853e7e246607a31c3e2e8c', + + itemType: CommitDetailItem, + workspace: atomEnv.workspace, + commands: atomEnv.commands, + keymaps: atomEnv.keymaps, + tooltips: atomEnv.tooltips, + config: atomEnv.config, + + destroy: () => {}, + + ...override, + }; + + return ; + } + + it('renders a loading spinner while the repository is loading', function() { + const wrapper = mount(buildApp()); + assert.isTrue(wrapper.find('LoadingView').exists()); + }); + + it('renders a loading spinner while the file patch is being loaded', async function() { + await repository.getLoadPromise(); + const patchPromise = repository.getStagedChangesPatch(); + let resolveDelayedPromise = () => {}; + const delayedPromise = new Promise(resolve => { + resolveDelayedPromise = resolve; + }); + sinon.stub(repository, 'getCommit').returns(delayedPromise); + + const wrapper = mount(buildApp()); + + assert.isTrue(wrapper.find('LoadingView').exists()); + resolveDelayedPromise(patchPromise); + await assert.async.isFalse(wrapper.update().find('LoadingView').exists()); + }); + + it('renders a CommitDetailController once the commit is loaded', async function() { + await repository.getLoadPromise(); + const commit = await repository.getCommit('18920c900bfa6e4844853e7e246607a31c3e2e8c'); + + const wrapper = mount(buildApp()); + await assert.async.isTrue(wrapper.update().find('CommitDetailController').exists()); + assert.strictEqual(wrapper.find('CommitDetailController').prop('commit'), commit); + }); +}); From 70a707bbf897e9e2b044050371b4664e793663f8 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 26 Nov 2018 14:42:31 +0100 Subject: [PATCH 1196/4053] =?UTF-8?q?=F0=9F=94=A5=20`.only`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/containers/commit-detail-container.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/containers/commit-detail-container.test.js b/test/containers/commit-detail-container.test.js index c3ad0d4771..2fccdc3614 100644 --- a/test/containers/commit-detail-container.test.js +++ b/test/containers/commit-detail-container.test.js @@ -5,7 +5,7 @@ import CommitDetailContainer from '../../lib/containers/commit-detail-container' import CommitDetailItem from '../../lib/items/commit-detail-item'; import {cloneRepository, buildRepository} from '../helpers'; -describe.only('CommitDetailContainer', function() { +describe('CommitDetailContainer', function() { let atomEnv, repository; beforeEach(async function() { From 369ee23d9d259060ece73eef5bd497bf9a15ee7a Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 26 Nov 2018 15:48:14 +0100 Subject: [PATCH 1197/4053] =?UTF-8?q?=E2=9E=95=20[wip]=20CommitDetailContr?= =?UTF-8?q?oller=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../commit-detail-controller.test.js | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 test/controllers/commit-detail-controller.test.js diff --git a/test/controllers/commit-detail-controller.test.js b/test/controllers/commit-detail-controller.test.js new file mode 100644 index 0000000000..60065958c3 --- /dev/null +++ b/test/controllers/commit-detail-controller.test.js @@ -0,0 +1,74 @@ +import React from 'react'; +import moment from 'moment'; +import {shallow, mount} from 'enzyme'; + +import {cloneRepository, buildRepository} from '../helpers'; +import CommitDetailItem from '../../lib/items/commit-detail-item'; +import CommitDetailController from '../../lib/controllers/commit-detail-controller'; +import Commit from '../../lib/models/commit'; +import {multiFilePatchBuilder} from '../builder/patch'; + +describe.only('CommitDetailController', function() { + + let atomEnv, repository, commit; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + repository = await buildRepository(await cloneRepository('multiple-commits')); + commit = await repository.getCommit('18920c900bfa6e4844853e7e246607a31c3e2e8c'); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(override = {}) { + const props = { + repository, + commit, + itemType: CommitDetailItem, + + workspace: atomEnv.workspace, + commands: atomEnv.commands, + keymaps: atomEnv.keymaps, + tooltips: atomEnv.tooltips, + config: atomEnv.config, + destroy: () => {}, + + ...override, + }; + + return ; + } + + it('sets `disableStageUnstage` flag to true for MultiFilePatchController', function() { + const wrapper = mount(buildApp()); + assert.strictEqual(wrapper.find('MultiFilePatchController').prop('disableStageUnstage'), true); + }); + + it('passes unrecognized props to a MultiFilePatchController', function() { + const extra = Symbol('extra'); + const wrapper = shallow(buildApp({extra})); + + assert.strictEqual(wrapper.find('MultiFilePatchController').prop('extra'), extra); + }); + + it('renders commit details properly', function() { + const newCommit = new Commit({ + sha: '420', + authorEmail: 'very@nice.com', + authorDate: moment().subtract(2, 'days').unix(), + messageSubject: 'subject', + messageBody: 'messageBody 🌙', + }); + const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); + sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); + const wrapper = mount(buildApp({commit: newCommit})); + + assert.strictEqual(wrapper.find('h3.github-CommitDetailView-title').text(), 'subject'); + assert.strictEqual(wrapper.find('pre.github-CommitDetailView-moreText').text(), 'messageBody 🌙'); + assert.strictEqual(wrapper.find('span.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); + + }); + +}); From a9c40efd824b676e1fbee568ed9351d6b883508d Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 26 Nov 2018 16:10:49 +0100 Subject: [PATCH 1198/4053] more tests --- test/controllers/commit-detail-controller.test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/controllers/commit-detail-controller.test.js b/test/controllers/commit-detail-controller.test.js index 60065958c3..9764d2f00a 100644 --- a/test/controllers/commit-detail-controller.test.js +++ b/test/controllers/commit-detail-controller.test.js @@ -65,9 +65,12 @@ describe.only('CommitDetailController', function() { sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); const wrapper = mount(buildApp({commit: newCommit})); - assert.strictEqual(wrapper.find('h3.github-CommitDetailView-title').text(), 'subject'); - assert.strictEqual(wrapper.find('pre.github-CommitDetailView-moreText').text(), 'messageBody 🌙'); - assert.strictEqual(wrapper.find('span.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'messageBody 🌙'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); + /* TODO fix href test */ + assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); }); From dbe16e2946a489fdbd7785b59f9a66e473c2026c Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 26 Nov 2018 16:23:52 +0100 Subject: [PATCH 1199/4053] add test for co-authored commits --- .../commit-detail-controller.test.js | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/test/controllers/commit-detail-controller.test.js b/test/controllers/commit-detail-controller.test.js index 9764d2f00a..f7a2ad2e19 100644 --- a/test/controllers/commit-detail-controller.test.js +++ b/test/controllers/commit-detail-controller.test.js @@ -8,7 +8,7 @@ import CommitDetailController from '../../lib/controllers/commit-detail-controll import Commit from '../../lib/models/commit'; import {multiFilePatchBuilder} from '../builder/patch'; -describe.only('CommitDetailController', function() { +describe('CommitDetailController', function() { let atomEnv, repository, commit; @@ -59,19 +59,41 @@ describe.only('CommitDetailController', function() { authorEmail: 'very@nice.com', authorDate: moment().subtract(2, 'days').unix(), messageSubject: 'subject', - messageBody: 'messageBody 🌙', + messageBody: 'messageBody', }); const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); const wrapper = mount(buildApp({commit: newCommit})); assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'messageBody 🌙'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'messageBody'); assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); /* TODO fix href test */ - assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); + // assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); + assert.strictEqual(wrapper.find('img.github-RecentCommit-avatar').prop('src'), 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32'); + }); + it('renders multiple avatars for co-authored commit', function() { + const newCommit = new Commit({ + sha: '420', + authorEmail: 'very@nice.com', + authorDate: moment().subtract(2, 'days').unix(), + messageSubject: 'subject', + messageBody: 'messageBody', + coAuthors: [{name: 'two', email: 'two@coauthor.com'}, {name: 'three', email: 'three@coauthor.com'}], + }); + const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); + sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); + const wrapper = mount(buildApp({commit: newCommit})); + assert.deepEqual( + wrapper.find('img.github-RecentCommit-avatar').map(w => w.prop('src')), + [ + 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32', + 'https://avatars.githubusercontent.com/u/e?email=two%40coauthor.com&s=32', + 'https://avatars.githubusercontent.com/u/e?email=three%40coauthor.com&s=32', + ], + ); }); }); From ef7e68afbb11eb2a5d4a869292d430b16269ddf6 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 26 Nov 2018 16:28:03 +0100 Subject: [PATCH 1200/4053] verify multifilepatchcontroller better --- test/controllers/commit-detail-controller.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/controllers/commit-detail-controller.test.js b/test/controllers/commit-detail-controller.test.js index f7a2ad2e19..d045d8b6d5 100644 --- a/test/controllers/commit-detail-controller.test.js +++ b/test/controllers/commit-detail-controller.test.js @@ -41,9 +41,10 @@ describe('CommitDetailController', function() { return ; } - it('sets `disableStageUnstage` flag to true for MultiFilePatchController', function() { + it('has a MultiFilePatchController that has `disableStageUnstage` flag set to true', function() { const wrapper = mount(buildApp()); - assert.strictEqual(wrapper.find('MultiFilePatchController').prop('disableStageUnstage'), true); + assert.isTrue(wrapper.find('MultiFilePatchController').exists()); + assert.isTrue(wrapper.find('MultiFilePatchController').prop('disableStageUnstage')); }); it('passes unrecognized props to a MultiFilePatchController', function() { From 9d15000b1202aca849aab3bb6aaba5c84438a9a1 Mon Sep 17 00:00:00 2001 From: simurai Date: Tue, 27 Nov 2018 15:41:30 +0900 Subject: [PATCH 1201/4053] Update screenshots with the commit preview --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 04f232016b..07b7ea2bea 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ The Atom GitHub package provides Git and GitHub integration for Atom. Check out [github.atom.io](https://github.atom.io) for more information. -git-integration +GitHub for Atom -merge-conflicts +git-integration -github-integration +github-integration ## Installation From 7c7fb1acec4caa90c69736fcfeb115e5995079de Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 27 Nov 2018 15:12:43 -0500 Subject: [PATCH 1202/4053] Hand-offs explicitly in Slack Co-Authored-By: Katrina Uychaco --- docs/core-team-process.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core-team-process.md b/docs/core-team-process.md index 5559ab93a7..3e9fe3406a 100644 --- a/docs/core-team-process.md +++ b/docs/core-team-process.md @@ -57,9 +57,9 @@ The problem at hand is decomposed into a queue of relatively independent tasks t ### 3. Hand-offs -In this method, each developer (or pair) tackles a single problem in serial during their working hours. When the next developer becomes available, the previous one writes a summary of their efforts and progress in a hand-off, either synchronously and interactively in Slack or asynchronously with a pull request comment. Once the next developer is caught up, they make progress and hand off to the next, and so on. +In this method, each developer (or pair) tackles a single problem in serial during their working hours. When the next developer becomes available, the previous one writes a summary of their efforts and progress in a hand-off, synchronously and interactively in a dedicated Slack channel. Once the next developer is caught up, they make progress and hand off to the next, and so on. -> Example: developer A logs in during their morning and works for a few hours on the next phase of a feature implementation. They make some progress on the model, but don't progress the controller beyond some stubs and don't get a chance to touch the view at all. When developer B logs in, developer A shares their progress with a conversation in Slack or Zoom until developer B is confident that they understand the problem's current state, at which point developer B begins working and making commits to the feature branch. Developer B implements the view, correcting and adding some methods to the model as needed. Finally, developer C logs in, and developers A and C pair to write the controller methods. They write a comment on the pull request as they wrap up describing the changes that they've made together. Developer B returns the next day, puts the finishing touches on the tests, writes or refines some documentation on the new code, and merges the pull request. +> Example: developer A logs in during their morning and works for a few hours on the next phase of a feature implementation. They make some progress on the model, but don't progress the controller beyond some stubs and don't get a chance to touch the view at all. When developer B logs in, developer A shares their progress with a conversation in Slack until developer B is confident that they understand the problem's current state, at which point developer B begins working and making commits to the feature branch. Developer B implements the view, correcting and adding some methods to the model as needed. Finally, developer C logs in, and developers A and C pair to write the controller methods. They update Slack with their progress as they wrap up describing the changes that they've made together. Developer B returns the next day, puts the finishing touches on the tests, writes or refines some documentation on the new code, and merges the pull request. :+1: _Advantages:_ From 3567cd17717920a122d370e937b5c6aa3fe7af8f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 27 Nov 2018 15:16:21 -0500 Subject: [PATCH 1203/4053] Feature-specific project board Co-Authored-By: Katrina Uychaco Co-Authored-By: Tilde Ann Thurium Co-Authored-By: Vanessa Yuen --- docs/core-team-process.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core-team-process.md b/docs/core-team-process.md index 3e9fe3406a..825ee6e974 100644 --- a/docs/core-team-process.md +++ b/docs/core-team-process.md @@ -103,7 +103,7 @@ Use a package configuration setting to control when features under development a ## All together -Each set of developers who are online synchronously can divide work into Seams. As that set changes when people come online and drop offline, we use Handoffs to pass context along. +Each set of developers who are online synchronously can divide work into Seams. As that set changes when people come online and drop offline, we use Handoffs to pass context along. Remaining tasking is tracked in a dedicated, loosely-managed feature project linked from the feature request PR. As we work, we push commits to a common branch, against a common pull request. Depending on the feature under construction, we either Dark Ship code in an early state or hide its entry points behind a Feature Flag. From 934a8565e83385d7682a332ff50bbb8b8351623b Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 27 Nov 2018 22:19:17 +0100 Subject: [PATCH 1204/4053] fix arguments passed during serialization --- lib/items/commit-detail-item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/items/commit-detail-item.js b/lib/items/commit-detail-item.js index ed8f5f1936..ea29e43a9a 100644 --- a/lib/items/commit-detail-item.js +++ b/lib/items/commit-detail-item.js @@ -80,7 +80,7 @@ export default class CommitDetailItem extends React.Component { serialize() { return { deserializer: 'CommitDetailStub', - uri: CommitDetailItem.buildURI(this.props.sha), + uri: CommitDetailItem.buildURI(this.props.workingDirectory, this.props.sha), }; } From 1057245ed2326d1ec1b3866bb70815dc7aa5dc73 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 27 Nov 2018 22:30:17 +0100 Subject: [PATCH 1205/4053] get started with `CommitDetailItem` tests --- test/items/commit-detail-item.test.js | 73 +++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 test/items/commit-detail-item.test.js diff --git a/test/items/commit-detail-item.test.js b/test/items/commit-detail-item.test.js new file mode 100644 index 0000000000..4c236d03cc --- /dev/null +++ b/test/items/commit-detail-item.test.js @@ -0,0 +1,73 @@ +import React from 'react'; +import {mount} from 'enzyme'; + +import CommitDetailItem from '../../lib/items/commit-detail-item'; +import PaneItem from '../../lib/atom/pane-item'; +import WorkdirContextPool from '../../lib/models/workdir-context-pool'; +import {cloneRepository} from '../helpers'; + +describe.only('CommitDetailItem', function() { + let atomEnv, repository, pool; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + const workdir = await cloneRepository('multiple-commits'); + + pool = new WorkdirContextPool({ + workspace: atomEnv.workspace, + }); + + repository = pool.add(workdir).getRepository(); + }); + + afterEach(function() { + atomEnv.destroy(); + pool.clear(); + }); + + function buildPaneApp(override = {}) { + const props = { + workdirContextPool: pool, + workspace: atomEnv.workspace, + commands: atomEnv.commands, + keymaps: atomEnv.keymaps, + tooltips: atomEnv.tooltips, + config: atomEnv.config, + discardLines: () => {}, + ...override, + }; + + return ( + + {({itemHolder, params}) => { + return ( + + ); + }} + + ); + } + + function open(wrapper, options = {}) { + const opts = { + workingDirectory: repository.getWorkingDirectoryPath(), + sha: '18920c900bfa6e4844853e7e246607a31c3e2e8c', + ...options, + }; + const uri = CommitDetailItem.buildURI(opts.workingDirectory); + return atomEnv.workspace.open(uri); + } + + it('constructs and opens the correct URI', async function() { + const wrapper = mount(buildPaneApp()); + await open(wrapper); + + assert.isTrue(wrapper.update().find('CommitDetailItem').exists()); + }); + +}); From b75bab63cd4861084d139d138571436fa73ef028 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 27 Nov 2018 22:37:33 +0100 Subject: [PATCH 1206/4053] add several more tests --- test/items/commit-detail-item.test.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/test/items/commit-detail-item.test.js b/test/items/commit-detail-item.test.js index 4c236d03cc..f0bc2eeb89 100644 --- a/test/items/commit-detail-item.test.js +++ b/test/items/commit-detail-item.test.js @@ -59,7 +59,7 @@ describe.only('CommitDetailItem', function() { sha: '18920c900bfa6e4844853e7e246607a31c3e2e8c', ...options, }; - const uri = CommitDetailItem.buildURI(opts.workingDirectory); + const uri = CommitDetailItem.buildURI(opts.workingDirectory, opts.sha); return atomEnv.workspace.open(uri); } @@ -70,4 +70,27 @@ describe.only('CommitDetailItem', function() { assert.isTrue(wrapper.update().find('CommitDetailItem').exists()); }); + it('passes extra props to its container', async function() { + const extra = Symbol('extra'); + const wrapper = mount(buildPaneApp({extra})); + await open(wrapper); + + assert.strictEqual(wrapper.update().find('CommitDetailItem').prop('extra'), extra); + }); + + it('serializes itself as a CommitDetailItem', async function() { + const wrapper = mount(buildPaneApp()); + const item0 = await open(wrapper, {workingDirectory: '/dir0', sha: '420'}); + assert.deepEqual(item0.serialize(), { + deserializer: 'CommitDetailStub', + uri: 'atom-github://commit-detail?workdir=%2Fdir0&sha=420', + }); + + const item1 = await open(wrapper, {workingDirectory: '/dir1', sha: '1337'}); + assert.deepEqual(item1.serialize(), { + deserializer: 'CommitDetailStub', + uri: 'atom-github://commit-detail?workdir=%2Fdir1&sha=1337', + }); + }); + }); From 5add6404099bfdc7263db5907bd579cd327ff27d Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 27 Nov 2018 22:43:50 +0100 Subject: [PATCH 1207/4053] and MOAR --- test/items/commit-detail-item.test.js | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/items/commit-detail-item.test.js b/test/items/commit-detail-item.test.js index f0bc2eeb89..4d0f376986 100644 --- a/test/items/commit-detail-item.test.js +++ b/test/items/commit-detail-item.test.js @@ -93,4 +93,56 @@ describe.only('CommitDetailItem', function() { }); }); + it('locates the repository from the context pool', async function() { + const wrapper = mount(buildPaneApp()); + await open(wrapper); + + assert.strictEqual(wrapper.update().find('CommitDetailContainer').prop('repository'), repository); + }); + + it('passes an absent repository if the working directory is unrecognized', async function() { + const wrapper = mount(buildPaneApp()); + await open(wrapper, {workingDirectory: '/nah'}); + + assert.isTrue(wrapper.update().find('CommitDetailContainer').prop('repository').isAbsent()); + }); + + it('returns a fixed title and icon', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(wrapper, {sha: '1337'}); + + assert.strictEqual(item.getTitle(), 'Commit: 1337'); + assert.strictEqual(item.getIconName(), 'git-commit'); + }); + + it('terminates pending state', async function() { + const wrapper = mount(buildPaneApp()); + + const item = await open(wrapper); + const callback = sinon.spy(); + const sub = item.onDidTerminatePendingState(callback); + + assert.strictEqual(callback.callCount, 0); + item.terminatePendingState(); + assert.strictEqual(callback.callCount, 1); + item.terminatePendingState(); + assert.strictEqual(callback.callCount, 1); + + sub.dispose(); + }); + + it('may be destroyed once', async function() { + const wrapper = mount(buildPaneApp()); + + const item = await open(wrapper); + const callback = sinon.spy(); + const sub = item.onDidDestroy(callback); + + assert.strictEqual(callback.callCount, 0); + item.destroy(); + assert.strictEqual(callback.callCount, 1); + + sub.dispose(); + }); + }); From 81315d2fbde0f852339a6c82450494e7afcd5c6e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 27 Nov 2018 23:00:06 +0100 Subject: [PATCH 1208/4053] add sha accessor --- lib/items/commit-detail-item.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/items/commit-detail-item.js b/lib/items/commit-detail-item.js index ea29e43a9a..fdfc39e638 100644 --- a/lib/items/commit-detail-item.js +++ b/lib/items/commit-detail-item.js @@ -77,6 +77,10 @@ export default class CommitDetailItem extends React.Component { return this.props.workingDirectory; } + getSha() { + return this.props.sha; + } + serialize() { return { deserializer: 'CommitDetailStub', From 7a10d03841f473b529a31f39326608d74e9550ba Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 27 Nov 2018 23:00:34 +0100 Subject: [PATCH 1209/4053] i *think* this is it for `CommitDetailItem` tests??? :thinking: --- test/items/commit-detail-item.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/items/commit-detail-item.test.js b/test/items/commit-detail-item.test.js index 4d0f376986..7199b6b20d 100644 --- a/test/items/commit-detail-item.test.js +++ b/test/items/commit-detail-item.test.js @@ -145,4 +145,11 @@ describe.only('CommitDetailItem', function() { sub.dispose(); }); + it('has an item-level accessor for the current working directory & sha', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(wrapper, {workingDirectory: '/dir7', sha: '420'}); + assert.strictEqual(item.getWorkingDirectory(), '/dir7'); + assert.strictEqual(item.getSha(), '420'); + }); + }); From ae893960013187a712f4c8aad45541366f152fec Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 28 Nov 2018 21:34:05 +0900 Subject: [PATCH 1210/4053] Add focus styling to selections --- styles/file-patch-view.less | 61 +++++++++++++++++++++++++----------- styles/hunk-header-view.less | 24 +++++++++++--- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 3de411746a..fcf9a7cb48 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -32,14 +32,6 @@ } } - // Editor overrides - - atom-text-editor { - .selection .region { - background-color: mix(@button-background-color-selected, @syntax-background-color, 24%); - } - } - &-header { display: flex; justify-content: space-between; @@ -220,11 +212,6 @@ min-width: 6ch; // Fit up to 4 characters (+1 padding on each side) opacity: 1; padding: 0 1ch 0 0; - - &.github-FilePatchView-line--selected { - color: contrast(@button-background-color-selected); - background: @button-background-color-selected; - } } &.icons .line-number { @@ -249,11 +236,6 @@ &.github-FilePatchView-line--nonewline:before { content: @no-newline; } - - &.github-FilePatchView-line--selected { - color: contrast(@button-background-color-selected); - background: @button-background-color-selected; - } } } @@ -272,6 +254,49 @@ } } + +// States + +// Selected +.github-FilePatchView { + .gutter { + &.old .line-number, + &.new .line-number, + &.icons .line-number { + &.github-FilePatchView-line--selected { + color: @text-color-selected; + background: @background-color-selected; + } + } + } + + atom-text-editor { + .selection .region { + background-color: transparent; + } + } +} + +// Selected + focused +.github-FilePatchView:focus-within { + .gutter { + &.old .line-number, + &.new .line-number, + &.icons .line-number { + &.github-FilePatchView-line--selected { + color: contrast(@button-background-color-selected); + background: @button-background-color-selected; + } + } + } + + atom-text-editor { + .selection .region { + background-color: mix(@button-background-color-selected, @syntax-background-color, 24%); + } + } +} + .gitub-FilePatchHeaderView-basename { font-weight: bold; } diff --git a/styles/hunk-header-view.less b/styles/hunk-header-view.less index 11cffdec76..5d4bfa61de 100644 --- a/styles/hunk-header-view.less +++ b/styles/hunk-header-view.less @@ -68,9 +68,10 @@ } } +// Selected .github-HunkHeaderView--isSelected { - color: contrast(@button-background-color-selected); - background-color: @button-background-color-selected; + color: @text-color-selected; + background-color: @background-color-selected; border-color: transparent; .github-HunkHeaderView-title { color: inherit; @@ -78,7 +79,22 @@ .github-HunkHeaderView-title, .github-HunkHeaderView-stageButton, .github-HunkHeaderView-discardButton { - &:hover { background-color: lighten(@button-background-color-selected, 4%); } - &:active { background-color: darken(@button-background-color-selected, 4%); } + &:hover { background-color: @background-color-highlight; } + &:active { background-color: @background-color-selected; } + } +} + + +// Selected + focused +.github-FilePatchView:focus-within { + .github-HunkHeaderView--isSelected { + color: contrast(@button-background-color-selected); + background-color: @button-background-color-selected; + .github-HunkHeaderView-title, + .github-HunkHeaderView-stageButton, + .github-HunkHeaderView-discardButton { + &:hover { background-color: lighten(@button-background-color-selected, 4%); } + &:active { background-color: darken(@button-background-color-selected, 4%); } + } } } From cdbe9ea4a9dd3bdfeb198c907ed14effb63fc74d Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 28 Nov 2018 21:47:59 +0900 Subject: [PATCH 1211/4053] Add hover styles to recent commits --- styles/recent-commits.less | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/styles/recent-commits.less b/styles/recent-commits.less index 31e0d2034c..f9ad97ef66 100644 --- a/styles/recent-commits.less +++ b/styles/recent-commits.less @@ -87,6 +87,11 @@ color: @text-color-subtle; } + &:hover { + color: @text-color-highlight; + background: @background-color-highlight; + } + &.is-selected { // is selected color: @text-color-selected; From a537d84956fd909e150bfae0698c6c4cdd0661c8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 09:53:19 -0500 Subject: [PATCH 1212/4053] Allow the FilePatchView to control scrolling within the AtomTextEditor Ensuring that the is the element that handles scrolling within the DOM gives us a substantial performance boost, because it can tile its lines and decorations and render much less content on any given frame. Moving the scrolling here gets us a "fixed" header for free, too :wink: --- lib/controllers/commit-detail-controller.js | 2 +- styles/commit-detail.less | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index ab5ca27953..d73bf852f2 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -55,7 +55,7 @@ export default class CommitDetailController extends React.Component {
    diff --git a/styles/commit-detail.less b/styles/commit-detail.less index fc8db7a940..6f8c24f975 100644 --- a/styles/commit-detail.less +++ b/styles/commit-detail.less @@ -3,19 +3,13 @@ @default-padding: @component-padding; @avatar-dimensions: 16px; -.github-CommitDetail { - - &-root { - // TODO: Remove if CommitDetailView gets moved to a editor decoration - overflow-y: auto; - } - -} - - .github-CommitDetailView { + display: flex; + flex-direction: column; + height: 100%; &-header { + flex: 0; padding: @default-padding; padding-bottom: 0; background-color: @syntax-background-color; From 5c7aa08f03effb162b2408255cd0d2e08c9fda5c Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 28 Nov 2018 14:51:58 +0100 Subject: [PATCH 1213/4053] remove jump to file --- lib/views/file-patch-header-view.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index c76311f353..5eb5476d08 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -74,14 +74,18 @@ export default class FilePatchHeaderView extends React.Component { } renderButtonGroup() { - return ( - - {!this.props.disableStageUnstage && this.renderUndoDiscardButton()} - {!this.props.disableStageUnstage && this.renderMirrorPatchButton()} - {this.renderOpenFileButton()} - {!this.props.disableStageUnstage && this.renderToggleFileButton()} - - ); + if (this.props.disableStageUnstage) { + return null; + } else { + return ( + + {this.renderUndoDiscardButton()} + {this.renderMirrorPatchButton()} + {this.renderOpenFileButton()} + {this.renderToggleFileButton()} + + ); + } } renderUndoDiscardButton() { From fc6ca8ac95dc2d25627383a4347cbe788bfdea2e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 10:38:09 -0500 Subject: [PATCH 1214/4053] Cover that last line in CommitDetailItem --- test/items/commit-detail-item.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/items/commit-detail-item.test.js b/test/items/commit-detail-item.test.js index 7199b6b20d..6a6d4e89d3 100644 --- a/test/items/commit-detail-item.test.js +++ b/test/items/commit-detail-item.test.js @@ -152,4 +152,17 @@ describe.only('CommitDetailItem', function() { assert.strictEqual(item.getSha(), '420'); }); + it('passes a focus() call to the component designated as its initial focus', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(wrapper); + wrapper.update(); + + const refHolder = wrapper.find('CommitDetailContainer').prop('refInitialFocus'); + const initialFocus = await refHolder.getPromise(); + sinon.spy(initialFocus, 'focus'); + + item.focus(); + + assert.isTrue(initialFocus.focus.called); + }); }); From b61b9ae96f5c4ee89941f2f86730fece51d88ea2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 10:38:16 -0500 Subject: [PATCH 1215/4053] :fire: dot only --- test/items/commit-detail-item.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/items/commit-detail-item.test.js b/test/items/commit-detail-item.test.js index 6a6d4e89d3..eecbc66cf6 100644 --- a/test/items/commit-detail-item.test.js +++ b/test/items/commit-detail-item.test.js @@ -6,7 +6,7 @@ import PaneItem from '../../lib/atom/pane-item'; import WorkdirContextPool from '../../lib/models/workdir-context-pool'; import {cloneRepository} from '../helpers'; -describe.only('CommitDetailItem', function() { +describe('CommitDetailItem', function() { let atomEnv, repository, pool; beforeEach(async function() { From 867397f5b48266a486fb3a509762377a7ed8c7f3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 10:46:13 -0500 Subject: [PATCH 1216/4053] Ensure we return a valid Commit to suppress console errors --- test/containers/commit-detail-container.test.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/containers/commit-detail-container.test.js b/test/containers/commit-detail-container.test.js index 2fccdc3614..e4dff8718f 100644 --- a/test/containers/commit-detail-container.test.js +++ b/test/containers/commit-detail-container.test.js @@ -5,6 +5,8 @@ import CommitDetailContainer from '../../lib/containers/commit-detail-container' import CommitDetailItem from '../../lib/items/commit-detail-item'; import {cloneRepository, buildRepository} from '../helpers'; +const VALID_SHA = '18920c900bfa6e4844853e7e246607a31c3e2e8c'; + describe('CommitDetailContainer', function() { let atomEnv, repository; @@ -23,7 +25,7 @@ describe('CommitDetailContainer', function() { const props = { repository, - sha: '18920c900bfa6e4844853e7e246607a31c3e2e8c', + sha: VALID_SHA, itemType: CommitDetailItem, workspace: atomEnv.workspace, @@ -47,7 +49,7 @@ describe('CommitDetailContainer', function() { it('renders a loading spinner while the file patch is being loaded', async function() { await repository.getLoadPromise(); - const patchPromise = repository.getStagedChangesPatch(); + const commitPromise = repository.getCommit(VALID_SHA); let resolveDelayedPromise = () => {}; const delayedPromise = new Promise(resolve => { resolveDelayedPromise = resolve; @@ -57,13 +59,13 @@ describe('CommitDetailContainer', function() { const wrapper = mount(buildApp()); assert.isTrue(wrapper.find('LoadingView').exists()); - resolveDelayedPromise(patchPromise); + resolveDelayedPromise(commitPromise); await assert.async.isFalse(wrapper.update().find('LoadingView').exists()); }); it('renders a CommitDetailController once the commit is loaded', async function() { await repository.getLoadPromise(); - const commit = await repository.getCommit('18920c900bfa6e4844853e7e246607a31c3e2e8c'); + const commit = await repository.getCommit(VALID_SHA); const wrapper = mount(buildApp()); await assert.async.isTrue(wrapper.update().find('CommitDetailController').exists()); From b27744610eab776f72814bbc14fe7bc480fcbe1e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 28 Nov 2018 18:50:04 +0100 Subject: [PATCH 1217/4053] remove `disableStageUnstage` flag and use `itemType` as checker instead --- lib/controllers/commit-detail-controller.js | 1 - lib/controllers/multi-file-patch-controller.js | 1 - lib/views/file-patch-header-view.js | 3 +-- lib/views/hunk-header-view.js | 1 - lib/views/multi-file-patch-view.js | 4 ---- test/controllers/commit-detail-controller.test.js | 3 +-- 6 files changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index d73bf852f2..b5b3c3f5bb 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -57,7 +57,6 @@ export default class CommitDetailController extends React.Component { multiFilePatch={commit.getMultiFileDiff()} autoHeight={false} {...this.props} - disableStageUnstage={true} />
    ); diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 139d15a0a3..667184aae4 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -26,7 +26,6 @@ export default class MultiFilePatchController extends React.Component { undoLastDiscard: PropTypes.func, surface: PropTypes.func, autoHeight: PropTypes.bool, - disableStageUnstage: PropTypes.bool, } constructor(props) { diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 5eb5476d08..51131689f0 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -26,7 +26,6 @@ export default class FilePatchHeaderView extends React.Component { toggleFile: PropTypes.func.isRequired, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, - disableStageUnstage: PropTypes.bool, }; constructor(props) { @@ -74,7 +73,7 @@ export default class FilePatchHeaderView extends React.Component { } renderButtonGroup() { - if (this.props.disableStageUnstage) { + if (this.props.itemType === CommitDetailItem) { return null; } else { return ( diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index 0c4dd8516d..57e71a28a3 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -28,7 +28,6 @@ export default class HunkHeaderView extends React.Component { toggleSelection: PropTypes.func, discardSelection: PropTypes.func, mouseDown: PropTypes.func.isRequired, - disableStageUnstage: PropTypes.bool, }; constructor(props) { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index a066c29bac..3cb5a61731 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -59,7 +59,6 @@ export default class MultiFilePatchView extends React.Component { undoLastDiscard: PropTypes.func, discardRows: PropTypes.func, autoHeight: PropTypes.bool, - disableStageUnstage: PropTypes.bool, refInitialFocus: RefHolderPropType, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, } @@ -329,7 +328,6 @@ export default class MultiFilePatchView extends React.Component { diveIntoMirrorPatch={() => this.props.diveIntoMirrorPatch(filePatch)} openFile={() => this.didOpenFile({selectedFilePatch: filePatch})} toggleFile={() => this.props.toggleFile(filePatch)} - disableStageUnstage={this.props.disableStageUnstage} /> {this.renderSymlinkChangeMeta(filePatch)} {this.renderExecutableModeChangeMeta(filePatch)} @@ -505,8 +503,6 @@ export default class MultiFilePatchView extends React.Component { toggleSelection={() => this.toggleHunkSelection(hunk, containsSelection)} discardSelection={() => this.discardHunkSelection(hunk, containsSelection)} mouseDown={this.didMouseDownOnHeader} - - disableStageUnstage={this.props.disableStageUnstage} /> diff --git a/test/controllers/commit-detail-controller.test.js b/test/controllers/commit-detail-controller.test.js index d045d8b6d5..92a5659a22 100644 --- a/test/controllers/commit-detail-controller.test.js +++ b/test/controllers/commit-detail-controller.test.js @@ -41,10 +41,9 @@ describe('CommitDetailController', function() { return ; } - it('has a MultiFilePatchController that has `disableStageUnstage` flag set to true', function() { + it('has a MultiFilePatchController', function() { const wrapper = mount(buildApp()); assert.isTrue(wrapper.find('MultiFilePatchController').exists()); - assert.isTrue(wrapper.find('MultiFilePatchController').prop('disableStageUnstage')); }); it('passes unrecognized props to a MultiFilePatchController', function() { From e8620da653cefc3b17e988e11175d927ee667aa1 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 28 Nov 2018 18:50:35 +0100 Subject: [PATCH 1218/4053] use `itemType` in hunk headers too! --- lib/views/hunk-header-view.js | 6 +++++- lib/views/multi-file-patch-view.js | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index 57e71a28a3..6fd986ee54 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -7,6 +7,9 @@ import {RefHolderPropType} from '../prop-types'; import RefHolder from '../models/ref-holder'; import Tooltip from '../atom/tooltip'; import Keystroke from '../atom/keystroke'; +import ChangedFileItem from '../items/changed-file-item'; +import CommitPreviewItem from '../items/commit-preview-item'; +import CommitDetailItem from '../items/commit-detail-item'; function theBuckStopsHere(event) { event.stopPropagation(); @@ -28,6 +31,7 @@ export default class HunkHeaderView extends React.Component { toggleSelection: PropTypes.func, discardSelection: PropTypes.func, mouseDown: PropTypes.func.isRequired, + itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, }; constructor(props) { @@ -54,7 +58,7 @@ export default class HunkHeaderView extends React.Component { } renderButtons() { - if (this.props.disableStageUnstage) { + if (this.props.itemType === CommitPreviewItem) { return null; } else { return ( diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 3cb5a61731..5e3d3d73bf 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -503,6 +503,7 @@ export default class MultiFilePatchView extends React.Component { toggleSelection={() => this.toggleHunkSelection(hunk, containsSelection)} discardSelection={() => this.discardHunkSelection(hunk, containsSelection)} mouseDown={this.didMouseDownOnHeader} + itemType={this.props.itemType} /> From 22718c24c38b98b0b4cfbe7e9e9020373a0a0eac Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 13:48:21 -0500 Subject: [PATCH 1219/4053] Totally arbitrary "long commit message threshold" --- lib/models/commit.js | 10 ++++++++ test/models/commit.test.js | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 test/models/commit.test.js diff --git a/lib/models/commit.js b/lib/models/commit.js index f1fbd72a1c..c568de8066 100644 --- a/lib/models/commit.js +++ b/lib/models/commit.js @@ -1,6 +1,8 @@ const UNBORN = Symbol('unborn'); export default class Commit { + static LONG_MESSAGE_THRESHOLD = 1000; + static createUnborn() { return new Commit({unbornRef: UNBORN}); } @@ -40,6 +42,10 @@ export default class Commit { return this.messageBody; } + isBodyLong() { + return this.getMessageBody().length > this.constructor.LONG_MESSAGE_THRESHOLD; + } + getFullMessage() { return `${this.getMessageSubject()}\n\n${this.getMessageBody()}`.trim(); } @@ -77,4 +83,8 @@ export const nullCommit = { isPresent() { return false; }, + + isBodyLong() { + return false; + }, }; diff --git a/test/models/commit.test.js b/test/models/commit.test.js new file mode 100644 index 0000000000..a8cd468a4c --- /dev/null +++ b/test/models/commit.test.js @@ -0,0 +1,51 @@ +import moment from 'moment'; +import dedent from 'dedent-js'; + +import Commit, {nullCommit} from '../../lib/models/commit'; + +describe('Commit', function() { + function buildCommit(override = {}) { + return new Commit({ + sha: '0123456789abcdefghij0123456789abcdefghij', + authorEmail: 'me@email.com', + coAuthors: [], + authorDate: moment('2018-11-28T12:00:00', moment.ISO_8601).unix(), + messageSubject: 'subject', + messageBody: 'body', + ...override, + }); + } + + describe('isBodyLong()', function() { + it('returns false if the commit message body is short', function() { + assert.isFalse(buildCommit({messageBody: 'short'}).isBodyLong()); + }); + + it('returns true if the commit message body is long', function() { + const messageBody = dedent` + Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. Mazim alterum sea ea, + essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam + tantas nullam corrumpit ad, in oratio luptatum eleifend vim. + + Ea salutatus contentiones eos. Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere + urbanitas, usu ut aperiri mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, + scripta iudicabit ne nam, in duis clita commodo sit. + + Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat comprehensam ut his, et eum + voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei liber putant. Ad doctus + tractatos ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent philosophia et vix. Nusquam + reprehendunt et mea. Ea eius omnes voluptua sit. + + No cum illud verear efficiantur. Id altera imperdiet nec. Noster audiam accusamus mei at, no zril libris nemore + duo, ius ne rebum doctus fuisset. Legimus epicurei in sit, esse purto suscipit eu qui, oporteat deserunt + delicatissimi sea in. Est id putent accusata convenire, no tibique molestie accommodare quo, cu est fuisset + offendit evertitur. + `; + assert.isTrue(buildCommit({messageBody}).isBodyLong()); + }); + + it('returns false for a null commit', function() { + assert.isFalse(nullCommit.isBodyLong()); + }); + }); +}); From 8434c08aec30d92726d4ac2599297d1e132ddf19 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 13:59:03 -0500 Subject: [PATCH 1220/4053] CommitDetailController is only responsible for message collapse state All markup generation has been moved (verbatim) to CommitDetailView. --- lib/controllers/commit-detail-controller.js | 102 +++------------- lib/views/commit-detail-view.js | 102 ++++++++++++++++ .../commit-detail-controller.test.js | 110 ++++++++++-------- test/views/commit-detail-view.test.js | 69 +++++++++++ 4 files changed, 248 insertions(+), 135 deletions(-) create mode 100644 lib/views/commit-detail-view.js create mode 100644 test/views/commit-detail-view.test.js diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index b5b3c3f5bb..6e0df0c146 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -1,104 +1,38 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {emojify} from 'node-emoji'; -import moment from 'moment'; -import MultiFilePatchController from './multi-file-patch-controller'; - -const avatarAltText = 'committer avatar'; +import CommitDetailView from '../views/commit-detail-view'; export default class CommitDetailController extends React.Component { static propTypes = { - repository: PropTypes.object.isRequired, - - workspace: PropTypes.object.isRequired, - commands: PropTypes.object.isRequired, - keymaps: PropTypes.object.isRequired, - tooltips: PropTypes.object.isRequired, - config: PropTypes.object.isRequired, + ...CommitDetailView.propTypes, - destroy: PropTypes.func.isRequired, commit: PropTypes.object.isRequired, } - render() { - const commit = this.props.commit; - // const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; - // const {avatarUrl, name, date} = this.props.item.committer; - - return ( -
    -
    -
    -
    -

    - {emojify(commit.getMessageSubject())} -

    -
    -                {emojify(commit.getMessageBody())}
    -
    - {/* TODO fix image src */} - {this.renderAuthors()} - - {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} - -
    -
    -
    - {/* TODO fix href */} - - {commit.getSha()} - -
    -
    -
    - -
    - ); - } + constructor(props) { + super(props); - humanizeTimeSince(date) { - return moment(date * 1000).fromNow(); + this.state = { + messageCollapsible: this.props.commit.isBodyLong(), + messageOpen: !this.props.commit.isBodyLong(), + }; } - getAuthorInfo() { - const coAuthorCount = this.props.commit.getCoAuthors().length; - return coAuthorCount ? this.props.commit.getAuthorEmail() : `${coAuthorCount + 1} people`; - } - - renderAuthor(email) { - const match = email.match(/^(\d+)\+[^@]+@users.noreply.github.com$/); - - let avatarUrl; - if (match) { - avatarUrl = 'https://avatars.githubusercontent.com/u/' + match[1] + '?s=32'; - } else { - avatarUrl = 'https://avatars.githubusercontent.com/u/e?email=' + encodeURIComponent(email) + '&s=32'; - } - + render() { return ( - {`${email}'s ); } - renderAuthors() { - const coAuthorEmails = this.props.commit.getCoAuthors().map(author => author.email); - const authorEmails = [this.props.commit.getAuthorEmail(), ...coAuthorEmails]; - - return ( - - {authorEmails.map(this.renderAuthor)} - - ); + toggleMessage = () => { + return new Promise(resolve => { + this.setState(prevState => ({messageOpen: !prevState.messageOpen}), resolve); + }); } } diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js new file mode 100644 index 0000000000..96b38f1ec6 --- /dev/null +++ b/lib/views/commit-detail-view.js @@ -0,0 +1,102 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {emojify} from 'node-emoji'; +import moment from 'moment'; + +import MultiFilePatchController from '../controllers/multi-file-patch-controller'; + +export default class CommitDetailView extends React.Component { + static propTypes = { + repository: PropTypes.object.isRequired, + + workspace: PropTypes.object.isRequired, + commands: PropTypes.object.isRequired, + keymaps: PropTypes.object.isRequired, + tooltips: PropTypes.object.isRequired, + config: PropTypes.object.isRequired, + + destroy: PropTypes.func.isRequired, + commit: PropTypes.object.isRequired, + } + + render() { + const commit = this.props.commit; + // const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; + // const {avatarUrl, name, date} = this.props.item.committer; + + return ( +
    +
    +
    +
    +

    + {emojify(commit.getMessageSubject())} +

    +
    +                {emojify(commit.getMessageBody())}
    +
    + {/* TODO fix image src */} + {this.renderAuthors()} + + {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} + +
    +
    +
    + {/* TODO fix href */} + + {commit.getSha()} + +
    +
    +
    + +
    + ); + } + + humanizeTimeSince(date) { + return moment(date * 1000).fromNow(); + } + + getAuthorInfo() { + const coAuthorCount = this.props.commit.getCoAuthors().length; + return coAuthorCount ? this.props.commit.getAuthorEmail() : `${coAuthorCount + 1} people`; + } + + renderAuthor(email) { + const match = email.match(/^(\d+)\+[^@]+@users.noreply.github.com$/); + + let avatarUrl; + if (match) { + avatarUrl = 'https://avatars.githubusercontent.com/u/' + match[1] + '?s=32'; + } else { + avatarUrl = 'https://avatars.githubusercontent.com/u/e?email=' + encodeURIComponent(email) + '&s=32'; + } + + return ( + {`${email}'s + ); + } + + renderAuthors() { + const coAuthorEmails = this.props.commit.getCoAuthors().map(author => author.email); + const authorEmails = [this.props.commit.getAuthorEmail(), ...coAuthorEmails]; + + return ( + + {authorEmails.map(this.renderAuthor)} + + ); + } +} diff --git a/test/controllers/commit-detail-controller.test.js b/test/controllers/commit-detail-controller.test.js index 92a5659a22..2231af47da 100644 --- a/test/controllers/commit-detail-controller.test.js +++ b/test/controllers/commit-detail-controller.test.js @@ -1,21 +1,20 @@ import React from 'react'; -import moment from 'moment'; -import {shallow, mount} from 'enzyme'; +import {shallow} from 'enzyme'; +import dedent from 'dedent-js'; import {cloneRepository, buildRepository} from '../helpers'; import CommitDetailItem from '../../lib/items/commit-detail-item'; import CommitDetailController from '../../lib/controllers/commit-detail-controller'; -import Commit from '../../lib/models/commit'; -import {multiFilePatchBuilder} from '../builder/patch'; -describe('CommitDetailController', function() { +const VALID_SHA = '18920c900bfa6e4844853e7e246607a31c3e2e8c'; +describe('CommitDetailController', function() { let atomEnv, repository, commit; beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); repository = await buildRepository(await cloneRepository('multiple-commits')); - commit = await repository.getCommit('18920c900bfa6e4844853e7e246607a31c3e2e8c'); + commit = await repository.getCommit(VALID_SHA); }); afterEach(function() { @@ -41,59 +40,68 @@ describe('CommitDetailController', function() { return ; } - it('has a MultiFilePatchController', function() { - const wrapper = mount(buildApp()); - assert.isTrue(wrapper.find('MultiFilePatchController').exists()); + it('forwards props to its CommitDetailView', function() { + const wrapper = shallow(buildApp()); + const view = wrapper.find('CommitDetailView'); + + assert.strictEqual(view.prop('repository'), repository); + assert.strictEqual(view.prop('commit'), commit); + assert.strictEqual(view.prop('itemType'), CommitDetailItem); }); - it('passes unrecognized props to a MultiFilePatchController', function() { + it('passes unrecognized props to its CommitDetailView', function() { const extra = Symbol('extra'); const wrapper = shallow(buildApp({extra})); - - assert.strictEqual(wrapper.find('MultiFilePatchController').prop('extra'), extra); + assert.strictEqual(wrapper.find('CommitDetailView').prop('extra'), extra); }); - it('renders commit details properly', function() { - const newCommit = new Commit({ - sha: '420', - authorEmail: 'very@nice.com', - authorDate: moment().subtract(2, 'days').unix(), - messageSubject: 'subject', - messageBody: 'messageBody', + describe('commit body collapsing', function() { + const LONG_MESSAGE = dedent` + Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. Mazim alterum sea ea, + essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam + tantas nullam corrumpit ad, in oratio luptatum eleifend vim. + + Ea salutatus contentiones eos. Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere + urbanitas, usu ut aperiri mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, scripta + iudicabit ne nam, in duis clita commodo sit. + + Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat comprehensam ut his, et eum + voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei liber putant. Ad doctus tractatos + ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent philosophia et vix. Nusquam reprehendunt et + mea. Ea eius omnes voluptua sit. + + No cum illud verear efficiantur. Id altera imperdiet nec. Noster audiam accusamus mei at, no zril libris nemore + duo, ius ne rebum doctus fuisset. Legimus epicurei in sit, esse purto suscipit eu qui, oporteat deserunt + delicatissimi sea in. Est id putent accusata convenire, no tibique molestie accommodare quo, cu est fuisset + offendit evertitur. + `; + + it('is uncollapsible if the commit message is short', function() { + sinon.stub(commit, 'getMessageBody').returns('short'); + const wrapper = shallow(buildApp()); + const view = wrapper.find('CommitDetailView'); + assert.isFalse(view.prop('messageCollapsible')); + assert.isTrue(view.prop('messageOpen')); }); - const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); - sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); - const wrapper = mount(buildApp({commit: newCommit})); - - assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'messageBody'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); - /* TODO fix href test */ - // assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); - assert.strictEqual(wrapper.find('img.github-RecentCommit-avatar').prop('src'), 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32'); - }); - it('renders multiple avatars for co-authored commit', function() { - const newCommit = new Commit({ - sha: '420', - authorEmail: 'very@nice.com', - authorDate: moment().subtract(2, 'days').unix(), - messageSubject: 'subject', - messageBody: 'messageBody', - coAuthors: [{name: 'two', email: 'two@coauthor.com'}, {name: 'three', email: 'three@coauthor.com'}], + it('is collapsible and begins collapsed if the commit message is long', function() { + sinon.stub(commit, 'getMessageBody').returns(LONG_MESSAGE); + + const wrapper = shallow(buildApp()); + const view = wrapper.find('CommitDetailView'); + assert.isTrue(view.prop('messageCollapsible')); + assert.isFalse(view.prop('messageOpen')); }); - const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); - sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); - const wrapper = mount(buildApp({commit: newCommit})); - assert.deepEqual( - wrapper.find('img.github-RecentCommit-avatar').map(w => w.prop('src')), - [ - 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32', - 'https://avatars.githubusercontent.com/u/e?email=two%40coauthor.com&s=32', - 'https://avatars.githubusercontent.com/u/e?email=three%40coauthor.com&s=32', - ], - ); - }); + it('toggles collapsed state', async function() { + sinon.stub(commit, 'getMessageBody').returns(LONG_MESSAGE); + + const wrapper = shallow(buildApp()); + assert.isFalse(wrapper.find('CommitDetailView').prop('messageOpen')); + + await wrapper.find('CommitDetailView').prop('toggleMessage')(); + + assert.isTrue(wrapper.find('CommitDetailView').prop('messageOpen')); + }); + }); }); diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js new file mode 100644 index 0000000000..731a5b7118 --- /dev/null +++ b/test/views/commit-detail-view.test.js @@ -0,0 +1,69 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +describe('CommitDetailView', function() { + it('has a MultiFilePatchController that its itemType set'); + + it('passes unrecognized props to a MultiFilePatchController'); + + it('renders commit details properly'); + + it('renders multiple avatars for co-authored commit'); +}); + +/* +it('has a MultiFilePatchController that has `disableStageUnstage` flag set to true', function() { + const wrapper = mount(buildApp()); + assert.isTrue(wrapper.find('MultiFilePatchController').exists()); + assert.isTrue(wrapper.find('MultiFilePatchController').prop('disableStageUnstage')); +}); + +it('passes unrecognized props to a MultiFilePatchController', function() { + const extra = Symbol('extra'); + const wrapper = shallow(buildApp({extra})); + + assert.strictEqual(wrapper.find('MultiFilePatchController').prop('extra'), extra); +}); + +it('renders commit details properly', function() { + const newCommit = new Commit({ + sha: '420', + authorEmail: 'very@nice.com', + authorDate: moment().subtract(2, 'days').unix(), + messageSubject: 'subject', + messageBody: 'messageBody', + }); + const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); + sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); + const wrapper = mount(buildApp({commit: newCommit})); + + assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'messageBody'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); + // assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); + assert.strictEqual(wrapper.find('img.github-RecentCommit-avatar').prop('src'), 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32'); +}); + +it('renders multiple avatars for co-authored commit', function() { + const newCommit = new Commit({ + sha: '420', + authorEmail: 'very@nice.com', + authorDate: moment().subtract(2, 'days').unix(), + messageSubject: 'subject', + messageBody: 'messageBody', + coAuthors: [{name: 'two', email: 'two@coauthor.com'}, {name: 'three', email: 'three@coauthor.com'}], + }); + const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); + sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); + const wrapper = mount(buildApp({commit: newCommit})); + assert.deepEqual( + wrapper.find('img.github-RecentCommit-avatar').map(w => w.prop('src')), + [ + 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32', + 'https://avatars.githubusercontent.com/u/e?email=two%40coauthor.com&s=32', + 'https://avatars.githubusercontent.com/u/e?email=three%40coauthor.com&s=32', + ], + ); +}); +*/ From 23ed35b724d7b460acc2cde7d6d3e40d2fd13ec2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 14:36:30 -0500 Subject: [PATCH 1221/4053] Builder for creating test Commits --- test/builder/commit.js | 54 ++++++++++++++++++++++++++++++++++++++ test/models/commit.test.js | 22 +++++----------- 2 files changed, 60 insertions(+), 16 deletions(-) create mode 100644 test/builder/commit.js diff --git a/test/builder/commit.js b/test/builder/commit.js new file mode 100644 index 0000000000..3d4dfef206 --- /dev/null +++ b/test/builder/commit.js @@ -0,0 +1,54 @@ +import moment from 'moment'; + +import Commit from '../../lib/models/commit'; + +class CommitBuilder { + constructor() { + this._sha = '0123456789abcdefghij0123456789abcdefghij'; + this._authorEmail = 'default@email.com'; + this._authorDate = moment('2018-11-28T12:00:00', moment.ISO_8601).unix(); + this._coAuthors = []; + this._messageSubject = 'subject'; + this._messageBody = 'body'; + } + + sha(newSha) { + this._sha = newSha; + return this; + } + + authorEmail(newEmail) { + this._authorEmail = newEmail; + return this; + } + + authorDate(timestamp) { + this._authorDate = timestamp; + return this; + } + + messageSubject(subject) { + this._messageSubject = subject; + return this; + } + + messageBody(body) { + this._messageBody = body; + return this; + } + + build() { + return new Commit({ + sha: this._sha, + authorEmail: this._authorEmail, + authorDate: this._authorDate, + coAuthors: this._coAuthors, + messageSubject: this._messageSubject, + messageBody: this._messageBody, + }); + } +} + +export function commitBuilder() { + return new CommitBuilder(); +} diff --git a/test/models/commit.test.js b/test/models/commit.test.js index a8cd468a4c..1adb497f10 100644 --- a/test/models/commit.test.js +++ b/test/models/commit.test.js @@ -1,24 +1,13 @@ -import moment from 'moment'; import dedent from 'dedent-js'; -import Commit, {nullCommit} from '../../lib/models/commit'; +import {nullCommit} from '../../lib/models/commit'; +import {commitBuilder} from '../builder/commit'; describe('Commit', function() { - function buildCommit(override = {}) { - return new Commit({ - sha: '0123456789abcdefghij0123456789abcdefghij', - authorEmail: 'me@email.com', - coAuthors: [], - authorDate: moment('2018-11-28T12:00:00', moment.ISO_8601).unix(), - messageSubject: 'subject', - messageBody: 'body', - ...override, - }); - } - describe('isBodyLong()', function() { it('returns false if the commit message body is short', function() { - assert.isFalse(buildCommit({messageBody: 'short'}).isBodyLong()); + const commit = commitBuilder().messageBody('short').build(); + assert.isFalse(commit.isBodyLong()); }); it('returns true if the commit message body is long', function() { @@ -41,7 +30,8 @@ describe('Commit', function() { delicatissimi sea in. Est id putent accusata convenire, no tibique molestie accommodare quo, cu est fuisset offendit evertitur. `; - assert.isTrue(buildCommit({messageBody}).isBodyLong()); + const commit = commitBuilder().messageBody(messageBody).build(); + assert.isTrue(commit.isBodyLong()); }); it('returns false for a null commit', function() { From d1237527de6eb258cb2bb0710d9807fb176b340b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 14:56:15 -0500 Subject: [PATCH 1222/4053] setMultiFileDiff() to construct a Commit's diff --- test/builder/commit.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/builder/commit.js b/test/builder/commit.js index 3d4dfef206..a68ada437b 100644 --- a/test/builder/commit.js +++ b/test/builder/commit.js @@ -1,6 +1,7 @@ import moment from 'moment'; import Commit from '../../lib/models/commit'; +import {multiFilePatchBuilder} from './patch'; class CommitBuilder { constructor() { @@ -10,6 +11,8 @@ class CommitBuilder { this._coAuthors = []; this._messageSubject = 'subject'; this._messageBody = 'body'; + + this._multiFileDiff = null; } sha(newSha) { @@ -37,8 +40,14 @@ class CommitBuilder { return this; } + setMultiFileDiff(block = () => {}) { + const builder = multiFilePatchBuilder(); + block(builder); + this._multiFileDiff = builder.build().multiFilePatch; + return this; + } build() { - return new Commit({ + const commit = new Commit({ sha: this._sha, authorEmail: this._authorEmail, authorDate: this._authorDate, @@ -46,6 +55,12 @@ class CommitBuilder { messageSubject: this._messageSubject, messageBody: this._messageBody, }); + + if (this._multiFileDiff !== null) { + commit.setMultiFileDiff(this._multiFileDiff); + } + + return commit; } } From a22b69233e5e10e6fc8724af2ee52cdeed8379ed Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 14:56:28 -0500 Subject: [PATCH 1223/4053] CoAuthor construction --- test/builder/commit.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/builder/commit.js b/test/builder/commit.js index a68ada437b..d9dd960bb7 100644 --- a/test/builder/commit.js +++ b/test/builder/commit.js @@ -46,6 +46,12 @@ class CommitBuilder { this._multiFileDiff = builder.build().multiFilePatch; return this; } + + addCoAuthor(name, email) { + this._coAuthors.push({name, email}); + return this; + } + build() { const commit = new Commit({ sha: this._sha, From 942e7dccda77c95aed4e1a08713e292eaf8c5ec1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 14:56:41 -0500 Subject: [PATCH 1224/4053] PropTypes shuffle :dancer: --- lib/views/commit-detail-view.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index 96b38f1ec6..ec0416a57b 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -4,10 +4,13 @@ import {emojify} from 'node-emoji'; import moment from 'moment'; import MultiFilePatchController from '../controllers/multi-file-patch-controller'; +import CommitDetailItem from '../items/commit-detail-item'; export default class CommitDetailView extends React.Component { static propTypes = { repository: PropTypes.object.isRequired, + commit: PropTypes.object.isRequired, + itemType: PropTypes.oneOf([CommitDetailItem]).isRequired, workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, @@ -16,7 +19,6 @@ export default class CommitDetailView extends React.Component { config: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, - commit: PropTypes.object.isRequired, } render() { From 1a28b7d3a7b3a62615690b605dc8ad54367e1a0d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 14:56:59 -0500 Subject: [PATCH 1225/4053] Ported CommitDetailView tests, all passing --- test/views/commit-detail-view.test.js | 127 +++++++++++++++----------- 1 file changed, 74 insertions(+), 53 deletions(-) diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js index 731a5b7118..0dfc18ef13 100644 --- a/test/views/commit-detail-view.test.js +++ b/test/views/commit-detail-view.test.js @@ -1,69 +1,90 @@ import React from 'react'; import {shallow} from 'enzyme'; +import moment from 'moment'; + +import CommitDetailView from '../../lib/views/commit-detail-view'; +import CommitDetailItem from '../../lib/items/commit-detail-item'; +import {cloneRepository, buildRepository} from '../helpers'; +import {commitBuilder} from '../builder/commit'; describe('CommitDetailView', function() { - it('has a MultiFilePatchController that its itemType set'); + let repository, atomEnv; - it('passes unrecognized props to a MultiFilePatchController'); + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + repository = await buildRepository(await cloneRepository('multiple-commits')); + }); - it('renders commit details properly'); + afterEach(function() { + atomEnv.destroy(); + }); - it('renders multiple avatars for co-authored commit'); -}); + function buildApp(override = {}) { + const props = { + repository, + commit: commitBuilder().build(), + itemType: CommitDetailItem, -/* -it('has a MultiFilePatchController that has `disableStageUnstage` flag set to true', function() { - const wrapper = mount(buildApp()); - assert.isTrue(wrapper.find('MultiFilePatchController').exists()); - assert.isTrue(wrapper.find('MultiFilePatchController').prop('disableStageUnstage')); -}); + workspace: atomEnv.workspace, + commands: atomEnv.commands, + keymaps: atomEnv.keymaps, + tooltips: atomEnv.tooltips, + config: atomEnv.config, -it('passes unrecognized props to a MultiFilePatchController', function() { - const extra = Symbol('extra'); - const wrapper = shallow(buildApp({extra})); + destroy: () => {}, + ...override, + }; - assert.strictEqual(wrapper.find('MultiFilePatchController').prop('extra'), extra); -}); + return ; + } -it('renders commit details properly', function() { - const newCommit = new Commit({ - sha: '420', - authorEmail: 'very@nice.com', - authorDate: moment().subtract(2, 'days').unix(), - messageSubject: 'subject', - messageBody: 'messageBody', + it('has a MultiFilePatchController that its itemType set', function() { + const wrapper = shallow(buildApp({itemType: CommitDetailItem})); + assert.strictEqual(wrapper.find('MultiFilePatchController').prop('itemType'), CommitDetailItem); }); - const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); - sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); - const wrapper = mount(buildApp({commit: newCommit})); - assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'messageBody'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); - // assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); - assert.strictEqual(wrapper.find('img.github-RecentCommit-avatar').prop('src'), 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32'); -}); - -it('renders multiple avatars for co-authored commit', function() { - const newCommit = new Commit({ - sha: '420', - authorEmail: 'very@nice.com', - authorDate: moment().subtract(2, 'days').unix(), - messageSubject: 'subject', - messageBody: 'messageBody', - coAuthors: [{name: 'two', email: 'two@coauthor.com'}, {name: 'three', email: 'three@coauthor.com'}], + it('passes unrecognized props to a MultiFilePatchController', function() { + const extra = Symbol('extra'); + const wrapper = shallow(buildApp({extra})); + assert.strictEqual(wrapper.find('MultiFilePatchController').prop('extra'), extra); }); - const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); - sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); - const wrapper = mount(buildApp({commit: newCommit})); - assert.deepEqual( - wrapper.find('img.github-RecentCommit-avatar').map(w => w.prop('src')), - [ + + it('renders commit details properly', function() { + const commit = commitBuilder() + .sha('420') + .authorEmail('very@nice.com') + .authorDate(moment().subtract(2, 'days').unix()) + .messageSubject('subject') + .messageBody('body') + .setMultiFileDiff() + .build(); + const wrapper = shallow(buildApp({commit})); + + assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'body'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); + // assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); + assert.strictEqual( + wrapper.find('img.github-RecentCommit-avatar').prop('src'), 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32', - 'https://avatars.githubusercontent.com/u/e?email=two%40coauthor.com&s=32', - 'https://avatars.githubusercontent.com/u/e?email=three%40coauthor.com&s=32', - ], - ); + ); + }); + + it('renders multiple avatars for co-authored commit', function() { + const commit = commitBuilder() + .authorEmail('blaze@it.com') + .addCoAuthor('two', 'two@coauthor.com') + .addCoAuthor('three', 'three@coauthor.com') + .build(); + const wrapper = shallow(buildApp({commit})); + assert.deepEqual( + wrapper.find('img.github-RecentCommit-avatar').map(w => w.prop('src')), + [ + 'https://avatars.githubusercontent.com/u/e?email=blaze%40it.com&s=32', + 'https://avatars.githubusercontent.com/u/e?email=two%40coauthor.com&s=32', + 'https://avatars.githubusercontent.com/u/e?email=three%40coauthor.com&s=32', + ], + ); + }); }); -*/ From 4d8737cbc321cddefddecd98ed34e6e8304613fa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 15:48:33 -0500 Subject: [PATCH 1226/4053] Failing tests and initial implementation of abbreviatedBody() --- lib/models/commit.js | 39 +++++++++++++ test/models/commit.test.js | 112 +++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) diff --git a/lib/models/commit.js b/lib/models/commit.js index c568de8066..fb6433ace5 100644 --- a/lib/models/commit.js +++ b/lib/models/commit.js @@ -3,6 +3,8 @@ const UNBORN = Symbol('unborn'); export default class Commit { static LONG_MESSAGE_THRESHOLD = 1000; + static BOUNDARY_SEARCH_THRESHOLD = 100; + static createUnborn() { return new Commit({unbornRef: UNBORN}); } @@ -50,6 +52,43 @@ export default class Commit { return `${this.getMessageSubject()}\n\n${this.getMessageBody()}`.trim(); } + /* + * Return the messageBody, truncated before the character at LONG_MESSAGE_THRESHOLD. If a paragraph boundary is + * found within BOUNDARY_SEARCH_THRESHOLD characters before that position, the message will be truncated at the + * end of the previous paragraph. If there is no paragraph boundary found, but a word boundary is found within + * that range, the text is truncated at that word boundary and an elipsis (...) is added. If neither are found, + * the text is truncated hard at LONG_MESSAGE_THRESHOLD - 3 characters and an elipsis (...) is added. + */ + abbreviatedBody() { + if (!this.isBodyLong()) { + return this.getMessageBody(); + } + + const {LONG_MESSAGE_THRESHOLD, BOUNDARY_SEARCH_THRESHOLD} = this.constructor; + let elipsis = '...'; + let lastParagraphIndex = Infinity; + let lastWordIndex = Infinity; + + const boundarySearch = this.getMessageBody() + .substring(LONG_MESSAGE_THRESHOLD - BOUNDARY_SEARCH_THRESHOLD, LONG_MESSAGE_THRESHOLD); + + const boundaryRx = /\r?\n\r?\n|\s+/g; + let result; + while ((result = boundaryRx.exec(boundarySearch)) !== null) { + if (/\r?\n\r?\n/.test(result[0])) { + // Paragraph boundary. Omit the elipsis + lastParagraphIndex = result.index; + elipsis = ''; + } else if (result.index <= BOUNDARY_SEARCH_THRESHOLD - elipsis.length) { + // Word boundary. Only count if we have room for the elipsis under the cutoff. + lastWordIndex = result.index; + } + } + + const cutoffIndex = Math.min(lastParagraphIndex, lastWordIndex); + return this.getMessageBody().substring(0, cutoffIndex) + elipsis; + } + setMultiFileDiff(multiFileDiff) { this.multiFileDiff = multiFileDiff; } diff --git a/test/models/commit.test.js b/test/models/commit.test.js index 1adb497f10..9e3e7664ea 100644 --- a/test/models/commit.test.js +++ b/test/models/commit.test.js @@ -38,4 +38,116 @@ describe('Commit', function() { assert.isFalse(nullCommit.isBodyLong()); }); }); + + describe('abbreviatedBody()', function() { + it('returns the message body as-is when the body is short', function() { + const commit = commitBuilder().messageBody('short').build(); + assert.strictEqual(commit.abbreviatedBody(), 'short'); + }); + + it('truncates the message body at the nearest paragraph boundary before the cutoff if one is nearby', function() { + // The | is at the 1000-character mark. + const body = dedent` + Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. Mazim alterum sea ea, + essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam + tantas nullam corrumpit ad, in oratio luptatum eleifend vim. + + Ea salutatus contentiones eos. Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere + urbanitas, usu ut aperiri mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, + scripta iudicabit ne nam, in duis clita commodo sit. + + Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat comprehensam ut his, et eum + voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei liber putant. Ad doctus + tractatos ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent philosophia et vix. Nusquam + reprehendunt et mea. Ea eius omnes voluptua sit. + + No cum illud verear efficiantur. Id altera imperdiet nec. Noster aud|iam accusamus mei at, no zril libris nemore + duo, ius ne rebum doctus fuisset. Legimus epicurei in sit, esse purto suscipit eu qui, oporteat deserunt + delicatissimi sea in. Est id putent accusata convenire, no tibique molestie accommodare quo, cu est fuisset + offendit evertitur. + `; + + const commit = commitBuilder().messageBody(body).build(); + assert.strictEqual(commit.abbreviatedBody(), dedent` + Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. Mazim alterum sea ea, + essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam + tantas nullam corrumpit ad, in oratio luptatum eleifend vim. + + Ea salutatus contentiones eos. Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere + urbanitas, usu ut aperiri mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, + scripta iudicabit ne nam, in duis clita commodo sit. + + Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat comprehensam ut his, et eum + voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei liber putant. Ad doctus + tractatos ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent philosophia et vix. Nusquam + reprehendunt et mea. Ea eius omnes voluptua sit. + `); + }); + + it('truncates the message body at the nearest word boundary before the cutoff if one is nearby', function() { + // The | is at the 1000-character mark. + const body = dedent` + Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. + + Mazim alterum sea ea, essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore + albucius te vis, eam tantas nullam corrumpit ad, in oratio luptatum eleifend vim. Ea salutatus contentiones eos. + Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere urbanitas, usu ut aperiri + mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, scripta iudicabit ne nam, in + duis clita commodo sit. Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat + comprehensam ut his, et eum voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei + liber putant. Ad doctus tractatos ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent + philosophia et vix. Nusquam reprehendunt et mea. Ea eius omnes voluptua sit. No cum illud verear efficiantur. Id + altera imperdiet nec. Noster audia|m accusamus mei at, no zril libris nemore duo, ius ne rebum doctus fuisset. + Legimus epicurei in sit, esse purto suscipit eu qui, oporteat deserunt delicatissimi sea in. Est id putent + accusata convenire, no tibique molestie accommodare quo, cu est fuisset offendit evertitur. + `; + + const commit = commitBuilder().messageBody(body).build(); + assert.strictEqual(commit.abbreviatedBody(), dedent` + Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. + + Mazim alterum sea ea, essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore + albucius te vis, eam tantas nullam corrumpit ad, in oratio luptatum eleifend vim. Ea salutatus contentiones eos. + Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere urbanitas, usu ut aperiri + mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, scripta iudicabit ne nam, in + duis clita commodo sit. Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat + comprehensam ut his, et eum voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei + liber putant. Ad doctus tractatos ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent + philosophia et vix. Nusquam reprehendunt et mea. Ea eius omnes voluptua sit. No cum illud verear efficiantur. Id + altera imperdiet nec. Noster... + `); + }); + + it('truncates the message body at the character cutoff if no word or paragraph boundaries can be found', function() { + // The | is at the 1000-character mark. + const body = 'Loremipsumdolorsitamet,ethisjustodeleniti,omniumfastidiiadversariumathas.\n\n' + + 'Mazim alterumseaea,essentmalorumpersiusnemei.Nameatemporqualisque,modusdomingtehas.Affertdolore' + + 'albuciustevis,eamtantasnullamcorrumpitad,inoratioluptatumeleifendvim.Easalutatuscontentioneseos.' + + 'Eaminveniamfacetevolutpat,solumappetereadversariumutquo.Velcuappetereurbanitas,usuutaperiri' + + 'mediocritatem,aliamolestieurbanitascuqui.Velitantiopamerroribusnoeum,scriptaiudicabitnenam,in' + + 'duisclitacommodosit.Assumsensibusoporteretevel,vissemperevertiturdefiniebasin.Tamquamfeugiat' + + 'comprehensamuthis,eteumvoluptuaullamcorper,exmeidebitisinciderint.Sitdiscerepertinaxte,anmei' + + 'liberputant.Addoctustractatosius,duoadcivibusalienum,nominativoluptariasedan.Librisessent' + + 'philosophiaetvix.Nusquamreprehenduntetmea.Eaeiusomnesvoluptuasit.Nocumilludverearefficiantur.Id' + + 'alteraimperdietnec.Nosteraudiamaccusamusmeiat,nozrillibrisnemoreduo,iusnerebumdoctusfuisset.' + + 'Legimusepicureiinsit,essepurtosuscipiteuqui,oporteatdeseruntdelicatissimiseain.Estidputent' + + '|accusataconvenire,notibiquemolestieaccommodarequo,cuestfuissetoffenditevertitur.'; + + const commit = commitBuilder().messageBody(body).build(); + assert.strictEqual( + commit.abbreviatedBody(), + 'Loremipsumdolorsitamet,ethisjustodeleniti,omniumfastidiiadversariumathas.\n\n' + + 'Mazim alterumseaea,essentmalorumpersiusnemei.Nameatemporqualisque,modusdomingtehas.Affertdolore' + + 'albuciustevis,eamtantasnullamcorrumpitad,inoratioluptatumeleifendvim.Easalutatuscontentioneseos.' + + 'Eaminveniamfacetevolutpat,solumappetereadversariumutquo.Velcuappetereurbanitas,usuutaperiri' + + 'mediocritatem,aliamolestieurbanitascuqui.Velitantiopamerroribusnoeum,scriptaiudicabitnenam,in' + + 'duisclitacommodosit.Assumsensibusoporteretevel,vissemperevertiturdefiniebasin.Tamquamfeugiat' + + 'comprehensamuthis,eteumvoluptuaullamcorper,exmeidebitisinciderint.Sitdiscerepertinaxte,anmei' + + 'liberputant.Addoctustractatosius,duoadcivibusalienum,nominativoluptariasedan.Librisessent' + + 'philosophiaetvix.Nusquamreprehenduntetmea.Eaeiusomnesvoluptuasit.Nocumilludverearefficiantur.Id' + + 'alteraimperdietnec.Nosteraudiamaccusamusmeiat,nozrillibrisnemoreduo,iusnerebumdoctusfuisset.' + + 'Legimusepicureiinsit,essepurtosuscipiteuqui,oporteatdeseruntdelicatissimiseain.Estidput...', + ); + }); + }); }); From 4ceba4b4ca61ae6e4da2218b9cb00e0bd0fc5d62 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 15:52:06 -0500 Subject: [PATCH 1227/4053] Pending tests for collapsing and uncollapsing message bodies --- test/views/commit-detail-view.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js index 0dfc18ef13..357505912e 100644 --- a/test/views/commit-detail-view.test.js +++ b/test/views/commit-detail-view.test.js @@ -23,6 +23,8 @@ describe('CommitDetailView', function() { const props = { repository, commit: commitBuilder().build(), + messageCollapsible: false, + messageOpen: true, itemType: CommitDetailItem, workspace: atomEnv.workspace, @@ -87,4 +89,14 @@ describe('CommitDetailView', function() { ], ); }); + + describe('commit message collapsibility', function() { + it('renders the full message when messageCollapsible is false'); + + it('renders an abbreviated message when messageCollapsible is true and messageOpen is false'); + + it('renders the full message when messageCollapsible is true and messageOpen is true'); + + it('calls toggleMessage the "See More" or "See Less" buttons are clicked'); + }); }); From 38b0fa9e40b40e6f9a8c1034c1d7f5b6e700e0e3 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 28 Nov 2018 13:11:40 -0800 Subject: [PATCH 1228/4053] :fire: autoHeight as a prop since it's always false --- lib/controllers/commit-preview-controller.js | 1 - lib/controllers/multi-file-patch-controller.js | 1 - lib/views/multi-file-patch-view.js | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/controllers/commit-preview-controller.js b/lib/controllers/commit-preview-controller.js index ff5f3cf72b..f1ce3c988c 100644 --- a/lib/controllers/commit-preview-controller.js +++ b/lib/controllers/commit-preview-controller.js @@ -23,7 +23,6 @@ export default class CommitPreviewController extends React.Component { return ( ); diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 667184aae4..cdba88def6 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -25,7 +25,6 @@ export default class MultiFilePatchController extends React.Component { discardLines: PropTypes.func, undoLastDiscard: PropTypes.func, surface: PropTypes.func, - autoHeight: PropTypes.bool, } constructor(props) { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 5e3d3d73bf..5e35e5033b 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -58,7 +58,6 @@ export default class MultiFilePatchView extends React.Component { toggleSymlinkChange: PropTypes.func, undoLastDiscard: PropTypes.func, discardRows: PropTypes.func, - autoHeight: PropTypes.bool, refInitialFocus: RefHolderPropType, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, } @@ -241,7 +240,7 @@ export default class MultiFilePatchView extends React.Component { buffer={this.props.multiFilePatch.getBuffer()} lineNumberGutterVisible={false} autoWidth={false} - autoHeight={this.props.autoHeight} + autoHeight={false} readOnly={true} softWrapped={true} From 205c43754631120585824ac433e8fbd844469156 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 28 Nov 2018 14:42:02 -0800 Subject: [PATCH 1229/4053] collapse and uncollapse commit message bodies --- lib/views/commit-detail-view.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index ec0416a57b..dafdcb1123 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -19,6 +19,10 @@ export default class CommitDetailView extends React.Component { config: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, + + messageCollapsible: PropTypes.bool.isRequired, + messageOpen: PropTypes.bool.isRequired, + toggleMessage: PropTypes.func.isRequired, } render() { @@ -33,9 +37,9 @@ export default class CommitDetailView extends React.Component {

    {emojify(commit.getMessageSubject())} + {this.renderShowMoreButton()}

    -
    -                {emojify(commit.getMessageBody())}
    + {this.renderCommitMessageBody(commit)}
    {/* TODO fix image src */} {this.renderAuthors()} @@ -62,6 +66,25 @@ export default class CommitDetailView extends React.Component { ); } + renderCommitMessageBody(commit) { + if (this.props.messageOpen || !this.props.messageCollapsible) { + return ( +
    +          {emojify(commit.getMessageBody())}
    + ); + } + } + + renderShowMoreButton() { + if (!this.props.messageCollapsible) { + return null; + } + const buttonText = this.props.messageOpen ? 'Hide More' : 'Show More'; + return ( + + ); + } + humanizeTimeSince(date) { return moment(date * 1000).fromNow(); } From ec10f482e22823995def2968eb11d6451c5a16d4 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 28 Nov 2018 14:45:53 -0800 Subject: [PATCH 1230/4053] return null if we're not gonna render the commit message body --- lib/views/commit-detail-view.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index dafdcb1123..11a3981f99 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -72,6 +72,8 @@ export default class CommitDetailView extends React.Component {
               {emojify(commit.getMessageBody())}
    ); + } else { + return null; } } From e1102d07bd0cdc2f5b31ca4242edd3fea453e659 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 28 Nov 2018 14:56:19 -0800 Subject: [PATCH 1231/4053] style the button --- lib/views/commit-detail-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index 11a3981f99..b965f70eb2 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -83,7 +83,7 @@ export default class CommitDetailView extends React.Component { } const buttonText = this.props.messageOpen ? 'Hide More' : 'Show More'; return ( - + ); } From 1af8a5d119256a46e4822acece992a7b6f0153fb Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 28 Nov 2018 16:51:35 -0800 Subject: [PATCH 1232/4053] :fire: some dead code --- lib/views/commit-detail-view.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index b965f70eb2..125be8778b 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -27,8 +27,6 @@ export default class CommitDetailView extends React.Component { render() { const commit = this.props.commit; - // const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; - // const {avatarUrl, name, date} = this.props.item.committer; return (
    From 6a3d65cc7fe8584e5747ec5f826f8b75da6b153b Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 28 Nov 2018 16:53:56 -0800 Subject: [PATCH 1233/4053] unit tests for commit message collapsibility --- test/views/commit-detail-view.test.js | 63 +++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js index 357505912e..ee39d83e40 100644 --- a/test/views/commit-detail-view.test.js +++ b/test/views/commit-detail-view.test.js @@ -1,6 +1,7 @@ import React from 'react'; import {shallow} from 'enzyme'; import moment from 'moment'; +import dedent from 'dedent-js'; import CommitDetailView from '../../lib/views/commit-detail-view'; import CommitDetailItem from '../../lib/items/commit-detail-item'; @@ -34,6 +35,7 @@ describe('CommitDetailView', function() { config: atomEnv.config, destroy: () => {}, + toggleMessage: () => {}, ...override, }; @@ -91,12 +93,65 @@ describe('CommitDetailView', function() { }); describe('commit message collapsibility', function() { - it('renders the full message when messageCollapsible is false'); + let wrapper; + const commitMessageBody = dedent` + if every pork chop was perfect... - it('renders an abbreviated message when messageCollapsible is true and messageOpen is false'); - it('renders the full message when messageCollapsible is true and messageOpen is true'); - it('calls toggleMessage the "See More" or "See Less" buttons are clicked'); + we wouldn't have hot dogs! + 🌭🌭🌭🌭🌭🌭🌭 + `; + const commit = commitBuilder() + .authorEmail('greg@mruniverse.biz') + .messageBody(commitMessageBody) + .build(); + describe('when messageCollapsible is false', function() { + beforeEach(function() { + wrapper = shallow(buildApp({commit, messageCollapsible: false})); + }); + it('renders the full message body', function() { + assert.deepEqual(wrapper.find('.github-CommitDetailView-moreText').text(), commitMessageBody); + }); + it('does not render a button', function() { + + }); + }); + describe('when messageCollapsible is true and messageOpen is false', function() { + beforeEach(function() { + wrapper = shallow(buildApp({commit, messageCollapsible: true, messageOpen: false})); + }); + it('does not render commit message', function() { + assert.lengthOf(wrapper.find('.github-CommitDetailView-moreText'), 0); + }); + + it('renders button with the text `Show More`', function() { + const button = wrapper.find('.github-CommitDetailView-moreButton'); + assert.lengthOf(button, 1); + assert.deepEqual(button.text(), 'Show More'); + }); + }); + + describe('when messageCollapsible is true and messageOpen is true', function() { + let toggleMessage; + beforeEach(function() { + toggleMessage = sinon.spy(); + wrapper = shallow(buildApp({commit, messageCollapsible: true, messageOpen: true, toggleMessage})); + }); + it('renders the full message', function() { + assert.deepEqual(wrapper.find('.github-CommitDetailView-moreText').text(), commitMessageBody); + }); + it('renders a button with the text `Hide More`', function() { + const button = wrapper.find('.github-CommitDetailView-moreButton'); + assert.lengthOf(button, 1); + assert.deepEqual(button.text(), 'Hide More'); + }); + it('button calls `toggleMessage` prop when clicked', function() { + const button = wrapper.find('.github-CommitDetailView-moreButton'); + button.simulate('click'); + assert.ok(toggleMessage.called); + }); + + }); }); }); From 8e7561f6808e1ffd9f4584524d5a641e836dbb98 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 28 Nov 2018 16:57:28 -0800 Subject: [PATCH 1234/4053] :fire: unnecessary console logging --- lib/views/pr-commit-view.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/views/pr-commit-view.js b/lib/views/pr-commit-view.js index 1d31e5947d..88d879a3cb 100644 --- a/lib/views/pr-commit-view.js +++ b/lib/views/pr-commit-view.js @@ -38,7 +38,6 @@ export class PrCommitView extends React.Component { } render() { - console.log('zzz'); const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; const {avatarUrl, name, date} = this.props.item.committer; return ( From 414390db1fe765fb12c2fdb3bb1547f3ddeae434 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 28 Nov 2018 17:16:51 -0800 Subject: [PATCH 1235/4053] don't render staging/unstaging buttons for mode and symlink changes --- lib/views/file-patch-meta-view.js | 28 +++++++++++++++++++++------- lib/views/multi-file-patch-view.js | 5 +++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/views/file-patch-meta-view.js b/lib/views/file-patch-meta-view.js index bbefd913f6..fadc45edd8 100644 --- a/lib/views/file-patch-meta-view.js +++ b/lib/views/file-patch-meta-view.js @@ -1,6 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; +import CommitDetailItem from '../items/commit-detail-item'; +import ChangedFileItem from '../items/changed-file-item'; +import CommitPreviewItem from '../items/commit-preview-item'; export default class FilePatchMetaView extends React.Component { static propTypes = { @@ -11,21 +14,32 @@ export default class FilePatchMetaView extends React.Component { action: PropTypes.func.isRequired, children: PropTypes.element.isRequired, + itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, }; + renderMetaControls() { + console.log(this.props); + if (this.props.itemType === CommitDetailItem) { + return null; + } + return ( +
    + +
    + ); + } + render() { return (

    {this.props.title}

    -
    - -
    + {this.renderMetaControls()}
    {this.props.children} diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 5e35e5033b..61414c30a3 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -361,6 +361,7 @@ export default class MultiFilePatchView extends React.Component { title="Mode change" actionIcon={attrs.actionIcon} actionText={attrs.actionText} + itemType={this.props.itemType} action={() => this.props.toggleModeChange(filePatch)}> File changed mode @@ -444,6 +445,7 @@ export default class MultiFilePatchView extends React.Component { title={title} actionIcon={attrs.actionIcon} actionText={attrs.actionText} + itemType={this.props.itemType} action={() => this.props.toggleSymlinkChange(filePatch)}> {detail} @@ -453,6 +455,9 @@ export default class MultiFilePatchView extends React.Component { } renderHunkHeaders(filePatch) { + if (this.props.itemType === CommitDetailItem) { + return null; + } const toggleVerb = this.props.stagingStatus === 'unstaged' ? 'Stage' : 'Unstage'; const selectedHunks = new Set( Array.from(this.props.selectedRows, row => this.props.multiFilePatch.getHunkAt(row)), From 1467db2a5af9ece794d6d67767c8b8adfb1c6aab Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 29 Nov 2018 11:24:29 +0900 Subject: [PATCH 1236/4053] Wrap whitespace in commit body --- styles/commit-detail.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/commit-detail.less b/styles/commit-detail.less index 6f8c24f975..b3c3ef8c00 100644 --- a/styles/commit-detail.less +++ b/styles/commit-detail.less @@ -69,7 +69,7 @@ font-family: var(--editor-font-family); word-wrap: initial; word-break: break-word; - white-space: initial; + white-space: pre-wrap; background-color: transparent; &:empty { display: none; From e1de0771e7c6d9c267381d5f1cc27e9d884bbf32 Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 29 Nov 2018 12:36:04 +0900 Subject: [PATCH 1237/4053] Move sha into CommitDetailView-meta --- lib/views/commit-detail-view.js | 38 ++++++++++++++++----------------- styles/commit-detail.less | 21 +++++++++--------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index 125be8778b..a3b924a3ad 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -31,28 +31,26 @@ export default class CommitDetailView extends React.Component { return (
    -
    -
    -

    - {emojify(commit.getMessageSubject())} - {this.renderShowMoreButton()} -

    - {this.renderCommitMessageBody(commit)} -
    - {/* TODO fix image src */} - {this.renderAuthors()} - - {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} - +
    +

    + {emojify(commit.getMessageSubject())} + {this.renderShowMoreButton()} +

    + {this.renderCommitMessageBody(commit)} +
    + {/* TODO fix image src */} + {this.renderAuthors()} + + {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} + +
    + {/* TODO fix href */} + + {commit.getSha()} +
    -
    - {/* TODO fix href */} - - {commit.getSha()} - -
    Date: Thu, 29 Nov 2018 14:10:34 +0900 Subject: [PATCH 1238/4053] Restyle header --- styles/commit-detail.less | 7 ++----- styles/file-patch-view.less | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/styles/commit-detail.less b/styles/commit-detail.less index bc5cddb509..e6cab0bf14 100644 --- a/styles/commit-detail.less +++ b/styles/commit-detail.less @@ -10,15 +10,12 @@ &-header { flex: 0; - padding: @default-padding; - padding-bottom: 0; + border-bottom: 1px solid @base-border-color; background-color: @syntax-background-color; } &-commit { - padding: @default-padding; - border: 1px solid @base-border-color; - border-radius: @component-border-radius; + padding: @default-padding*2; } &-title { diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index fcf9a7cb48..830fadc1e4 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -24,7 +24,7 @@ } .github-FilePatchView-controlBlock { - padding: @component-padding*2 @component-padding @component-padding 0; + padding: @component-padding*4 @component-padding @component-padding 0; background-color: @syntax-background-color; & + .github-FilePatchView-controlBlock { From 2cc14b1b17973cbd10d459fdd9ba18864e7f6c12 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 28 Nov 2018 23:38:05 +0100 Subject: [PATCH 1239/4053] add test to ensure buttons don't get rendered when inside a CommitDetailItem --- lib/views/hunk-header-view.js | 2 +- test/views/file-patch-header-view.test.js | 6 ++++++ test/views/hunk-header-view.test.js | 12 ++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index 6fd986ee54..ab2a43ae1b 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -58,7 +58,7 @@ export default class HunkHeaderView extends React.Component { } renderButtons() { - if (this.props.itemType === CommitPreviewItem) { + if (this.props.itemType === CommitPreviewItem || this.props.itemType === CommitDetailItem) { return null; } else { return ( diff --git a/test/views/file-patch-header-view.test.js b/test/views/file-patch-header-view.test.js index 2d4e0acb65..cf778723cb 100644 --- a/test/views/file-patch-header-view.test.js +++ b/test/views/file-patch-header-view.test.js @@ -5,6 +5,7 @@ import path from 'path'; import FilePatchHeaderView from '../../lib/views/file-patch-header-view'; import ChangedFileItem from '../../lib/items/changed-file-item'; import CommitPreviewItem from '../../lib/items/commit-preview-item'; +import CommitDetailItem from '../../lib/items/commit-detail-item'; describe('FilePatchHeaderView', function() { const relPath = path.join('dir', 'a.txt'); @@ -178,5 +179,10 @@ describe('FilePatchHeaderView', function() { buttonClass: 'icon-move-up', oppositeButtonClass: 'icon-move-down', })); + + it('does not render buttons when in a CommitDetailItem', function() { + const wrapper = shallow(buildApp({itemType: CommitDetailItem})); + assert.isFalse(wrapper.find('.btn-group').exists()); + }); }); }); diff --git a/test/views/hunk-header-view.test.js b/test/views/hunk-header-view.test.js index ae1b80fefe..78c263ae81 100644 --- a/test/views/hunk-header-view.test.js +++ b/test/views/hunk-header-view.test.js @@ -4,6 +4,8 @@ import {shallow} from 'enzyme'; import HunkHeaderView from '../../lib/views/hunk-header-view'; import RefHolder from '../../lib/models/ref-holder'; import Hunk from '../../lib/models/patch/hunk'; +import CommitDetailItem from '../../lib/items/commit-detail-item'; +import CommitPreviewItem from '../../lib/items/commit-preview-item'; describe('HunkHeaderView', function() { let atomEnv, hunk; @@ -117,4 +119,14 @@ describe('HunkHeaderView', function() { assert.isFalse(mouseDown.called); assert.isTrue(evt.stopPropagation.called); }); + + it('does not render extra buttons when in a CommitPreviewItem or a CommitDetailItem', function() { + let wrapper = shallow(buildApp({itemType: CommitPreviewItem})); + assert.isFalse(wrapper.find('.github-HunkHeaderView-stageButton').exists()); + assert.isFalse(wrapper.find('.github-HunkHeaderView-discardButton').exists()); + + wrapper = shallow(buildApp({itemType: CommitDetailItem})); + assert.isFalse(wrapper.find('.github-HunkHeaderView-stageButton').exists()); + assert.isFalse(wrapper.find('.github-HunkHeaderView-discardButton').exists()); + }) }); From e0d97abd00e26b87a49f0bf9a752e5cafb7de458 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 29 Nov 2018 10:34:37 -0500 Subject: [PATCH 1240/4053] Fixed it all on the first go apparently That never happens --- lib/models/commit.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/models/commit.js b/lib/models/commit.js index fb6433ace5..3e7e65b3d4 100644 --- a/lib/models/commit.js +++ b/lib/models/commit.js @@ -65,28 +65,29 @@ export default class Commit { } const {LONG_MESSAGE_THRESHOLD, BOUNDARY_SEARCH_THRESHOLD} = this.constructor; - let elipsis = '...'; + let elipses = '...'; let lastParagraphIndex = Infinity; let lastWordIndex = Infinity; + const lastSubwordIndex = BOUNDARY_SEARCH_THRESHOLD - elipses.length; - const boundarySearch = this.getMessageBody() - .substring(LONG_MESSAGE_THRESHOLD - BOUNDARY_SEARCH_THRESHOLD, LONG_MESSAGE_THRESHOLD); + const baseIndex = LONG_MESSAGE_THRESHOLD - BOUNDARY_SEARCH_THRESHOLD; + const boundarySearch = this.getMessageBody().substring(baseIndex, LONG_MESSAGE_THRESHOLD); const boundaryRx = /\r?\n\r?\n|\s+/g; let result; while ((result = boundaryRx.exec(boundarySearch)) !== null) { if (/\r?\n\r?\n/.test(result[0])) { - // Paragraph boundary. Omit the elipsis + // Paragraph boundary. Omit the elipses lastParagraphIndex = result.index; - elipsis = ''; - } else if (result.index <= BOUNDARY_SEARCH_THRESHOLD - elipsis.length) { - // Word boundary. Only count if we have room for the elipsis under the cutoff. + elipses = ''; + } else if (result.index <= BOUNDARY_SEARCH_THRESHOLD - elipses.length) { + // Word boundary. Only count if we have room for the elipses under the cutoff. lastWordIndex = result.index; } } - const cutoffIndex = Math.min(lastParagraphIndex, lastWordIndex); - return this.getMessageBody().substring(0, cutoffIndex) + elipsis; + const cutoffIndex = baseIndex + Math.min(lastParagraphIndex, lastWordIndex, lastSubwordIndex); + return this.getMessageBody().substring(0, cutoffIndex) + elipses; } setMultiFileDiff(multiFileDiff) { From 94b5b960bd6018d3b6a38ab6989d8e49789013d6 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 29 Nov 2018 16:29:14 +0100 Subject: [PATCH 1241/4053] `getRemoteForBranch` now gets the remote from its remoteSet --- lib/models/repository.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/models/repository.js b/lib/models/repository.js index 68202d136e..dbb238faeb 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -214,11 +214,7 @@ export default class Repository { async getRemoteForBranch(branchName) { const name = await this.getConfig(`branch.${branchName}.remote`); - if (name === null) { - return nullRemote; - } else { - return new Remote(name); - } + return (await this.getRemotes()).withName(name); } async saveDiscardHistory() { From 6ee4dd4a6df3e340ce6f5a086045d4592769fb59 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 29 Nov 2018 16:38:02 +0100 Subject: [PATCH 1242/4053] remove unused improts --- lib/models/repository.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/models/repository.js b/lib/models/repository.js index dbb238faeb..81104895a9 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -5,7 +5,6 @@ import fs from 'fs-extra'; import {getNullActionPipelineManager} from '../action-pipeline'; import CompositeGitStrategy from '../composite-git-strategy'; -import Remote, {nullRemote} from './remote'; import Author, {nullAuthor} from './author'; import Branch from './branch'; import {Loading, Absent, LoadingGuess, AbsentGuess} from './repository-states'; From 4655dd303f756f5a5745383b3ab53510ad927c41 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 29 Nov 2018 16:53:56 +0100 Subject: [PATCH 1243/4053] [wip] render dotcom link --- lib/containers/commit-detail-container.js | 3 +++ lib/models/repository-states/present.js | 4 +++ lib/models/repository-states/state.js | 4 +++ lib/models/repository.js | 1 + lib/views/commit-detail-view.js | 30 +++++++++++++++++------ 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/lib/containers/commit-detail-container.js b/lib/containers/commit-detail-container.js index 911c73b5f8..275d82ce35 100644 --- a/lib/containers/commit-detail-container.js +++ b/lib/containers/commit-detail-container.js @@ -15,6 +15,9 @@ export default class CommitDetailContainer extends React.Component { fetchData = repository => { return yubikiri({ commit: repository.getCommit(this.props.sha), + currentBranch: repository.getCurrentBranch(), + currentRemote: async query => repository.getRemoteForBranch((await query.currentBranch).getName()), + isCommitPushed: repository.isCommitPushed(this.props.sha), }); } diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index bc28589081..6eb53323e6 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -741,6 +741,10 @@ export default class Present extends State { }); } + isCommitPushed({sha}) { + return true; + } + // Author information getAuthors(options) { diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 92961a777a..82c443a255 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -306,6 +306,10 @@ export default class State { return Promise.resolve([]); } + isCommitPushed({sha}) { + return false; + } + // Author information getAuthors() { diff --git a/lib/models/repository.js b/lib/models/repository.js index 81104895a9..e4960aed2a 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -328,6 +328,7 @@ const delegates = [ 'getLastCommit', 'getCommit', 'getRecentCommits', + 'isCommitPushed', 'getAuthors', diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index a3b924a3ad..487ffa5c7b 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -23,6 +23,10 @@ export default class CommitDetailView extends React.Component { messageCollapsible: PropTypes.bool.isRequired, messageOpen: PropTypes.bool.isRequired, toggleMessage: PropTypes.func.isRequired, + + currentRemote: PropTypes.object.isRequired, + currentBranch: PropTypes.object.isRequired, + isCommitPushed: PropTypes.bool.isRequired, } render() { @@ -43,13 +47,7 @@ export default class CommitDetailView extends React.Component { {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} -
    - {/* TODO fix href */} - - {commit.getSha()} - -
    + {this.renderDotComLink()}
    @@ -87,6 +85,24 @@ export default class CommitDetailView extends React.Component { return moment(date * 1000).fromNow(); } + renderDotComLink() { + const remote = this.props.currentRemote; + const sha = this.props.commit.getSha(); + if (remote && remote.isGithubRepo() && this.props.isCommitPushed) { + const repoUrl = `https://www.github.com/${this.props.currentRemote.getOwner()}/${this.props.currentRemote.getRepo()}`; + return ( + + ); + } else { + return null; + } + } + getAuthorInfo() { const coAuthorCount = this.props.commit.getCoAuthors().length; return coAuthorCount ? this.props.commit.getAuthorEmail() : `${coAuthorCount + 1} people`; From da431290974773b1ccdede986aac2f0e6262ad6f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 29 Nov 2018 11:19:00 -0500 Subject: [PATCH 1244/4053] Toggle between an abbreviated commit message body and the full one. --- lib/views/commit-detail-view.js | 28 ++++++------ test/views/commit-detail-view.test.js | 61 +++++++++++++++++---------- 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index 487ffa5c7b..9438c075c2 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -36,11 +36,9 @@ export default class CommitDetailView extends React.Component {
    -

    - {emojify(commit.getMessageSubject())} - {this.renderShowMoreButton()} -

    - {this.renderCommitMessageBody(commit)} +

    {emojify(commit.getMessageSubject())}

    + {this.renderCommitMessageBody()} + {this.renderShowMoreButton()}
    {/* TODO fix image src */} {this.renderAuthors()} @@ -60,22 +58,22 @@ export default class CommitDetailView extends React.Component { ); } - renderCommitMessageBody(commit) { - if (this.props.messageOpen || !this.props.messageCollapsible) { - return ( -
    -          {emojify(commit.getMessageBody())}
    - ); - } else { - return null; - } + renderCommitMessageBody() { + const collapsed = this.props.messageCollapsible && !this.props.messageOpen; + + return ( +
    +        {collapsed ? this.props.commit.abbreviatedBody() : this.props.commit.getMessageBody()}
    +      
    + ); } renderShowMoreButton() { if (!this.props.messageCollapsible) { return null; } - const buttonText = this.props.messageOpen ? 'Hide More' : 'Show More'; + + const buttonText = this.props.messageOpen ? 'Show Less' : 'Show More'; return ( ); diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js index ee39d83e40..acca9a093d 100644 --- a/test/views/commit-detail-view.test.js +++ b/test/views/commit-detail-view.test.js @@ -5,6 +5,7 @@ import dedent from 'dedent-js'; import CommitDetailView from '../../lib/views/commit-detail-view'; import CommitDetailItem from '../../lib/items/commit-detail-item'; +import Commit from '../../lib/models/commit'; import {cloneRepository, buildRepository} from '../helpers'; import {commitBuilder} from '../builder/commit'; @@ -93,65 +94,81 @@ describe('CommitDetailView', function() { }); describe('commit message collapsibility', function() { - let wrapper; - const commitMessageBody = dedent` - if every pork chop was perfect... + let wrapper, shortMessage, longMessage; + beforeEach(function() { + shortMessage = dedent` + if every pork chop was perfect... + we wouldn't have hot dogs! + 🌭🌭🌭🌭🌭🌭🌭 + `; + + longMessage = 'this message is really really really\n'; + while (longMessage.length < Commit.LONG_MESSAGE_THRESHOLD) { + longMessage += 'really really really really really really\n'; + } + longMessage += 'really really long.'; + }); - we wouldn't have hot dogs! - 🌭🌭🌭🌭🌭🌭🌭 - `; - const commit = commitBuilder() - .authorEmail('greg@mruniverse.biz') - .messageBody(commitMessageBody) - .build(); describe('when messageCollapsible is false', function() { beforeEach(function() { + const commit = commitBuilder().messageBody(shortMessage).build(); wrapper = shallow(buildApp({commit, messageCollapsible: false})); }); + it('renders the full message body', function() { - assert.deepEqual(wrapper.find('.github-CommitDetailView-moreText').text(), commitMessageBody); + assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), shortMessage); }); - it('does not render a button', function() { + it('does not render a button', function() { + assert.isFalse(wrapper.find('.github-CommitDetailView-moreButton').exists()); }); }); + describe('when messageCollapsible is true and messageOpen is false', function() { beforeEach(function() { + const commit = commitBuilder().messageBody(longMessage).build(); wrapper = shallow(buildApp({commit, messageCollapsible: true, messageOpen: false})); }); - it('does not render commit message', function() { - assert.lengthOf(wrapper.find('.github-CommitDetailView-moreText'), 0); + + it('renders an abbreviated commit message', function() { + const messageText = wrapper.find('.github-CommitDetailView-moreText').text(); + assert.notStrictEqual(messageText, longMessage); + assert.isAtMost(messageText.length, Commit.LONG_MESSAGE_THRESHOLD); }); - it('renders button with the text `Show More`', function() { + it('renders a button to reveal the rest of the message', function() { const button = wrapper.find('.github-CommitDetailView-moreButton'); assert.lengthOf(button, 1); - assert.deepEqual(button.text(), 'Show More'); + assert.strictEqual(button.text(), 'Show More'); }); }); describe('when messageCollapsible is true and messageOpen is true', function() { let toggleMessage; + beforeEach(function() { toggleMessage = sinon.spy(); + const commit = commitBuilder().messageBody(longMessage).build(); wrapper = shallow(buildApp({commit, messageCollapsible: true, messageOpen: true, toggleMessage})); }); + it('renders the full message', function() { - assert.deepEqual(wrapper.find('.github-CommitDetailView-moreText').text(), commitMessageBody); + assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), longMessage); }); - it('renders a button with the text `Hide More`', function() { + + it('renders a button to collapse the message text', function() { const button = wrapper.find('.github-CommitDetailView-moreButton'); assert.lengthOf(button, 1); - assert.deepEqual(button.text(), 'Hide More'); + assert.strictEqual(button.text(), 'Show Less'); }); - it('button calls `toggleMessage` prop when clicked', function() { + + it('the button calls toggleMessage when clicked', function() { const button = wrapper.find('.github-CommitDetailView-moreButton'); button.simulate('click'); - assert.ok(toggleMessage.called); + assert.isTrue(toggleMessage.called); }); - }); }); }); From c58c54047d4ad44fd7b25f6fa2e8f556b9d31959 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 29 Nov 2018 17:24:26 +0100 Subject: [PATCH 1245/4053] fix: wrong conditional in hunk header --- lib/views/hunk-header-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index ab2a43ae1b..a2ed357015 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -58,7 +58,7 @@ export default class HunkHeaderView extends React.Component { } renderButtons() { - if (this.props.itemType === CommitPreviewItem || this.props.itemType === CommitDetailItem) { + if (this.props.itemType === CommitDetailItem) { return null; } else { return ( From e110282fa6fc206e771afa3bea539b0e16a1458d Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 29 Nov 2018 17:47:28 +0100 Subject: [PATCH 1246/4053] add `getBranchesWithCommit` method --- lib/git-shell-out-strategy.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 1addc791b3..5ea5d0ae56 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -934,6 +934,11 @@ export default class GitShellOutStrategy { }); } + async getBranchesWithCommit(sha, {remotesOnly} = {}) { + const args = ['branch', ...(remotesOnly ? ['--remotes'] : []), '--format=%(refname:short)', '--contains', sha]; + return (await this.exec(args)).trim().split(LINE_ENDING_REGEX); + } + checkoutFiles(paths, revision) { if (paths.length === 0) { return null; } const args = ['checkout']; From 2184380a7c5cfc6cf9275e247e87e18c7b08e26b Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 29 Nov 2018 17:57:41 +0100 Subject: [PATCH 1247/4053] add option to show local only or remote only or both --- lib/git-shell-out-strategy.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 5ea5d0ae56..38f5792480 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -934,8 +934,13 @@ export default class GitShellOutStrategy { }); } - async getBranchesWithCommit(sha, {remotesOnly} = {}) { - const args = ['branch', ...(remotesOnly ? ['--remotes'] : []), '--format=%(refname:short)', '--contains', sha]; + async getBranchesWithCommit(sha, option = {}) { + const args = ['branch', '--format=%(refname:short)', '--contains', sha]; + if (option.showLocal && option.showRemote) { + args.splice(1, 0, '--all'); + } else if (option.showRemote) { + args.splice(1, 0, '--remotes'); + } return (await this.exec(args)).trim().split(LINE_ENDING_REGEX); } From adef37cab40952bf0fd4c101cc59a0d76f8623e7 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 29 Nov 2018 18:10:38 +0100 Subject: [PATCH 1248/4053] don't use short ref --- lib/git-shell-out-strategy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 38f5792480..ea7bbad606 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -935,7 +935,7 @@ export default class GitShellOutStrategy { } async getBranchesWithCommit(sha, option = {}) { - const args = ['branch', '--format=%(refname:short)', '--contains', sha]; + const args = ['branch', '--format=%(refname)', '--contains', sha]; if (option.showLocal && option.showRemote) { args.splice(1, 0, '--all'); } else if (option.showRemote) { From ff235a5a4c84f01ed1ad608d18602a13408ab191 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 29 Nov 2018 18:11:24 +0100 Subject: [PATCH 1249/4053] `isCommitPushed` to find out if a commit exists on remote tracking branch --- lib/models/repository-states/present.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 6eb53323e6..5e323fc4e2 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -741,8 +741,10 @@ export default class Present extends State { }); } - isCommitPushed({sha}) { - return true; + async isCommitPushed(sha) { + const remoteBranchesWithCommit = await this.git().getBranchesWithCommit(sha, {showLocal: false, showRemote: true}); + const currentRemote = (await this.repository.getCurrentBranch()).getUpstream(); + return remoteBranchesWithCommit.includes(currentRemote.getFullRef()); } // Author information From 011a3be72d0f435e850f66ce5677f119feb52604 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 29 Nov 2018 12:32:28 -0500 Subject: [PATCH 1250/4053] Enable hunk headers for CommitDetailItem --- lib/views/multi-file-patch-view.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 61414c30a3..5d1407e496 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -455,9 +455,6 @@ export default class MultiFilePatchView extends React.Component { } renderHunkHeaders(filePatch) { - if (this.props.itemType === CommitDetailItem) { - return null; - } const toggleVerb = this.props.stagingStatus === 'unstaged' ? 'Stage' : 'Unstage'; const selectedHunks = new Set( Array.from(this.props.selectedRows, row => this.props.multiFilePatch.getHunkAt(row)), From c54195aa4fe20ecec4ac99d0c7f70bf5dc9cb4d8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 29 Nov 2018 12:36:01 -0500 Subject: [PATCH 1251/4053] Let that CSS actually target the avatar element --- lib/views/commit-detail-view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index 9438c075c2..cdf82b45ff 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -117,7 +117,7 @@ export default class CommitDetailView extends React.Component { } return ( - + {authorEmails.map(this.renderAuthor)} ); From fb2ec268cd1f8f140b4739ddc3651dc2f4dd3af5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 29 Nov 2018 12:58:27 -0500 Subject: [PATCH 1252/4053] Fussing with button styles since I moved it --- styles/commit-detail.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/styles/commit-detail.less b/styles/commit-detail.less index e6cab0bf14..a4d27f89ec 100644 --- a/styles/commit-detail.less +++ b/styles/commit-detail.less @@ -47,8 +47,8 @@ &-moreButton { border: none; - margin-left: @default-padding/1.5; - padding: 0em .2em; + margin-bottom: @default-padding/1.5; + padding: 0em .4em; color: @text-color-subtle; font-style: italic; font-size: .8em; From 312eccc313db78682535566eeb44082c762f8396 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 29 Nov 2018 14:44:45 -0500 Subject: [PATCH 1253/4053] Move the "Show More" button back to the header. Move the meta information up there, too. --- lib/views/commit-detail-view.js | 8 +++++--- styles/commit-detail.less | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index cdf82b45ff..3d513cd671 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -36,9 +36,10 @@ export default class CommitDetailView extends React.Component {
    -

    {emojify(commit.getMessageSubject())}

    - {this.renderCommitMessageBody()} - {this.renderShowMoreButton()} +

    + {emojify(commit.getMessageSubject())} + {this.renderShowMoreButton()} +

    {/* TODO fix image src */} {this.renderAuthors()} @@ -47,6 +48,7 @@ export default class CommitDetailView extends React.Component { {this.renderDotComLink()}
    + {this.renderCommitMessageBody()}
    Date: Thu, 29 Nov 2018 20:50:12 +0100 Subject: [PATCH 1254/4053] still show sha when it cannot be linked --- lib/views/commit-detail-view.js | 16 ++++++++-------- styles/commit-detail.less | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index 3d513cd671..e3e8b6ad4f 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -46,7 +46,9 @@ export default class CommitDetailView extends React.Component { {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} - {this.renderDotComLink()} +
    + {this.renderDotComLink()} +
    {this.renderCommitMessageBody()}
    @@ -91,15 +93,13 @@ export default class CommitDetailView extends React.Component { if (remote && remote.isGithubRepo() && this.props.isCommitPushed) { const repoUrl = `https://www.github.com/${this.props.currentRemote.getOwner()}/${this.props.currentRemote.getRepo()}`; return ( - + + {sha} + ); } else { - return null; + return ({sha}); } } diff --git a/styles/commit-detail.less b/styles/commit-detail.less index e6cab0bf14..b349b8e296 100644 --- a/styles/commit-detail.less +++ b/styles/commit-detail.less @@ -47,8 +47,8 @@ &-moreButton { border: none; - margin-left: @default-padding/1.5; - padding: 0em .2em; + margin-bottom: @default-padding/1.5; + padding: 0em .4em; color: @text-color-subtle; font-style: italic; font-size: .8em; @@ -78,12 +78,12 @@ flex: 0 0 7ch; // Limit to 7 characters margin-left: @default-padding*2; line-height: @avatar-dimensions; - color: @text-color-info; + color: @text-color-subtle; font-family: var(--editor-font-family); white-space: nowrap; overflow: hidden; a { - color: inherit; + color: @text-color-info; } } } From aedb0761f84ee22e8d3c7a6dd8e2fdc418d2db61 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 29 Nov 2018 15:20:36 -0500 Subject: [PATCH 1255/4053] Turn that back to a left margin :art: --- styles/commit-detail.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/commit-detail.less b/styles/commit-detail.less index b349b8e296..a6d5584545 100644 --- a/styles/commit-detail.less +++ b/styles/commit-detail.less @@ -47,7 +47,7 @@ &-moreButton { border: none; - margin-bottom: @default-padding/1.5; + margin-left: @default-padding/1.5; padding: 0em .4em; color: @text-color-subtle; font-style: italic; From d007a3492f0c99b7052cdcabcb182924c5770f97 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 29 Nov 2018 15:21:48 -0500 Subject: [PATCH 1256/4053] Drop LONG_MESSAGE_THRESHOLD to 500 and prepare newline counting --- lib/models/commit.js | 45 +++++++++------- test/models/commit.test.js | 104 +++++++++++++++---------------------- 2 files changed, 67 insertions(+), 82 deletions(-) diff --git a/lib/models/commit.js b/lib/models/commit.js index 3e7e65b3d4..99cccfa531 100644 --- a/lib/models/commit.js +++ b/lib/models/commit.js @@ -1,9 +1,11 @@ const UNBORN = Symbol('unborn'); -export default class Commit { - static LONG_MESSAGE_THRESHOLD = 1000; +// Truncation elipsis styles +const WORD_ELIPSES = '...'; +const PARAGRAPH_ELIPSES = '\n\n...'; - static BOUNDARY_SEARCH_THRESHOLD = 100; +export default class Commit { + static LONG_MESSAGE_THRESHOLD = 500; static createUnborn() { return new Commit({unbornRef: UNBORN}); @@ -64,29 +66,32 @@ export default class Commit { return this.getMessageBody(); } - const {LONG_MESSAGE_THRESHOLD, BOUNDARY_SEARCH_THRESHOLD} = this.constructor; - let elipses = '...'; - let lastParagraphIndex = Infinity; - let lastWordIndex = Infinity; - const lastSubwordIndex = BOUNDARY_SEARCH_THRESHOLD - elipses.length; + const {LONG_MESSAGE_THRESHOLD} = this.constructor; - const baseIndex = LONG_MESSAGE_THRESHOLD - BOUNDARY_SEARCH_THRESHOLD; - const boundarySearch = this.getMessageBody().substring(baseIndex, LONG_MESSAGE_THRESHOLD); + let lastParagraphCutoff = null; + let lastWordCutoff = null; - const boundaryRx = /\r?\n\r?\n|\s+/g; + const searchText = this.getMessageBody().substring(0, LONG_MESSAGE_THRESHOLD); + const boundaryRx = /\s+/g; let result; - while ((result = boundaryRx.exec(boundarySearch)) !== null) { - if (/\r?\n\r?\n/.test(result[0])) { - // Paragraph boundary. Omit the elipses - lastParagraphIndex = result.index; - elipses = ''; - } else if (result.index <= BOUNDARY_SEARCH_THRESHOLD - elipses.length) { - // Word boundary. Only count if we have room for the elipses under the cutoff. - lastWordIndex = result.index; + while ((result = boundaryRx.exec(searchText)) !== null) { + const newlineCount = (result[0].match(/\r?\n/g) || []).length; + if (newlineCount < 2 && result.index <= LONG_MESSAGE_THRESHOLD - WORD_ELIPSES.length) { + lastWordCutoff = result.index; + } else if (result.index < LONG_MESSAGE_THRESHOLD - PARAGRAPH_ELIPSES.length) { + lastParagraphCutoff = result.index; } } - const cutoffIndex = baseIndex + Math.min(lastParagraphIndex, lastWordIndex, lastSubwordIndex); + let elipses = WORD_ELIPSES; + let cutoffIndex = LONG_MESSAGE_THRESHOLD - WORD_ELIPSES.length; + if (lastParagraphCutoff !== null) { + elipses = PARAGRAPH_ELIPSES; + cutoffIndex = lastParagraphCutoff; + } else if (lastWordCutoff !== null) { + cutoffIndex = lastWordCutoff; + } + return this.getMessageBody().substring(0, cutoffIndex) + elipses; } diff --git a/test/models/commit.test.js b/test/models/commit.test.js index 9e3e7664ea..ee9f28f43e 100644 --- a/test/models/commit.test.js +++ b/test/models/commit.test.js @@ -45,108 +45,88 @@ describe('Commit', function() { assert.strictEqual(commit.abbreviatedBody(), 'short'); }); - it('truncates the message body at the nearest paragraph boundary before the cutoff if one is nearby', function() { - // The | is at the 1000-character mark. + it('truncates the message body at the last paragraph boundary before the cutoff if one is present', function() { const body = dedent` Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. Mazim alterum sea ea, - essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam - tantas nullam corrumpit ad, in oratio luptatum eleifend vim. + essent malorum persius ne mei. - Ea salutatus contentiones eos. Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere - urbanitas, usu ut aperiri mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, - scripta iudicabit ne nam, in duis clita commodo sit. + Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam + tantas nullam corrumpit ad, in oratio luptatum eleifend vim. Ea salutatus contentiones eos. Eam in veniam facete + volutpat, solum appetere adversarium ut quo. Vel cu appetere urbanitas, usu ut aperiri mediocritatem, alia + molestie urbanitas cu qui. - Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat comprehensam ut his, et eum - voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei liber putant. Ad doctus + Velit antiopam erroribus no eu|m, scripta iudicabit ne nam, in duis clita commodo + sit. Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat comprehensam ut his, et + eum voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei liber putant. Ad doctus tractatos ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent philosophia et vix. Nusquam reprehendunt et mea. Ea eius omnes voluptua sit. - - No cum illud verear efficiantur. Id altera imperdiet nec. Noster aud|iam accusamus mei at, no zril libris nemore - duo, ius ne rebum doctus fuisset. Legimus epicurei in sit, esse purto suscipit eu qui, oporteat deserunt - delicatissimi sea in. Est id putent accusata convenire, no tibique molestie accommodare quo, cu est fuisset - offendit evertitur. `; const commit = commitBuilder().messageBody(body).build(); assert.strictEqual(commit.abbreviatedBody(), dedent` Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. Mazim alterum sea ea, - essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam - tantas nullam corrumpit ad, in oratio luptatum eleifend vim. + essent malorum persius ne mei. - Ea salutatus contentiones eos. Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere - urbanitas, usu ut aperiri mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, - scripta iudicabit ne nam, in duis clita commodo sit. + Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam + tantas nullam corrumpit ad, in oratio luptatum eleifend vim. Ea salutatus contentiones eos. Eam in veniam facete + volutpat, solum appetere adversarium ut quo. Vel cu appetere urbanitas, usu ut aperiri mediocritatem, alia + molestie urbanitas cu qui. - Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat comprehensam ut his, et eum - voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei liber putant. Ad doctus - tractatos ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent philosophia et vix. Nusquam - reprehendunt et mea. Ea eius omnes voluptua sit. + ... `); }); - it('truncates the message body at the nearest word boundary before the cutoff if one is nearby', function() { - // The | is at the 1000-character mark. + it('truncates the message body at the nearest word boundary before the cutoff if one is present', function() { + // The | is at the 500-character mark. const body = dedent` - Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. - - Mazim alterum sea ea, essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore - albucius te vis, eam tantas nullam corrumpit ad, in oratio luptatum eleifend vim. Ea salutatus contentiones eos. - Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere urbanitas, usu ut aperiri - mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, scripta iudicabit ne nam, in - duis clita commodo sit. Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat - comprehensam ut his, et eum voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei - liber putant. Ad doctus tractatos ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent - philosophia et vix. Nusquam reprehendunt et mea. Ea eius omnes voluptua sit. No cum illud verear efficiantur. Id - altera imperdiet nec. Noster audia|m accusamus mei at, no zril libris nemore duo, ius ne rebum doctus fuisset. - Legimus epicurei in sit, esse purto suscipit eu qui, oporteat deserunt delicatissimi sea in. Est id putent - accusata convenire, no tibique molestie accommodare quo, cu est fuisset offendit evertitur. + Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. Mazim alterum sea ea, + essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam + tantas nullam corrumpit ad, in oratio luptatum eleifend vim. Ea salutatus contentiones eos. Eam in veniam facete + volutpat, solum appetere adversarium ut quo. Vel cu appetere urbanitas, usu ut aperiri mediocritatem, alia + molestie urbanitas cu qui. Velit antiopam erroribus no eum,| scripta iudicabit ne nam, in duis clita commodo + sit. Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat comprehensam ut his, et + eum voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei liber putant. Ad doctus + tractatos ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent philosophia et vix. Nusquam + reprehendunt et mea. Ea eius omnes voluptua sit. No cum illud verear efficiantur. Id altera imperdiet nec. + Noster audia|m accusamus mei at, no zril libris nemore duo, ius ne rebum doctus fuisset. Legimus epicurei in + sit, esse purto suscipit eu qui, oporteat deserunt delicatissimi sea in. Est id putent accusata convenire, no + tibique molestie accommodare quo, cu est fuisset offendit evertitur. `; const commit = commitBuilder().messageBody(body).build(); assert.strictEqual(commit.abbreviatedBody(), dedent` - Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. - - Mazim alterum sea ea, essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore - albucius te vis, eam tantas nullam corrumpit ad, in oratio luptatum eleifend vim. Ea salutatus contentiones eos. - Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere urbanitas, usu ut aperiri - mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, scripta iudicabit ne nam, in - duis clita commodo sit. Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat - comprehensam ut his, et eum voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei - liber putant. Ad doctus tractatos ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent - philosophia et vix. Nusquam reprehendunt et mea. Ea eius omnes voluptua sit. No cum illud verear efficiantur. Id - altera imperdiet nec. Noster... + Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. Mazim alterum sea ea, + essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam + tantas nullam corrumpit ad, in oratio luptatum eleifend vim. Ea salutatus contentiones eos. Eam in veniam facete + volutpat, solum appetere adversarium ut quo. Vel cu appetere urbanitas, usu ut aperiri mediocritatem, alia + molestie urbanitas cu qui. Velit antiopam erroribus no... `); }); it('truncates the message body at the character cutoff if no word or paragraph boundaries can be found', function() { - // The | is at the 1000-character mark. - const body = 'Loremipsumdolorsitamet,ethisjustodeleniti,omniumfastidiiadversariumathas.\n\n' + - 'Mazim alterumseaea,essentmalorumpersiusnemei.Nameatemporqualisque,modusdomingtehas.Affertdolore' + + // The | is at the 500-character mark. + const body = 'Loremipsumdolorsitamet,ethisjustodeleniti,omniumfastidiiadversariumathas.' + + 'Mazimalterumseaea,essentmalorumpersiusnemei.Nameatemporqualisque,modusdomingtehas.Affertdolore' + 'albuciustevis,eamtantasnullamcorrumpitad,inoratioluptatumeleifendvim.Easalutatuscontentioneseos.' + 'Eaminveniamfacetevolutpat,solumappetereadversariumutquo.Velcuappetereurbanitas,usuutaperiri' + 'mediocritatem,aliamolestieurbanitascuqui.Velitantiopamerroribusnoeum,scriptaiudicabitnenam,in' + - 'duisclitacommodosit.Assumsensibusoporteretevel,vissemperevertiturdefiniebasin.Tamquamfeugiat' + + 'duisclitacommodosit.Assumsensibusoporteretevel,vissem|perevertiturdefiniebasin.Tamquamfeugiat' + 'comprehensamuthis,eteumvoluptuaullamcorper,exmeidebitisinciderint.Sitdiscerepertinaxte,anmei' + 'liberputant.Addoctustractatosius,duoadcivibusalienum,nominativoluptariasedan.Librisessent' + 'philosophiaetvix.Nusquamreprehenduntetmea.Eaeiusomnesvoluptuasit.Nocumilludverearefficiantur.Id' + 'alteraimperdietnec.Nosteraudiamaccusamusmeiat,nozrillibrisnemoreduo,iusnerebumdoctusfuisset.' + 'Legimusepicureiinsit,essepurtosuscipiteuqui,oporteatdeseruntdelicatissimiseain.Estidputent' + - '|accusataconvenire,notibiquemolestieaccommodarequo,cuestfuissetoffenditevertitur.'; + 'accusataconvenire,notibiquemolestieaccommodarequo,cuestfuissetoffenditevertitur.'; const commit = commitBuilder().messageBody(body).build(); assert.strictEqual( commit.abbreviatedBody(), - 'Loremipsumdolorsitamet,ethisjustodeleniti,omniumfastidiiadversariumathas.\n\n' + - 'Mazim alterumseaea,essentmalorumpersiusnemei.Nameatemporqualisque,modusdomingtehas.Affertdolore' + + 'Loremipsumdolorsitamet,ethisjustodeleniti,omniumfastidiiadversariumathas.' + + 'Mazimalterumseaea,essentmalorumpersiusnemei.Nameatemporqualisque,modusdomingtehas.Affertdolore' + 'albuciustevis,eamtantasnullamcorrumpitad,inoratioluptatumeleifendvim.Easalutatuscontentioneseos.' + 'Eaminveniamfacetevolutpat,solumappetereadversariumutquo.Velcuappetereurbanitas,usuutaperiri' + 'mediocritatem,aliamolestieurbanitascuqui.Velitantiopamerroribusnoeum,scriptaiudicabitnenam,in' + - 'duisclitacommodosit.Assumsensibusoporteretevel,vissemperevertiturdefiniebasin.Tamquamfeugiat' + - 'comprehensamuthis,eteumvoluptuaullamcorper,exmeidebitisinciderint.Sitdiscerepertinaxte,anmei' + - 'liberputant.Addoctustractatosius,duoadcivibusalienum,nominativoluptariasedan.Librisessent' + - 'philosophiaetvix.Nusquamreprehenduntetmea.Eaeiusomnesvoluptuasit.Nocumilludverearefficiantur.Id' + - 'alteraimperdietnec.Nosteraudiamaccusamusmeiat,nozrillibrisnemoreduo,iusnerebumdoctusfuisset.' + - 'Legimusepicureiinsit,essepurtosuscipiteuqui,oporteatdeseruntdelicatissimiseain.Estidput...', + 'duisclitacommodosit.Assumsensibusoporteretevel,vis...', ); }); }); From 682503cc3fe0687b21c91df89299f47b2b0bec93 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 29 Nov 2018 15:33:42 -0500 Subject: [PATCH 1257/4053] Armor the abbreviation logic against messages with many short lines --- lib/models/commit.js | 29 ++++++++++++++++++++++++++--- test/models/commit.test.js | 18 ++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/lib/models/commit.js b/lib/models/commit.js index 99cccfa531..4f69679c19 100644 --- a/lib/models/commit.js +++ b/lib/models/commit.js @@ -2,11 +2,14 @@ const UNBORN = Symbol('unborn'); // Truncation elipsis styles const WORD_ELIPSES = '...'; +const NEWLINE_ELIPSES = '\n...'; const PARAGRAPH_ELIPSES = '\n\n...'; export default class Commit { static LONG_MESSAGE_THRESHOLD = 500; + static NEWLINE_THRESHOLD = 9; + static createUnborn() { return new Commit({unbornRef: UNBORN}); } @@ -47,7 +50,15 @@ export default class Commit { } isBodyLong() { - return this.getMessageBody().length > this.constructor.LONG_MESSAGE_THRESHOLD; + if (this.getMessageBody().length > this.constructor.LONG_MESSAGE_THRESHOLD) { + return true; + } + + if ((this.getMessageBody().match(/\r?\n/g) || []).length > this.constructor.NEWLINE_THRESHOLD) { + return true; + } + + return false; } getFullMessage() { @@ -66,16 +77,25 @@ export default class Commit { return this.getMessageBody(); } - const {LONG_MESSAGE_THRESHOLD} = this.constructor; + const {LONG_MESSAGE_THRESHOLD, NEWLINE_THRESHOLD} = this.constructor; + let lastNewlineCutoff = null; let lastParagraphCutoff = null; let lastWordCutoff = null; const searchText = this.getMessageBody().substring(0, LONG_MESSAGE_THRESHOLD); const boundaryRx = /\s+/g; let result; + let lineCount = 0; while ((result = boundaryRx.exec(searchText)) !== null) { const newlineCount = (result[0].match(/\r?\n/g) || []).length; + + lineCount += newlineCount; + if (lineCount > NEWLINE_THRESHOLD) { + lastNewlineCutoff = result.index; + break; + } + if (newlineCount < 2 && result.index <= LONG_MESSAGE_THRESHOLD - WORD_ELIPSES.length) { lastWordCutoff = result.index; } else if (result.index < LONG_MESSAGE_THRESHOLD - PARAGRAPH_ELIPSES.length) { @@ -85,7 +105,10 @@ export default class Commit { let elipses = WORD_ELIPSES; let cutoffIndex = LONG_MESSAGE_THRESHOLD - WORD_ELIPSES.length; - if (lastParagraphCutoff !== null) { + if (lastNewlineCutoff !== null) { + elipses = NEWLINE_ELIPSES; + cutoffIndex = lastNewlineCutoff; + } else if (lastParagraphCutoff !== null) { elipses = PARAGRAPH_ELIPSES; cutoffIndex = lastParagraphCutoff; } else if (lastWordCutoff !== null) { diff --git a/test/models/commit.test.js b/test/models/commit.test.js index ee9f28f43e..4b4d6aa08d 100644 --- a/test/models/commit.test.js +++ b/test/models/commit.test.js @@ -34,6 +34,15 @@ describe('Commit', function() { assert.isTrue(commit.isBodyLong()); }); + it('returns true if the commit message body contains too many newlines', function() { + let messageBody = 'a\n'; + for (let i = 0; i < 50; i++) { + messageBody += 'a\n'; + } + const commit = commitBuilder().messageBody(messageBody).build(); + assert.isTrue(commit.isBodyLong()); + }); + it('returns false for a null commit', function() { assert.isFalse(nullCommit.isBodyLong()); }); @@ -129,5 +138,14 @@ describe('Commit', function() { 'duisclitacommodosit.Assumsensibusoporteretevel,vis...', ); }); + + it('truncates the message body when it contains too many newlines', function() { + let messageBody = ''; + for (let i = 0; i < 50; i++) { + messageBody += `${i}\n`; + } + const commit = commitBuilder().messageBody(messageBody).build(); + assert.strictEqual(commit.abbreviatedBody(), '0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n...'); + }); }); }); From aff05e5c50e2d8a73e8a2e074a4ea1c12bc8e36b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 29 Nov 2018 15:46:42 -0500 Subject: [PATCH 1258/4053] Allow the CommitDetailView header to focus and use native bindings --- lib/views/commit-detail-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index e3e8b6ad4f..2684c7c28d 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -34,7 +34,7 @@ export default class CommitDetailView extends React.Component { return (
    -
    +

    {emojify(commit.getMessageSubject())} From 5b0c75a26577b62408c52978660c1ce2fb44f164 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 29 Nov 2018 13:14:53 -0800 Subject: [PATCH 1259/4053] set a max-height on the commit message body text. We want the content beneath to remain visible / scrollable. --- styles/commit-detail.less | 3 +++ 1 file changed, 3 insertions(+) diff --git a/styles/commit-detail.less b/styles/commit-detail.less index a6d5584545..5044469874 100644 --- a/styles/commit-detail.less +++ b/styles/commit-detail.less @@ -69,6 +69,9 @@ word-break: break-word; white-space: pre-wrap; background-color: transparent; + // in the case of loonnng commit message bodies, we want to cap the height so that + // the content beneath will remain visible / scrollable. + max-height: 55vh; &:empty { display: none; } From 5fa5e65775272af634cdfb4002ba471e701c6e48 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 29 Nov 2018 21:31:39 +0100 Subject: [PATCH 1260/4053] update function name in state --- lib/models/repository-states/state.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 82c443a255..747b7b3ac5 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -306,7 +306,7 @@ export default class State { return Promise.resolve([]); } - isCommitPushed({sha}) { + isCommitPushed(sha) { return false; } From dfa6c6a619487add5da72447e893f8bb4f1865c5 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 29 Nov 2018 21:32:23 +0100 Subject: [PATCH 1261/4053] add isCommitPushed to default returns a null object test --- test/models/repository.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 2eaac205e5..99a3ec2746 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -64,7 +64,7 @@ describe('Repository', function() { for (const method of [ 'isLoadingGuess', 'isAbsentGuess', 'isAbsent', 'isLoading', 'isEmpty', 'isPresent', 'isTooLarge', 'isUndetermined', 'showGitTabInit', 'showGitTabInitInProgress', 'showGitTabLoading', 'showStatusBarTiles', - 'hasDiscardHistory', 'isMerging', 'isRebasing', + 'hasDiscardHistory', 'isMerging', 'isRebasing', 'isCommitPushed', ]) { assert.isFalse(await repository[method]()); } From b7ef5ed750cb44d5a0ccff1907e3a2de5a423aee Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 29 Nov 2018 22:21:15 +0100 Subject: [PATCH 1262/4053] after undoing a commit, close corresponding commit item pane, if opened. --- lib/controllers/git-tab-controller.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/controllers/git-tab-controller.js b/lib/controllers/git-tab-controller.js index 57087c7170..f3322f4c79 100644 --- a/lib/controllers/git-tab-controller.js +++ b/lib/controllers/git-tab-controller.js @@ -11,6 +11,7 @@ import { CommitPropType, BranchPropType, FilePatchItemPropType, MergeConflictItemPropType, RefHolderPropType, } from '../prop-types'; import {autobind} from '../helpers'; +import CommitDetailItem from '../items/commit-detail-item'; export default class GitTabController extends React.Component { static focus = { @@ -277,6 +278,16 @@ export default class GitTabController extends React.Component { new Author(author.email, author.name)); this.updateSelectedCoAuthors(coAuthors); + + // close corresponding commit item pane, if opened + const uri = CommitDetailItem.buildURI(this.props.repository.getWorkingDirectoryPath(), lastCommit.getSha()); + const pane = this.props.workspace.paneForURI(uri); + if (pane) { + const item = pane.itemForURI(uri); + if (item) { + await pane.destroyItem(item); + } + } return null; } From 9341c95edcca0eebb6adc05c45ab455c6a2d43e0 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 30 Nov 2018 13:03:13 +0900 Subject: [PATCH 1263/4053] Move "show more" button --- lib/views/commit-detail-view.js | 2 +- styles/commit-detail.less | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index 2684c7c28d..e76b70ec22 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -38,7 +38,6 @@ export default class CommitDetailView extends React.Component {

    {emojify(commit.getMessageSubject())} - {this.renderShowMoreButton()}

    {/* TODO fix image src */} @@ -50,6 +49,7 @@ export default class CommitDetailView extends React.Component { {this.renderDotComLink()}
    + {this.renderShowMoreButton()} {this.renderCommitMessageBody()}

    diff --git a/styles/commit-detail.less b/styles/commit-detail.less index 5044469874..9f9bcc4762 100644 --- a/styles/commit-detail.less +++ b/styles/commit-detail.less @@ -16,6 +16,7 @@ &-commit { padding: @default-padding*2; + padding-bottom: 0; } &-title { @@ -35,7 +36,7 @@ &-meta { display: flex; align-items: center; - margin-top: @default-padding/2; + margin: @default-padding/2 0 @default-padding*2 0; } &-metaText { @@ -46,12 +47,12 @@ } &-moreButton { - border: none; - margin-left: @default-padding/1.5; + position: absolute; + left: 50%; + transform: translate(-50%, -50%); padding: 0em .4em; color: @text-color-subtle; font-style: italic; - font-size: .8em; border: 1px solid @base-border-color; border-radius: @component-border-radius; background-color: @button-background-color; @@ -62,12 +63,13 @@ } &-moreText { - padding: @default-padding/2 0 @default-padding 0; + padding: @default-padding*2 0; font-size: inherit; font-family: var(--editor-font-family); word-wrap: initial; word-break: break-word; white-space: pre-wrap; + border-top: 1px solid @base-border-color; background-color: transparent; // in the case of loonnng commit message bodies, we want to cap the height so that // the content beneath will remain visible / scrollable. From 99ba3bc4cc2c0e512d9116e46b823b271416d035 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 29 Nov 2018 21:00:40 -0800 Subject: [PATCH 1264/4053] [wip] keyboard navigability in RecentCommitsView ...I feel like I'm doing this wrong. Feel free to revert and start over. --- keymaps/git.cson | 5 ++++ lib/controllers/recent-commits-controller.js | 2 ++ lib/views/git-tab-view.js | 7 +++++ lib/views/recent-commits-view.js | 29 ++++++++++++++++++++ test/views/git-tab-view.test.js | 9 ++++++ 5 files changed, 52 insertions(+) diff --git a/keymaps/git.cson b/keymaps/git.cson index 038e4d942c..f6236b9a07 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -32,6 +32,11 @@ 'tab': 'core:focus-next' 'shift-tab': 'core:focus-previous' +'.github-RecentCommitsView': + 'up': 'github:recent-commit-up' + 'down': 'github:recent-commit-down' + 'enter': 'github:open-recent-commit' + '.github-StagingView.unstaged-changes-focused': 'cmd-backspace': 'github:discard-changes-in-selected-files' 'ctrl-backspace': 'github:discard-changes-in-selected-files' diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index 2e72da64f9..e97b079192 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -15,6 +15,7 @@ export default class RecentCommitsController extends React.Component { undoLastCommit: PropTypes.func.isRequired, workspace: PropTypes.object.isRequired, repository: PropTypes.object.isRequired, + commandRegistry: PropTypes.object.isRequired, } constructor(props, context) { @@ -54,6 +55,7 @@ export default class RecentCommitsController extends React.Component { undoLastCommit={this.props.undoLastCommit} openCommit={this.openCommit} selectedCommitSha={this.state.selectedCommitSha} + commandRegistry={this.props.commandRegistry} /> ); } diff --git a/lib/views/git-tab-view.js b/lib/views/git-tab-view.js index fb7339869d..322cc3ad97 100644 --- a/lib/views/git-tab-view.js +++ b/lib/views/git-tab-view.js @@ -7,6 +7,7 @@ import StagingView from './staging-view'; import GitLogo from './git-logo'; import CommitController from '../controllers/commit-controller'; import RecentCommitsController from '../controllers/recent-commits-controller'; +import RecentCommitsView from '../views/recent-commits-view'; import RefHolder from '../models/ref-holder'; import {isValidWorkdir, autobind} from '../helpers'; import {AuthorPropType, UserStorePropType, RefHolderPropType} from '../prop-types'; @@ -16,6 +17,7 @@ export default class GitTabView extends React.Component { ...StagingView.focus, ...CommitController.focus, ...RecentCommitsController.focus, + ...RecentCommitsView.focus, }; static propTypes = { @@ -193,6 +195,7 @@ export default class GitTabView extends React.Component { updateSelectedCoAuthors={this.props.updateSelectedCoAuthors} /> view.setFocus(focus)).getOr(false); + } + + rememberFocus(event) { + return this.refRecentCommits.map(view => view.rememberFocus(event)).getOr(null); + } + + selectNextCommit() { + // okay, we should probably move the state of the selected commit into this component + // instead of using the sha, so we can more easily move to next / previous. + } + render() { return (
    + + + {this.renderCommits()}
    ); diff --git a/test/views/git-tab-view.test.js b/test/views/git-tab-view.test.js index c21f152e91..5164c65ecb 100644 --- a/test/views/git-tab-view.test.js +++ b/test/views/git-tab-view.test.js @@ -234,4 +234,13 @@ describe('GitTabView', function() { assert.isTrue(setFocus.called); assert.isTrue(setFocus.lastCall.returnValue); }); + + it('imperatively focuses the recent commits view', async function() { + const wrapper = mount(await buildApp()); + + const setFocus = sinon.spy(wrapper.find('RecentCommitsView').instance(), 'setFocus'); + wrapper.instance().focusAndSelectRecentCommit(); + assert.isTrue(setFocus.called); + assert.isTrue(setFocus.lastCall.returnValue); + }); }); From 2bed5f1db9d939f6fde9553157796506a7b20971 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 29 Nov 2018 17:06:11 -0800 Subject: [PATCH 1265/4053] Display co-author information --- lib/views/commit-detail-view.js | 13 +++++++--- test/views/commit-detail-view.test.js | 35 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index e76b70ec22..40a5280618 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -43,7 +43,7 @@ export default class CommitDetailView extends React.Component { {/* TODO fix image src */} {this.renderAuthors()} - {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} + {this.getAuthorInfo()} committed {this.humanizeTimeSince(commit.getAuthorDate())}
    {this.renderDotComLink()} @@ -104,8 +104,15 @@ export default class CommitDetailView extends React.Component { } getAuthorInfo() { - const coAuthorCount = this.props.commit.getCoAuthors().length; - return coAuthorCount ? this.props.commit.getAuthorEmail() : `${coAuthorCount + 1} people`; + const commit = this.props.commit; + const coAuthorCount = commit.getCoAuthors().length; + if (coAuthorCount === 0) { + return commit.getAuthorEmail(); + } else if (coAuthorCount === 1) { + return `${commit.getAuthorEmail()} and ${commit.getCoAuthors()[0].email}`; + } else { + return `${commit.getAuthorEmail()} and ${coAuthorCount} others`; + } } renderAuthor(email) { diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js index acca9a093d..e56c35a042 100644 --- a/test/views/commit-detail-view.test.js +++ b/test/views/commit-detail-view.test.js @@ -93,6 +93,41 @@ describe('CommitDetailView', function() { ); }); + describe('getAuthorInfo', function() { + describe('when there are no co-authors', function() { + it('returns only the author', function() { + const commit = commitBuilder() + .authorEmail('blaze@it.com') + .build(); + const wrapper = shallow(buildApp({commit})); + assert.strictEqual(wrapper.instance().getAuthorInfo(), 'blaze@it.com'); + }); + }); + + describe('when there is one co-author', function() { + it('returns author and the co-author', function() { + const commit = commitBuilder() + .authorEmail('blaze@it.com') + .addCoAuthor('two', 'two@coauthor.com') + .build(); + const wrapper = shallow(buildApp({commit})); + assert.strictEqual(wrapper.instance().getAuthorInfo(), 'blaze@it.com and two@coauthor.com'); + }); + }); + + describe('when there is more than one co-author', function() { + it('returns the author and number of co-authors', function() { + const commit = commitBuilder() + .authorEmail('blaze@it.com') + .addCoAuthor('two', 'two@coauthor.com') + .addCoAuthor('three', 'three@coauthor.com') + .build(); + const wrapper = shallow(buildApp({commit})); + assert.strictEqual(wrapper.instance().getAuthorInfo(), 'blaze@it.com and 2 others'); + }); + }); + }); + describe('commit message collapsibility', function() { let wrapper, shortMessage, longMessage; From d5efe90bd53e024e4deccf0c8ddf549a4ab7a7ec Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 29 Nov 2018 17:57:14 -0800 Subject: [PATCH 1266/4053] Add test for RecentCommitsController#openCommit, including event recording --- lib/controllers/recent-commits-controller.js | 2 +- .../recent-commits-controller.test.js | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index e97b079192..a257cab1fc 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -64,7 +64,7 @@ export default class RecentCommitsController extends React.Component { const workdir = this.props.repository.getWorkingDirectoryPath(); const uri = CommitDetailItem.buildURI(workdir, sha); this.props.workspace.open(uri).then(() => { - addEvent('open-commit-in-pane', {package: 'github', from: 'recent commit'}); + addEvent('open-commit-in-pane', {package: 'github', from: this.constructor.name}); }); } } diff --git a/test/controllers/recent-commits-controller.test.js b/test/controllers/recent-commits-controller.test.js index 66151879b1..f69c33ae17 100644 --- a/test/controllers/recent-commits-controller.test.js +++ b/test/controllers/recent-commits-controller.test.js @@ -3,6 +3,8 @@ import {shallow} from 'enzyme'; import RecentCommitsController from '../../lib/controllers/recent-commits-controller'; import Commit from '../../lib/models/commit'; +import {cloneRepository, buildRepository} from '../helpers'; +import * as reporterProxy from '../../lib/reporter-proxy'; describe('RecentCommitsController', function() { let app; @@ -23,4 +25,46 @@ describe('RecentCommitsController', function() { const wrapper = shallow(app); assert.isTrue(wrapper.find('RecentCommitsView').prop('isLoading')); }); + + describe('openCommit({sha})', function() { + let atomEnv, workdirPath, repository; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + workdirPath = await cloneRepository(); + repository = await buildRepository(workdirPath); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + it('opens a commit detail item', function() { + sinon.stub(atomEnv.workspace, 'open').resolves(); + + const sha = 'asdf1234'; + const commits = [new Commit({sha})]; + app = React.cloneElement(app, {commits, workspace: atomEnv.workspace, repository}); + const wrapper = shallow(app); + wrapper.instance().openCommit({sha: 'asdf1234'}); + + assert.isTrue(atomEnv.workspace.open.calledWith( + `atom-github://commit-detail?workdir=${encodeURIComponent(workdirPath)}` + + `&sha=${encodeURIComponent(sha)}`, + )); + }); + + it('records an event', async function() { + sinon.stub(atomEnv.workspace, 'open').resolves(); + sinon.stub(reporterProxy, 'addEvent'); + + const sha = 'asdf1234'; + const commits = [new Commit({sha})]; + app = React.cloneElement(app, {commits, workspace: atomEnv.workspace, repository}); + const wrapper = shallow(app); + + await wrapper.instance().openCommit({sha: 'asdf1234'}); + assert.isTrue(reporterProxy.addEvent.calledWith('open-commit-in-pane', {package: 'github', from: RecentCommitsController.name})); + }); + }); }); From 8b3c56894fe3ed11e55cad0b97559bb2e5139233 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 29 Nov 2018 22:33:00 -0800 Subject: [PATCH 1267/4053] Add tests for OpenCommitDialog --- lib/controllers/root-controller.js | 2 +- test/controllers/root-controller.test.js | 57 ++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index c57c7a9d4f..ab6f9f588f 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -597,7 +597,7 @@ export default class RootController extends React.Component { const uri = CommitDetailItem.buildURI(workdir, sha); this.setState({openCommitDialogActive: false}); this.props.workspace.open(uri).then(() => { - addEvent('open-commit-in-pane', {package: 'github', from: 'dialog'}); + addEvent('open-commit-in-pane', {package: 'github', from: OpenCommitDialog.name}); }); } diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 7edebaab67..8c7a4168dc 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -17,9 +17,11 @@ import GitHubTabItem from '../../lib/items/github-tab-item'; import ResolutionProgress from '../../lib/models/conflicts/resolution-progress'; import IssueishDetailItem from '../../lib/items/issueish-detail-item'; import CommitPreviewItem from '../../lib/items/commit-preview-item'; +import CommitDetailItem from '../../lib/items/commit-detail-item'; import * as reporterProxy from '../../lib/reporter-proxy'; import RootController from '../../lib/controllers/root-controller'; +import OpenCommitDialog from '../../lib/views/open-commit-dialog'; describe('RootController', function() { let atomEnv, app; @@ -323,6 +325,61 @@ describe('RootController', function() { }); }); + describe('github:open-commit', function() { + let workdirPath, wrapper, openCommitDetails, resolveOpenCommit; + + beforeEach(async function() { + openCommitDetails = sinon.stub(atomEnv.workspace, 'open').returns(new Promise(resolve => { + resolveOpenCommit = resolve; + })); + + workdirPath = await cloneRepository('multiple-commits'); + const repository = await buildRepository(workdirPath); + + app = React.cloneElement(app, {repository}); + wrapper = shallow(app); + }); + + it('renders the modal open-commit panel', function() { + wrapper.instance().showOpenCommitDialog(); + wrapper.update(); + + assert.lengthOf(wrapper.find('Panel').find({location: 'modal'}).find('OpenCommitDialog'), 1); + }); + + it('triggers the open callback on accept and fires `open-commit-in-pane` event', async function() { + sinon.stub(reporterProxy, 'addEvent'); + wrapper.instance().showOpenCommitDialog(); + wrapper.update(); + + const dialog = wrapper.find('OpenCommitDialog'); + const sha = 'asdf1234'; + + const promise = dialog.prop('didAccept')({sha}); + resolveOpenCommit(); + await promise; + + const uri = CommitDetailItem.buildURI(workdirPath, sha); + + assert.isTrue(openCommitDetails.calledWith(uri)); + + await assert.isTrue(reporterProxy.addEvent.calledWith('open-commit-in-pane', {package: 'github', from: OpenCommitDialog.name})); + }); + + it('dismisses the open-commit panel on cancel', function() { + wrapper.instance().showOpenCommitDialog(); + wrapper.update(); + + const dialog = wrapper.find('OpenCommitDialog'); + dialog.prop('didCancel')(); + + wrapper.update(); + assert.lengthOf(wrapper.find('OpenCommitDialog'), 0); + assert.isFalse(openCommitDetails.called); + assert.isFalse(wrapper.state('openCommitDialogActive')); + }); + }); + describe('github:clone', function() { let wrapper, cloneRepositoryForProjectPath, resolveClone, rejectClone; From 994525dbd328b5865eb9445243c2c0d907a89826 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 29 Nov 2018 22:42:24 -0800 Subject: [PATCH 1268/4053] Add tests for OpenIssuishDialog --- lib/controllers/root-controller.js | 1 + test/controllers/root-controller.test.js | 58 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index ab6f9f588f..282150c71d 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -582,6 +582,7 @@ export default class RootController extends React.Component { acceptOpenIssueish({repoOwner, repoName, issueishNumber}) { const uri = IssueishDetailItem.buildURI('https://api.github.com', repoOwner, repoName, issueishNumber); + console.warn(uri); this.setState({openIssueishDialogActive: false}); this.props.workspace.open(uri).then(() => { addEvent('open-issueish-in-pane', {package: 'github', from: 'dialog'}); diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 8c7a4168dc..7769fd4fa5 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -22,6 +22,7 @@ import * as reporterProxy from '../../lib/reporter-proxy'; import RootController from '../../lib/controllers/root-controller'; import OpenCommitDialog from '../../lib/views/open-commit-dialog'; +import OpenIssueishDialog from '../../lib/views/open-issueish-dialog'; describe('RootController', function() { let atomEnv, app; @@ -380,6 +381,63 @@ describe('RootController', function() { }); }); + describe('github:open-issue-or-pull-request', function() { + let workdirPath, wrapper, openIssueishDetails, resolveOpenIssueish; + + beforeEach(async function() { + openIssueishDetails = sinon.stub(atomEnv.workspace, 'open').returns(new Promise(resolve => { + resolveOpenIssueish = resolve; + })); + + workdirPath = await cloneRepository('multiple-commits'); + const repository = await buildRepository(workdirPath); + + app = React.cloneElement(app, {repository}); + wrapper = shallow(app); + }); + + it('renders the modal open-commit panel', function() { + wrapper.instance().showOpenIssueishDialog(); + wrapper.update(); + + assert.lengthOf(wrapper.find('Panel').find({location: 'modal'}).find('OpenIssueishDialog'), 1); + }); + + it('triggers the open callback on accept and fires `open-commit-in-pane` event', async function() { + sinon.stub(reporterProxy, 'addEvent'); + wrapper.instance().showOpenIssueishDialog(); + wrapper.update(); + + const dialog = wrapper.find('OpenIssueishDialog'); + const repoOwner = 'owner'; + const repoName = 'name'; + const issueishNumber = 1234; + + const promise = dialog.prop('didAccept')({repoOwner, repoName, issueishNumber}); + resolveOpenIssueish(); + await promise; + + const uri = IssueishDetailItem.buildURI('https://api.github.com', repoOwner, repoName, issueishNumber); + + assert.isTrue(openIssueishDetails.calledWith(uri)); + + await assert.isTrue(reporterProxy.addEvent.calledWith('open-issueish-in-pane', {package: 'github', from: 'dialog'})); + }); + + it('dismisses the open-commit panel on cancel', function() { + wrapper.instance().showOpenIssueishDialog(); + wrapper.update(); + + const dialog = wrapper.find('OpenIssueishDialog'); + dialog.prop('didCancel')(); + + wrapper.update(); + assert.lengthOf(wrapper.find('OpenIssueishDialog'), 0); + assert.isFalse(openIssueishDetails.called); + assert.isFalse(wrapper.state('openIssueishDialogActive')); + }); + }); + describe('github:clone', function() { let wrapper, cloneRepositoryForProjectPath, resolveClone, rejectClone; From 664b7a94df8a9615846aafcebefdd39e1235057e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 29 Nov 2018 22:45:35 -0800 Subject: [PATCH 1269/4053] Open CommitDetailItem from RecentCommitView as pending item --- lib/controllers/recent-commits-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index a257cab1fc..45eeea4764 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -63,7 +63,7 @@ export default class RecentCommitsController extends React.Component { openCommit({sha}) { const workdir = this.props.repository.getWorkingDirectoryPath(); const uri = CommitDetailItem.buildURI(workdir, sha); - this.props.workspace.open(uri).then(() => { + this.props.workspace.open(uri, {pending: true}).then(() => { addEvent('open-commit-in-pane', {package: 'github', from: this.constructor.name}); }); } From 02034767f22d8b0a7a0e9772e168e9ce5c13cc70 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 00:40:50 -0800 Subject: [PATCH 1270/4053] Open CommitDetailItem from PrCommitView --- lib/containers/issueish-detail-container.js | 2 ++ lib/controllers/issueish-detail-controller.js | 15 ++++++++++++++- lib/controllers/root-controller.js | 2 ++ lib/items/issueish-detail-item.js | 3 +++ lib/views/issueish-detail-view.js | 6 +++++- lib/views/pr-commit-view.js | 12 +++++++++++- lib/views/pr-commits-view.js | 2 ++ 7 files changed, 39 insertions(+), 3 deletions(-) diff --git a/lib/containers/issueish-detail-container.js b/lib/containers/issueish-detail-container.js index b7022358e2..b253eae9aa 100644 --- a/lib/containers/issueish-detail-container.js +++ b/lib/containers/issueish-detail-container.js @@ -169,6 +169,8 @@ export default class IssueishDetailContainer extends React.Component { addRemote={repository.addRemote.bind(repository)} onTitleChange={this.props.onTitleChange} switchToIssueish={this.props.switchToIssueish} + workdirPath={repository.getWorkingDirectoryPath()} + workspace={this.props.workspace} /> ); } diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 2db4fba498..4267f3b46f 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -6,7 +6,8 @@ import {BranchSetPropType, RemoteSetPropType} from '../prop-types'; import {GitError} from '../git-shell-out-strategy'; import EnableableOperation from '../models/enableable-operation'; import IssueishDetailView, {checkoutStates} from '../views/issueish-detail-view'; -import {incrementCounter} from '../reporter-proxy'; +import CommitDetailItem from '../items/commit-detail-item'; +import {incrementCounter, addEvent} from '../reporter-proxy'; export class BareIssueishDetailController extends React.Component { static propTypes = { @@ -16,6 +17,7 @@ export class BareIssueishDetailController extends React.Component { login: PropTypes.string.isRequired, }).isRequired, issueish: PropTypes.any, // FIXME from IssueishPaneItemContainer.propTypes + getWorkingDirectoryPath: PropTypes.func.isRequired, }), issueishNumber: PropTypes.number.isRequired, @@ -33,6 +35,9 @@ export class BareIssueishDetailController extends React.Component { addRemote: PropTypes.func.isRequired, onTitleChange: PropTypes.func.isRequired, switchToIssueish: PropTypes.func.isRequired, + + workspace: PropTypes.object.isRequired, + workdirPath: PropTypes.string.isRequired, } constructor(props) { @@ -85,6 +90,7 @@ export class BareIssueishDetailController extends React.Component { issueish={repository.issueish} checkoutOp={this.checkoutOp} switchToIssueish={this.props.switchToIssueish} + openCommit={this.openCommit} /> ); } @@ -201,6 +207,13 @@ export class BareIssueishDetailController extends React.Component { incrementCounter('checkout-pr'); } + + openCommit = ({sha}) => { + const uri = CommitDetailItem.buildURI(this.props.workdirPath, sha); + this.props.workspace.open(uri, {pending: true}).then(() => { + addEvent('open-commit-in-pane', {package: 'github', from: this.constructor.name}); + }); + } } export default createFragmentContainer(BareIssueishDetailController, { diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 282150c71d..0977f7c5ad 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -415,6 +415,8 @@ export default class RootController extends React.Component { workingDirectory={params.workingDirectory} workdirContextPool={this.props.workdirContextPool} loginModel={this.props.loginModel} + + workspace={this.props.workspace} /> )} diff --git a/lib/items/issueish-detail-item.js b/lib/items/issueish-detail-item.js index 88b8242b71..88c0bd78d3 100644 --- a/lib/items/issueish-detail-item.js +++ b/lib/items/issueish-detail-item.js @@ -18,6 +18,8 @@ export default class IssueishDetailItem extends Component { workingDirectory: PropTypes.string.isRequired, workdirContextPool: WorkdirContextPoolPropType.isRequired, loginModel: GithubLoginModelPropType.isRequired, + + workspace: PropTypes.object.isRequired, } static uriPattern = 'atom-github://issueish/{host}/{owner}/{repo}/{issueishNumber}?workdir={workingDirectory}' @@ -66,6 +68,7 @@ export default class IssueishDetailItem extends Component { issueishNumber={this.state.issueishNumber} repository={this.state.repository} + workspace={this.props.workspace} loginModel={this.props.loginModel} onTitleChange={this.handleTitleChanged} diff --git a/lib/views/issueish-detail-view.js b/lib/views/issueish-detail-view.js index 55d5a148e7..940cabd3be 100644 --- a/lib/views/issueish-detail-view.js +++ b/lib/views/issueish-detail-view.js @@ -143,6 +143,10 @@ export class BareIssueishDetailView extends React.Component { } renderPullRequestBody(issueish, childProps) { + const {checkoutOp} = this.props; + const reason = checkoutOp.why(); + const onBranch = reason && !reason({hidden: true, default: false}); // is there a more direct way than this? + return ( @@ -184,7 +188,7 @@ export class BareIssueishDetailView extends React.Component { {/* commits */} - + ); diff --git a/lib/views/pr-commit-view.js b/lib/views/pr-commit-view.js index 88d879a3cb..3e6d2a036f 100644 --- a/lib/views/pr-commit-view.js +++ b/lib/views/pr-commit-view.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import {emojify} from 'node-emoji'; import moment from 'moment'; import {graphql, createFragmentContainer} from 'react-relay'; +import cx from 'classnames'; import {autobind} from '../helpers'; @@ -37,6 +38,12 @@ export class PrCommitView extends React.Component { return moment(date).fromNow(); } + openCommitDetailItem = () => { + if (this.props.onBranch) { + return this.props.openCommit({sha: this.props.item.abbreviatedOid}); + } + } + render() { const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; const {avatarUrl, name, date} = this.props.item.committer; @@ -44,7 +51,10 @@ export class PrCommitView extends React.Component {

    - {emojify(messageHeadline)} + + {emojify(messageHeadline)} + {messageBody ?

    ); } else if (this.props.repository.hasDirectory() && - !isValidWorkdir(this.props.repository.getWorkingDirectoryPath())) { + !isValidWorkdir(this.props.repository.getWorkingDirectoryPath())) { return (
    @@ -295,7 +293,7 @@ export default class GitTabView extends React.Component { } focusAndSelectRecentCommit() { - this.setFocus(RecentCommitsView.focus.RECENT_COMMIT); + this.setFocus(RecentCommitsController.focus.RECENT_COMMIT); } focusAndSelectCommitPreviewButton() { From 6fd2eb59b71ae8b9e7dd54c0babb4ef41e22c299 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 09:57:17 -0500 Subject: [PATCH 1276/4053] Touch up PropTypes in CommitDetail components --- lib/containers/commit-detail-container.js | 1 + lib/controllers/commit-detail-controller.js | 2 +- lib/views/commit-detail-view.js | 16 +++++++++------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/containers/commit-detail-container.js b/lib/containers/commit-detail-container.js index 275d82ce35..c624d127f7 100644 --- a/lib/containers/commit-detail-container.js +++ b/lib/containers/commit-detail-container.js @@ -10,6 +10,7 @@ export default class CommitDetailContainer extends React.Component { static propTypes = { repository: PropTypes.object.isRequired, sha: PropTypes.string.isRequired, + itemType: PropTypes.func.isRequired, } fetchData = repository => { diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index 6e0df0c146..3f6014b9c7 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -5,7 +5,7 @@ import CommitDetailView from '../views/commit-detail-view'; export default class CommitDetailController extends React.Component { static propTypes = { - ...CommitDetailView.propTypes, + ...CommitDetailView.drilledPropTypes, commit: PropTypes.object.isRequired, } diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index 40a5280618..cdd383e616 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -4,13 +4,15 @@ import {emojify} from 'node-emoji'; import moment from 'moment'; import MultiFilePatchController from '../controllers/multi-file-patch-controller'; -import CommitDetailItem from '../items/commit-detail-item'; export default class CommitDetailView extends React.Component { - static propTypes = { + static drilledPropTypes = { repository: PropTypes.object.isRequired, commit: PropTypes.object.isRequired, - itemType: PropTypes.oneOf([CommitDetailItem]).isRequired, + currentRemote: PropTypes.object.isRequired, + currentBranch: PropTypes.object.isRequired, + isCommitPushed: PropTypes.bool.isRequired, + itemType: PropTypes.func.isRequired, workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, @@ -19,14 +21,14 @@ export default class CommitDetailView extends React.Component { config: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, + } + + static propTypes = { + ...CommitDetailView.drilledPropTypes, messageCollapsible: PropTypes.bool.isRequired, messageOpen: PropTypes.bool.isRequired, toggleMessage: PropTypes.func.isRequired, - - currentRemote: PropTypes.object.isRequired, - currentBranch: PropTypes.object.isRequired, - isCommitPushed: PropTypes.bool.isRequired, } render() { From 5c1389c6c4e27fed55a0feba1f3dac90cbd0a7f2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 10:13:47 -0500 Subject: [PATCH 1277/4053] Forward focus management from RecentCommitsController to its view --- lib/controllers/recent-commits-controller.js | 15 ++++++++++++++- .../controllers/recent-commits-controller.test.js | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index cd846ca89e..041cfb8ad7 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -1,11 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import {addEvent} from '../reporter-proxy'; +import {CompositeDisposable} from 'event-kit'; import CommitDetailItem from '../items/commit-detail-item'; import URIPattern from '../atom/uri-pattern'; import RecentCommitsView from '../views/recent-commits-view'; -import {CompositeDisposable} from 'event-kit'; +import RefHolder from '../models/ref-holder'; export default class RecentCommitsController extends React.Component { static propTypes = { @@ -25,6 +26,9 @@ export default class RecentCommitsController extends React.Component { this.subscriptions = new CompositeDisposable( this.props.workspace.onDidChangeActivePaneItem(this.updateSelectedCommit), ); + + this.refView = new RefHolder(); + this.state = {selectedCommitSha: ''}; } @@ -50,6 +54,7 @@ export default class RecentCommitsController extends React.Component { render() { return ( view.rememberFocus(event)).getOr(null); + } + + setFocus(focus) { + return this.refView.map(view => view.setFocus(focus)).getOr(false); + } } diff --git a/test/controllers/recent-commits-controller.test.js b/test/controllers/recent-commits-controller.test.js index d67d1985cb..c566b7865c 100644 --- a/test/controllers/recent-commits-controller.test.js +++ b/test/controllers/recent-commits-controller.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import {shallow} from 'enzyme'; +import {shallow, mount} from 'enzyme'; import RecentCommitsController from '../../lib/controllers/recent-commits-controller'; import {commitBuilder} from '../builder/commit'; @@ -127,4 +127,17 @@ describe('RecentCommitsController', function() { }); }); }); + + it('forwards focus management methods to its view', function() { + const wrapper = mount(app); + + const setFocusSpy = sinon.spy(wrapper.find('RecentCommitsView').instance(), 'setFocus'); + const rememberFocusSpy = sinon.spy(wrapper.find('RecentCommitsView').instance(), 'rememberFocus'); + + wrapper.instance().setFocus(RecentCommitsController.focus.RECENT_COMMIT); + assert.isTrue(setFocusSpy.calledWith(RecentCommitsController.focus.RECENT_COMMIT)); + + wrapper.instance().rememberFocus({target: null}); + assert.isTrue(rememberFocusSpy.calledWith({target: null})); + }); }); From 333958b30a2c54d25fbf3286873563e8a4c0024c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 10:14:25 -0500 Subject: [PATCH 1278/4053] Optionally preserve keyboard focus when opening commit details --- lib/controllers/recent-commits-controller.js | 5 ++++- .../recent-commits-controller.test.js | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index 041cfb8ad7..ac4b6d9743 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -67,10 +67,13 @@ export default class RecentCommitsController extends React.Component { ); } - openCommit = async ({sha}) => { + openCommit = async ({sha, preserveFocus}) => { const workdir = this.props.repository.getWorkingDirectoryPath(); const uri = CommitDetailItem.buildURI(workdir, sha); await this.props.workspace.open(uri, {pending: true}); + if (preserveFocus) { + this.setFocus(this.constructor.focus.RECENT_COMMIT); + } addEvent('open-commit-in-pane', {package: 'github', from: this.constructor.name}); } diff --git a/test/controllers/recent-commits-controller.test.js b/test/controllers/recent-commits-controller.test.js index c566b7865c..3875daf6d2 100644 --- a/test/controllers/recent-commits-controller.test.js +++ b/test/controllers/recent-commits-controller.test.js @@ -44,7 +44,7 @@ describe('RecentCommitsController', function() { assert.isTrue(wrapper.find('RecentCommitsView').prop('isLoading')); }); - describe('openCommit({sha})', function() { + describe('openCommit({sha, preserveFocus})', function() { it('opens a commit detail item', async function() { sinon.stub(atomEnv.workspace, 'open').resolves(); @@ -53,7 +53,7 @@ describe('RecentCommitsController', function() { app = React.cloneElement(app, {commits}); const wrapper = shallow(app); - await wrapper.find('RecentCommitsView').prop('openCommit')({sha: 'asdf1234'}); + await wrapper.find('RecentCommitsView').prop('openCommit')({sha: 'asdf1234', preserveFocus: false}); assert.isTrue(atomEnv.workspace.open.calledWith( `atom-github://commit-detail?workdir=${encodeURIComponent(workdirPath)}` + @@ -61,6 +61,20 @@ describe('RecentCommitsController', function() { )); }); + it('preserves keyboard focus within the RecentCommitsView when requested', async function() { + sinon.stub(atomEnv.workspace, 'open').resolves(); + + const sha = 'asdf1234'; + const commits = [commitBuilder().sha(sha).build()]; + app = React.cloneElement(app, {commits}); + + const wrapper = mount(app); + const focusSpy = sinon.stub(wrapper.find('RecentCommitsView').instance(), 'setFocus').returns(true); + + await wrapper.find('RecentCommitsView').prop('openCommit')({sha: 'asdf1234', preserveFocus: true}); + assert.isTrue(focusSpy.called); + }); + it('records an event', async function() { sinon.stub(atomEnv.workspace, 'open').resolves(); sinon.stub(reporterProxy, 'addEvent'); From d1be87da8a204b34f026b518512ed9ce2dccaf43 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 10:26:41 -0500 Subject: [PATCH 1279/4053] Use the CommitBuilder in RecentCommitView tests --- test/views/recent-commits-view.test.js | 84 +++++++++++++------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/test/views/recent-commits-view.test.js b/test/views/recent-commits-view.test.js index 99ae103ddf..19a1ca9907 100644 --- a/test/views/recent-commits-view.test.js +++ b/test/views/recent-commits-view.test.js @@ -2,13 +2,30 @@ import React from 'react'; import {shallow, mount} from 'enzyme'; import RecentCommitsView from '../../lib/views/recent-commits-view'; -import Commit from '../../lib/models/commit'; +import {commitBuilder} from '../builder/commit'; describe('RecentCommitsView', function() { - let app; + let atomEnv, app; beforeEach(function() { - app = {}} isLoading={false} />; + atomEnv = global.buildAtomEnvironment(); + + app = ( + { }} + openCommit={() => { }} + selectNextCommit={() => { }} + selectPreviousCommit={() => { }} + /> + ); + }); + + afterEach(function() { + atomEnv.destroy(); }); it('shows a placeholder while commits are empty and loading', function() { @@ -28,11 +45,7 @@ describe('RecentCommitsView', function() { }); it('renders a RecentCommitView for each commit', function() { - const commits = ['1', '2', '3'].map(sha => { - return { - getSha() { return sha; }, - }; - }); + const commits = ['1', '2', '3'].map(sha => commitBuilder().sha(sha).build()); app = React.cloneElement(app, {commits}); const wrapper = shallow(app); @@ -41,20 +54,19 @@ describe('RecentCommitsView', function() { }); it('renders emojis in the commit subject', function() { - const commits = [new Commit({ - sha: '1111111111', - authorEmail: 'pizza@unicorn.com', - authorDate: 0, - messageSubject: ':heart: :shirt: :smile:', - })]; + const commits = [commitBuilder().messageSubject(':heart: :shirt: :smile:').build()]; + app = React.cloneElement(app, {commits}); const wrapper = mount(app); - assert.deepEqual(wrapper.find('.github-RecentCommit-message').text(), '❤️ 👕 😄'); + assert.strictEqual(wrapper.find('.github-RecentCommit-message').text(), '❤️ 👕 😄'); }); it('renders an avatar corresponding to the GitHub user who authored the commit', function() { const commits = ['thr&ee@z.com', 'two@y.com', 'one@x.com'].map((authorEmail, i) => { - return new Commit({sha: '1111111111' + i, authorEmail, authorDate: 0, message: 'x'}); + return commitBuilder() + .sha(`1111111111${i}`) + .authorEmail(authorEmail) + .build(); }); app = React.cloneElement(app, {commits}); @@ -70,13 +82,13 @@ describe('RecentCommitsView', function() { }); it('renders multiple avatars for co-authored commits', function() { - const commits = [new Commit({ - sha: '1111111111', - authorEmail: 'thr&ee@z.com', - authorDate: 0, - message: 'x', - coAuthors: [{name: 'One', email: 'two@y.com'}, {name: 'Two', email: 'one@x.com'}], - })]; + const commits = [ + commitBuilder() + .authorEmail('thr&ee@z.com') + .addCoAuthor('One', 'two@y.com') + .addCoAuthor('Two', 'one@x.com') + .build(), + ]; app = React.cloneElement(app, {commits}); const wrapper = mount(app); @@ -91,12 +103,7 @@ describe('RecentCommitsView', function() { }); it("renders the commit's relative age", function() { - const commit = new Commit({ - sha: '1111111111', - authorEmail: 'me@hooray.party', - authorDate: 1519848555, - message: 'x', - }); + const commit = commitBuilder().authorDate(1519848555).build(); app = React.cloneElement(app, {commits: [commit]}); const wrapper = mount(app); @@ -104,13 +111,7 @@ describe('RecentCommitsView', function() { }); it('renders emoji in the title attribute', function() { - const commit = new Commit({ - sha: '1111111111', - authorEmail: 'me@hooray.horse', - authorDate: 0, - messageSubject: ':heart:', - messageBody: 'and a commit body', - }); + const commit = commitBuilder().messageSubject(':heart:').messageBody('and a commit body').build(); app = React.cloneElement(app, {commits: [commit]}); const wrapper = mount(app); @@ -122,13 +123,10 @@ describe('RecentCommitsView', function() { }); it('renders the full commit message in a title attribute', function() { - const commit = new Commit({ - sha: '1111111111', - authorEmail: 'me@hooray.horse', - authorDate: 0, - messageSubject: 'really really really really really really really long', - messageBody: 'and a commit body', - }); + const commit = commitBuilder() + .messageSubject('really really really really really really really long') + .messageBody('and a commit body') + .build(); app = React.cloneElement(app, {commits: [commit]}); const wrapper = mount(app); From 1cea7a5c1621226ae18643c3f62c1075855b4322 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 10:44:02 -0500 Subject: [PATCH 1280/4053] RecentCommitView keyboard navigation --- lib/views/recent-commits-view.js | 41 +++++++++++++++--------- test/views/recent-commits-view.test.js | 43 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/lib/views/recent-commits-view.js b/lib/views/recent-commits-view.js index 9faffd716d..f07f9b21e7 100644 --- a/lib/views/recent-commits-view.js +++ b/lib/views/recent-commits-view.js @@ -92,12 +92,19 @@ class RecentCommitView extends React.Component { export default class RecentCommitsView extends React.Component { static propTypes = { + // Model state commits: PropTypes.arrayOf(PropTypes.object).isRequired, isLoading: PropTypes.bool.isRequired, - undoLastCommit: PropTypes.func.isRequired, - openCommit: PropTypes.func.isRequired, selectedCommitSha: PropTypes.string.isRequired, + + // Atom environment commandRegistry: PropTypes.object.isRequired, + + // Action methods + undoLastCommit: PropTypes.func.isRequired, + openCommit: PropTypes.func.isRequired, + selectNextCommit: PropTypes.func.isRequired, + selectPreviousCommit: PropTypes.func.isRequired, }; static focus = { @@ -106,27 +113,30 @@ export default class RecentCommitsView extends React.Component { constructor(props) { super(props); - this.refRecentCommits = new RefHolder(); + this.refRoot = new RefHolder(); } setFocus(focus) { - return this.refRecentCommits.map(view => view.setFocus(focus)).getOr(false); - } + if (focus === this.constructor.focus.RECENT_COMMIT) { + return this.refRoot.map(element => element.focus()).getOr(false); + } - rememberFocus(event) { - return this.refRecentCommits.map(view => view.rememberFocus(event)).getOr(null); + return false; } - selectNextCommit() { - // okay, we should probably move the state of the selected commit into this component - // instead of using the sha, so we can more easily move to next / previous. + rememberFocus(event) { + return this.refRoot.map(element => element.contains(event.target)).getOr(false) + ? this.constructor.focus.RECENT_COMMIT + : null; } render() { return ( -
    - - +
    + + + + {this.renderCommits()}
    @@ -158,7 +168,7 @@ export default class RecentCommitsView extends React.Component { isMostRecent={i === 0} commit={commit} undoLastCommit={this.props.undoLastCommit} - openCommit={() => this.props.openCommit({sha: commit.getSha()})} + openCommit={() => this.props.openCommit({sha: commit.getSha(), preserveFocus: true})} isSelected={this.props.selectedCommitSha === commit.getSha()} /> ); @@ -166,6 +176,7 @@ export default class RecentCommitsView extends React.Component { ); } - } + + openSelectedCommit = () => this.props.openCommit({sha: this.props.selectedCommitSha, preserveFocus: false}) } diff --git a/test/views/recent-commits-view.test.js b/test/views/recent-commits-view.test.js index 19a1ca9907..81e01ad562 100644 --- a/test/views/recent-commits-view.test.js +++ b/test/views/recent-commits-view.test.js @@ -137,4 +137,47 @@ describe('RecentCommitsView', function() { 'and a commit body', ); }); + + it('opens a commit on click, preserving keyboard focus', function() { + const openCommit = sinon.spy(); + const commits = [ + commitBuilder().sha('0').build(), + commitBuilder().sha('1').build(), + commitBuilder().sha('2').build(), + ]; + const wrapper = mount(React.cloneElement(app, {commits, openCommit, selectedCommitSha: '2'})); + + wrapper.find('RecentCommitView').at(1).simulate('click'); + + assert.isTrue(openCommit.calledWith({sha: '1', preserveFocus: true})); + }); + + describe('keybindings', function() { + it('advances to the next commit on core:move-down', function() { + const selectNextCommit = sinon.spy(); + const wrapper = mount(React.cloneElement(app, {selectNextCommit})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'core:move-down'); + + assert.isTrue(selectNextCommit.called); + }); + + it('retreats to the previous commit on core:move-up', function() { + const selectPreviousCommit = sinon.spy(); + const wrapper = mount(React.cloneElement(app, {selectPreviousCommit})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'core:move-up'); + + assert.isTrue(selectPreviousCommit.called); + }); + + it('opens the currently selected commit and does not preserve focus on core:confirm', function() { + const openCommit = sinon.spy(); + const wrapper = mount(React.cloneElement(app, {openCommit, selectedCommitSha: '1234'})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'core:confirm'); + + assert.isTrue(openCommit.calledWith({sha: '1234', preserveFocus: false})); + }); + }); }); From 3cafdc1968bed5448ef12e20740a9993b68bf954 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 10:44:13 -0500 Subject: [PATCH 1281/4053] Forgot to commit part of this test, whoops --- test/controllers/recent-commits-controller.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers/recent-commits-controller.test.js b/test/controllers/recent-commits-controller.test.js index 3875daf6d2..89b4c13578 100644 --- a/test/controllers/recent-commits-controller.test.js +++ b/test/controllers/recent-commits-controller.test.js @@ -84,7 +84,7 @@ describe('RecentCommitsController', function() { app = React.cloneElement(app, {commits}); const wrapper = shallow(app); - await wrapper.instance().openCommit({sha: 'asdf1234'}); + await wrapper.instance().openCommit({sha: 'asdf1234', preserveFocus: true}); assert.isTrue(reporterProxy.addEvent.calledWith('open-commit-in-pane', { package: 'github', from: RecentCommitsController.name, From c90e2a56d10375d54f86fee1413078ddc7c4d6eb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 10:54:31 -0500 Subject: [PATCH 1282/4053] Scroll commits into view as you select them --- lib/views/recent-commits-view.js | 19 +++++++++++++++++++ test/views/recent-commits-view.test.js | 12 ++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/views/recent-commits-view.js b/lib/views/recent-commits-view.js index f07f9b21e7..99d84a7f0b 100644 --- a/lib/views/recent-commits-view.js +++ b/lib/views/recent-commits-view.js @@ -18,12 +18,31 @@ class RecentCommitView extends React.Component { isSelected: PropTypes.bool.isRequired, }; + constructor(props) { + super(props); + + this.refRoot = new RefHolder(); + } + + componentDidMount() { + if (this.props.isSelected) { + this.refRoot.map(root => root.scrollIntoViewIfNeeded(false)); + } + } + + componentDidUpdate(prevProps) { + if (this.props.isSelected && !prevProps.isSelected) { + this.refRoot.map(root => root.scrollIntoViewIfNeeded(false)); + } + } + render() { const authorMoment = moment(this.props.commit.getAuthorDate() * 1000); const fullMessage = this.props.commit.getFullMessage(); return (
  • w.prop('commit')), commits); }); + it('scrolls the selected RecentCommitView into visibility', function() { + const commits = ['0', '1', '2', '3'].map(sha => commitBuilder().sha(sha).build()); + + app = React.cloneElement(app, {commits, selectedCommitSha: '1'}); + const wrapper = mount(app); + const scrollSpy = sinon.spy(wrapper.find('RecentCommitView').at(3).getDOMNode(), 'scrollIntoViewIfNeeded'); + + wrapper.setProps({selectedCommitSha: '3'}); + + assert.isTrue(scrollSpy.calledWith(false)); + }); + it('renders emojis in the commit subject', function() { const commits = [commitBuilder().messageSubject(':heart: :shirt: :smile:').build()]; From a37a3c861bd27cb4bb238c587f64090952577f6f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 10:56:37 -0500 Subject: [PATCH 1283/4053] Keymap entries for diving and surfacing from commits --- keymaps/git.cson | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/keymaps/git.cson b/keymaps/git.cson index f6236b9a07..16f31c82d6 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -32,11 +32,6 @@ 'tab': 'core:focus-next' 'shift-tab': 'core:focus-previous' -'.github-RecentCommitsView': - 'up': 'github:recent-commit-up' - 'down': 'github:recent-commit-down' - 'enter': 'github:open-recent-commit' - '.github-StagingView.unstaged-changes-focused': 'cmd-backspace': 'github:discard-changes-in-selected-files' 'ctrl-backspace': 'github:discard-changes-in-selected-files' @@ -51,6 +46,15 @@ 'ctrl-left': 'github:dive' 'enter': 'native!' +'.github-RecentCommits': + 'enter': 'github:dive' + 'cmd-left': 'github:dive' + 'ctrl-left': 'github:dive' + +'.github-CommitDetailView': + 'cmd-right': 'github:surface' + 'ctrl-right': 'github:surface' + '.github-FilePatchView atom-text-editor:not([mini])': 'cmd-/': 'github:toggle-patch-selection-mode' 'ctrl-/': 'github:toggle-patch-selection-mode' From 643c7fda79061ffd33abe80eddacb83043dd849d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 11:00:40 -0500 Subject: [PATCH 1284/4053] Use github:dive instead of core:confirm for consistency --- lib/views/recent-commits-view.js | 2 +- test/views/recent-commits-view.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/views/recent-commits-view.js b/lib/views/recent-commits-view.js index 99d84a7f0b..dfd60105fc 100644 --- a/lib/views/recent-commits-view.js +++ b/lib/views/recent-commits-view.js @@ -155,7 +155,7 @@ export default class RecentCommitsView extends React.Component { - + {this.renderCommits()}
  • diff --git a/test/views/recent-commits-view.test.js b/test/views/recent-commits-view.test.js index 540ef46b3c..31c6625703 100644 --- a/test/views/recent-commits-view.test.js +++ b/test/views/recent-commits-view.test.js @@ -183,11 +183,11 @@ describe('RecentCommitsView', function() { assert.isTrue(selectPreviousCommit.called); }); - it('opens the currently selected commit and does not preserve focus on core:confirm', function() { + it('opens the currently selected commit and does not preserve focus on github:dive', function() { const openCommit = sinon.spy(); const wrapper = mount(React.cloneElement(app, {openCommit, selectedCommitSha: '1234'})); - atomEnv.commands.dispatch(wrapper.getDOMNode(), 'core:confirm'); + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:dive'); assert.isTrue(openCommit.calledWith({sha: '1234', preserveFocus: false})); }); From 4852d709ce42708eea9033fa94f53feee15ddc25 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 11:46:30 -0500 Subject: [PATCH 1285/4053] Focus and select the current recent commit --- lib/controllers/git-tab-controller.js | 4 +++ lib/controllers/root-controller.js | 8 +++++- lib/items/git-tab-item.js | 4 +++ lib/views/commit-detail-view.js | 27 ++++++++++++++++++- test/controllers/git-tab-controller.test.js | 9 +++++++ test/controllers/root-controller.test.js | 22 ++++++++++++++++ test/items/git-tab-item.test.js | 1 + test/views/commit-detail-view.test.js | 29 ++++++++++++++++++--- 8 files changed, 98 insertions(+), 6 deletions(-) diff --git a/lib/controllers/git-tab-controller.js b/lib/controllers/git-tab-controller.js index f3322f4c79..c1d732df4f 100644 --- a/lib/controllers/git-tab-controller.js +++ b/lib/controllers/git-tab-controller.js @@ -363,6 +363,10 @@ export default class GitTabController extends React.Component { return this.refView.map(view => view.focusAndSelectCommitPreviewButton()); } + focusAndSelectRecentCommit() { + return this.refView.map(view => view.focusAndSelectRecentCommit()); + } + quietlySelectItem(filePath, stagingStatus) { return this.refView.map(view => view.quietlySelectItem(filePath, stagingStatus)).getOr(null); } diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 2b5bc84e0c..350c25d76f 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -100,7 +100,7 @@ export default class RootController extends React.Component { this.props.commandRegistry.onDidDispatch(event => { if (event.type && event.type.startsWith('github:') - && event.detail && event.detail[0] && event.detail[0].contextCommand) { + && event.detail && event.detail[0] && event.detail[0].contextCommand) { addEvent('context-menu-action', { package: 'github', command: event.type, @@ -399,6 +399,7 @@ export default class RootController extends React.Component { config={this.props.config} sha={params.sha} + surfaceCommit={this.surfaceToRecentCommit} /> )} @@ -617,6 +618,11 @@ export default class RootController extends React.Component { return gitTab && gitTab.focusAndSelectCommitPreviewButton(); } + surfaceToRecentCommit = () => { + const gitTab = this.gitTabTracker.getComponent(); + return gitTab && gitTab.focusAndSelectRecentCommit(); + } + destroyFilePatchPaneItems() { destroyFilePatchPaneItems({onlyStaged: false}, this.props.workspace); } diff --git a/lib/items/git-tab-item.js b/lib/items/git-tab-item.js index 254c9d717a..e8de6d7cca 100644 --- a/lib/items/git-tab-item.js +++ b/lib/items/git-tab-item.js @@ -90,4 +90,8 @@ export default class GitTabItem extends React.Component { quietlySelectItem(...args) { return this.refController.map(c => c.quietlySelectItem(...args)); } + + focusAndSelectRecentCommit() { + return this.refController.map(c => c.focusAndSelectRecentCommit()); + } } diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index cdd383e616..af819f9442 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -4,9 +4,12 @@ import {emojify} from 'node-emoji'; import moment from 'moment'; import MultiFilePatchController from '../controllers/multi-file-patch-controller'; +import Commands, {Command} from '../atom/commands'; +import RefHolder from '../models/ref-holder'; export default class CommitDetailView extends React.Component { static drilledPropTypes = { + // Model properties repository: PropTypes.object.isRequired, commit: PropTypes.object.isRequired, currentRemote: PropTypes.object.isRequired, @@ -14,28 +17,41 @@ export default class CommitDetailView extends React.Component { isCommitPushed: PropTypes.bool.isRequired, itemType: PropTypes.func.isRequired, + // Atom environment workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, keymaps: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, config: PropTypes.object.isRequired, + // Action functions destroy: PropTypes.func.isRequired, + surfaceCommit: PropTypes.func.isRequired, } static propTypes = { ...CommitDetailView.drilledPropTypes, + // Controller state messageCollapsible: PropTypes.bool.isRequired, messageOpen: PropTypes.bool.isRequired, + + // Action functions toggleMessage: PropTypes.func.isRequired, } + constructor(props) { + super(props); + + this.refRoot = new RefHolder(); + } + render() { const commit = this.props.commit; return ( -
    +
    + {this.renderCommands()}

    @@ -58,12 +74,21 @@ export default class CommitDetailView extends React.Component {

    ); } + renderCommands() { + return ( + + + + ); + } + renderCommitMessageBody() { const collapsed = this.props.messageCollapsible && !this.props.messageOpen; diff --git a/test/controllers/git-tab-controller.test.js b/test/controllers/git-tab-controller.test.js index facd99b14b..68791712fb 100644 --- a/test/controllers/git-tab-controller.test.js +++ b/test/controllers/git-tab-controller.test.js @@ -221,6 +221,15 @@ describe('GitTabController', function() { assert.isTrue(focusMethod.called); }); + it('imperatively selects the recent commit', async function() { + const repository = await buildRepository(await cloneRepository('three-files')); + const wrapper = mount(await buildApp(repository)); + + const focusMethod = sinon.spy(wrapper.find('GitTabView').instance(), 'focusAndSelectRecentCommit'); + wrapper.instance().focusAndSelectRecentCommit(); + assert.isTrue(focusMethod.called); + }); + describe('focus management', function() { it('remembers the last focus reported by the view', async function() { const repository = await buildRepository(await cloneRepository()); diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index e5203adadc..6796317781 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -1314,4 +1314,26 @@ describe('RootController', function() { }); }); + describe('surfaceToRecentCommit', function() { + it('focuses and selects the recent commit', async function() { + const repository = await buildRepository(await cloneRepository('multiple-commits')); + app = React.cloneElement(app, { + repository, + startOpen: true, + startRevealed: true, + }); + const wrapper = mount(app); + + const gitTabTracker = wrapper.instance().gitTabTracker; + + const gitTab = { + focusAndSelectRecentCommit: sinon.spy(), + }; + sinon.stub(gitTabTracker, 'getComponent').returns(gitTab); + + wrapper.instance().surfaceToRecentCommit(); + assert.isTrue(gitTab.focusAndSelectRecentCommit.called); + }); + }); + }); diff --git a/test/items/git-tab-item.test.js b/test/items/git-tab-item.test.js index 09be4becd0..4bd740b459 100644 --- a/test/items/git-tab-item.test.js +++ b/test/items/git-tab-item.test.js @@ -61,6 +61,7 @@ describe('GitTabItem', function() { const focusMethods = [ 'focusAndSelectStagingItem', 'focusAndSelectCommitPreviewButton', + 'focusAndSelectRecentCommit', ]; const spies = focusMethods.reduce((map, focusMethod) => { diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js index e56c35a042..ce64003698 100644 --- a/test/views/commit-detail-view.test.js +++ b/test/views/commit-detail-view.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import {shallow} from 'enzyme'; +import {shallow, mount} from 'enzyme'; import moment from 'moment'; import dedent from 'dedent-js'; @@ -24,7 +24,7 @@ describe('CommitDetailView', function() { function buildApp(override = {}) { const props = { repository, - commit: commitBuilder().build(), + commit: commitBuilder().setMultiFileDiff().build(), messageCollapsible: false, messageOpen: true, itemType: CommitDetailItem, @@ -35,8 +35,9 @@ describe('CommitDetailView', function() { tooltips: atomEnv.tooltips, config: atomEnv.config, - destroy: () => {}, - toggleMessage: () => {}, + destroy: () => { }, + toggleMessage: () => { }, + surfaceCommit: () => { }, ...override, }; @@ -206,4 +207,24 @@ describe('CommitDetailView', function() { }); }); }); + + describe('keyboard bindings', function() { + it('surfaces the recent commit on github:surface', function() { + const surfaceCommit = sinon.spy(); + const wrapper = mount(buildApp({surfaceCommit})); + + atomEnv.commands.dispatch(wrapper.getDOMNode(), 'github:surface'); + + assert.isTrue(surfaceCommit.called); + }); + + it('surfaces from the embedded MultiFilePatchView', function() { + const surfaceCommit = sinon.spy(); + const wrapper = mount(buildApp({surfaceCommit})); + + atomEnv.commands.dispatch(wrapper.find('.github-FilePatchView').getDOMNode(), 'github:surface'); + + assert.isTrue(surfaceCommit.called); + }); + }); }); From 2a2b9b0f83dcc98bb9fe608855733e23c6a66cde Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 11:46:45 -0500 Subject: [PATCH 1286/4053] Set and remember focus on the RecentCommitView --- lib/views/git-tab-view.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/views/git-tab-view.js b/lib/views/git-tab-view.js index a2da551840..1330e299c0 100644 --- a/lib/views/git-tab-view.js +++ b/lib/views/git-tab-view.js @@ -70,6 +70,7 @@ export default class GitTabView extends React.Component { this.subscriptions = new CompositeDisposable(); this.refCommitController = new RefHolder(); + this.refRecentCommitsController = new RefHolder(); } componentDidMount() { @@ -193,6 +194,7 @@ export default class GitTabView extends React.Component { updateSelectedCoAuthors={this.props.updateSelectedCoAuthors} /> controller.rememberFocus(event)).getOr(null); } + if (!currentFocus) { + currentFocus = this.refRecentCommitsController.map(controller => controller.rememberFocus(event)).getOr(null); + } + return currentFocus; } @@ -243,6 +249,10 @@ export default class GitTabView extends React.Component { return true; } + if (this.refRecentCommitsController.map(controller => controller.setFocus(focus)).getOr(false)) { + return true; + } + return false; } From 74ef8a8eb6a6f617364d77333a915a97f9500b9f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 11:48:48 -0500 Subject: [PATCH 1287/4053] Focus refInitialFocus even if it isn't available right away --- lib/items/commit-detail-item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/items/commit-detail-item.js b/lib/items/commit-detail-item.js index fdfc39e638..a7331b3cde 100644 --- a/lib/items/commit-detail-item.js +++ b/lib/items/commit-detail-item.js @@ -89,6 +89,6 @@ export default class CommitDetailItem extends React.Component { } focus() { - this.refInitialFocus.map(focusable => focusable.focus()); + this.refInitialFocus.getPromise().then(focusable => focusable.focus()); } } From 3e65b6e9ed0b6e0f54e24d70fb6ba8de4ef537a8 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 30 Nov 2018 18:43:27 +0100 Subject: [PATCH 1288/4053] slight styling for title of commit in PR to look clickable --- styles/pr-commit-view.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/styles/pr-commit-view.less b/styles/pr-commit-view.less index 8a6d4e49bb..0863199eec 100644 --- a/styles/pr-commit-view.less +++ b/styles/pr-commit-view.less @@ -42,6 +42,10 @@ margin: 0 0 .25em 0; font-size: 1.2em; line-height: 1.4; + &:hover { + cursor: pointer; + text-decoration: underline; + } } &-avatar { From 51bd40d4fd6b42efde81462c645183570b1dc7d8 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 30 Nov 2018 20:09:38 +0100 Subject: [PATCH 1289/4053] add oid field to pr commit query & rename abbreviatedOid to shortSha for clarity --- .../issueishDetailContainerQuery.graphql.js | 16 ++++++++++++---- .../issueishDetailViewRefetchQuery.graphql.js | 16 ++++++++++++---- .../__generated__/prCommitView_item.graphql.js | 14 +++++++++++--- .../__generated__/prCommitsViewQuery.graphql.js | 16 ++++++++++++---- lib/views/pr-commit-view.js | 14 ++++++++------ 5 files changed, 55 insertions(+), 21 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 73d262cf8c..8e537f3d56 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash fe501fb51956752e2d45ff061c36622e + * @relayHash d760438e6f0650bc4ba45ae430d56116 */ /* eslint-disable */ @@ -476,7 +476,8 @@ fragment prCommitView_item on Commit { } messageHeadline messageBody - abbreviatedOid + shortSha: abbreviatedOid + sha: oid url } */ @@ -997,7 +998,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueishDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueishDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...issueishDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueishDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueishDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueishDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueishDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...issueishDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueishDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueishDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1169,11 +1170,18 @@ return { }, { "kind": "ScalarField", - "alias": null, + "alias": "shortSha", "name": "abbreviatedOid", "args": null, "storageKey": null }, + { + "kind": "ScalarField", + "alias": "sha", + "name": "oid", + "args": null, + "storageKey": null + }, v12 ] }, diff --git a/lib/views/__generated__/issueishDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/issueishDetailViewRefetchQuery.graphql.js index 3b5eeaf1e0..d300687d26 100644 --- a/lib/views/__generated__/issueishDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/issueishDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 2918a11cbaa8f9b4f358a3913dab315c + * @relayHash 7a789201c89c0ffa32e9490d1bf7c181 */ /* eslint-disable */ @@ -446,7 +446,8 @@ fragment prCommitView_item on Commit { } messageHeadline messageBody - abbreviatedOid + shortSha: abbreviatedOid + sha: oid url } */ @@ -947,7 +948,7 @@ return { "operationKind": "query", "name": "issueishDetailViewRefetchQuery", "id": null, - "text": "query issueishDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueishDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...issueishDetailView_issueish_4cAEh0\n id\n }\n}\n\nfragment issueishDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueishDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "text": "query issueishDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueishDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...issueishDetailView_issueish_4cAEh0\n id\n }\n}\n\nfragment issueishDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueishDetailView_issueish_4cAEh0 on IssueOrPullRequest {\n __typename\n ... on Node {\n id\n }\n ... on Issue {\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n }\n ... on PullRequest {\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1155,11 +1156,18 @@ return { }, { "kind": "ScalarField", - "alias": null, + "alias": "shortSha", "name": "abbreviatedOid", "args": null, "storageKey": null }, + { + "kind": "ScalarField", + "alias": "sha", + "name": "oid", + "args": null, + "storageKey": null + }, v11 ] }, diff --git a/lib/views/__generated__/prCommitView_item.graphql.js b/lib/views/__generated__/prCommitView_item.graphql.js index c5d7709a26..2d1203207c 100644 --- a/lib/views/__generated__/prCommitView_item.graphql.js +++ b/lib/views/__generated__/prCommitView_item.graphql.js @@ -18,7 +18,8 @@ export type prCommitView_item = {| |}, +messageHeadline: string, +messageBody: string, - +abbreviatedOid: string, + +shortSha: string, + +sha: any, +url: any, +$refType: prCommitView_item$ref, |}; @@ -80,11 +81,18 @@ const node/*: ConcreteFragment*/ = { }, { "kind": "ScalarField", - "alias": null, + "alias": "shortSha", "name": "abbreviatedOid", "args": null, "storageKey": null }, + { + "kind": "ScalarField", + "alias": "sha", + "name": "oid", + "args": null, + "storageKey": null + }, { "kind": "ScalarField", "alias": null, @@ -95,5 +103,5 @@ const node/*: ConcreteFragment*/ = { ] }; // prettier-ignore -(node/*: any*/).hash = 'c7c00b19a2fd2a18e4c1bab7f5f252ff'; +(node/*: any*/).hash = '2bd193bec5d758f465d9428ff3cd8a09'; module.exports = node; diff --git a/lib/views/__generated__/prCommitsViewQuery.graphql.js b/lib/views/__generated__/prCommitsViewQuery.graphql.js index f3c5e4ff13..7d20b34ac5 100644 --- a/lib/views/__generated__/prCommitsViewQuery.graphql.js +++ b/lib/views/__generated__/prCommitsViewQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 9ee1d26899a0ab5d2eeeee7dbd86fb3d + * @relayHash 86632ba0fe5f43bd343d798b397ad81b */ /* eslint-disable */ @@ -73,7 +73,8 @@ fragment prCommitView_item on Commit { } messageHeadline messageBody - abbreviatedOid + shortSha: abbreviatedOid + sha: oid url } */ @@ -147,7 +148,7 @@ return { "operationKind": "query", "name": "prCommitsViewQuery", "id": null, - "text": "query prCommitsViewQuery(\n $commitCount: Int!\n $commitCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prCommitsView_pullRequest_38TpXw\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "text": "query prCommitsViewQuery(\n $commitCount: Int!\n $commitCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prCommitsView_pullRequest_38TpXw\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -331,11 +332,18 @@ return { }, { "kind": "ScalarField", - "alias": null, + "alias": "shortSha", "name": "abbreviatedOid", "args": null, "storageKey": null }, + { + "kind": "ScalarField", + "alias": "sha", + "name": "oid", + "args": null, + "storageKey": null + }, v4 ] }, diff --git a/lib/views/pr-commit-view.js b/lib/views/pr-commit-view.js index 3f2b3157bf..ed7d865fc6 100644 --- a/lib/views/pr-commit-view.js +++ b/lib/views/pr-commit-view.js @@ -19,7 +19,8 @@ export class PrCommitView extends React.Component { }).isRequired, messageBody: PropTypes.string, messageHeadline: PropTypes.string.isRequired, - abbreviatedOid: PropTypes.string.isRequired, + shortSha: PropTypes.string.isRequired, + sha: PropTypes.string.isRequired, url: PropTypes.string.isRequired, }).isRequired, onBranch: PropTypes.bool.isRequired, @@ -41,11 +42,11 @@ export class PrCommitView extends React.Component { } openCommitDetailItem = () => { - return this.props.onBranch ? this.props.openCommit({sha: this.props.item.abbreviatedOid}) : null; + return this.props.onBranch ? this.props.openCommit({sha: this.props.item.shortSha}) : null; } render() { - const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; + const {messageHeadline, messageBody, shortSha, url} = this.props.item; const {avatarUrl, name, date} = this.props.item.committer; return (
    @@ -77,8 +78,8 @@ export class PrCommitView extends React.Component {
    @@ -96,7 +97,8 @@ export default createFragmentContainer(PrCommitView, { } messageHeadline messageBody - abbreviatedOid + shortSha: abbreviatedOid + sha: oid url }`, }); From 0bed1f6c77b2fb1a4b6b4093e2d8140457aa4b26 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 30 Nov 2018 20:12:04 +0100 Subject: [PATCH 1290/4053] fix test --- test/views/pr-commits-view.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/views/pr-commits-view.test.js b/test/views/pr-commits-view.test.js index 0d6e226d6c..5b0cbf705e 100644 --- a/test/views/pr-commits-view.test.js +++ b/test/views/pr-commits-view.test.js @@ -11,7 +11,8 @@ const commitSpec = { date: '2018-05-16T21:54:24.500Z', }, messageHeadline: 'This one weird trick for getting to the moon will blow your mind 🚀', - abbreviatedOid: 'bad1dea', + shortSha: 'bad1dea', + sha: 'bad1deaea3d816383721478fc631b5edd0c2b370', url: 'https://github.com/atom/github/pull/1684/commits/bad1deaea3d816383721478fc631b5edd0c2b370', }; From 7b8e564fdd763dca103548297badde0bebf3ae58 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 30 Nov 2018 20:12:17 +0100 Subject: [PATCH 1291/4053] use the long sha for opening commit item --- lib/views/pr-commit-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/pr-commit-view.js b/lib/views/pr-commit-view.js index ed7d865fc6..e1fa730279 100644 --- a/lib/views/pr-commit-view.js +++ b/lib/views/pr-commit-view.js @@ -42,7 +42,7 @@ export class PrCommitView extends React.Component { } openCommitDetailItem = () => { - return this.props.onBranch ? this.props.openCommit({sha: this.props.item.shortSha}) : null; + return this.props.onBranch ? this.props.openCommit({sha: this.props.item.sha}) : null; } render() { From d868567c4326071af6d0431e2cc528b7b9f25852 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 30 Nov 2018 20:42:03 +0100 Subject: [PATCH 1292/4053] clickable styling should only apply to clickable nodes! DUH! --- styles/pr-commit-view.less | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/styles/pr-commit-view.less b/styles/pr-commit-view.less index 0863199eec..5847837599 100644 --- a/styles/pr-commit-view.less +++ b/styles/pr-commit-view.less @@ -42,10 +42,11 @@ margin: 0 0 .25em 0; font-size: 1.2em; line-height: 1.4; - &:hover { - cursor: pointer; - text-decoration: underline; - } + } + + &-messageHeadline.clickable:hover { + cursor: pointer; + text-decoration: underline; } &-avatar { From 4cf2d295c0c508c259d73772795abafe09a8981e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 30 Nov 2018 20:52:01 +0100 Subject: [PATCH 1293/4053] add tests for opening commits from PR view --- test/views/pr-commit-view.test.js | 34 ++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/test/views/pr-commit-view.test.js b/test/views/pr-commit-view.test.js index db9c453c95..c98e88b4e5 100644 --- a/test/views/pr-commit-view.test.js +++ b/test/views/pr-commit-view.test.js @@ -11,21 +11,25 @@ const defaultProps = { date: '2018-05-16T21:54:24.500Z', }, messageHeadline: 'This one weird trick for getting to the moon will blow your mind 🚀', - abbreviatedOid: 'bad1dea', + shortSha: 'bad1dea', + sha: 'bad1deaea3d816383721478fc631b5edd0c2b370', url: 'https://github.com/atom/github/pull/1684/commits/bad1deaea3d816383721478fc631b5edd0c2b370', }; -const getProps = function(overrides = {}) { +const getProps = function(itemOverrides = {}, overrides = {}) { return { item: { ...defaultProps, - ...overrides, + ...itemOverrides, }, + onBranch: true, + openCommit: () => {}, + ...overrides, }; }; describe('PrCommitView', function() { - function buildApp(overrideProps = {}) { - return ; + function buildApp(itemOverrides = {}, overrides = {}) { + return ; } it('renders the commit view for commits without message body', function() { const wrapper = shallow(buildApp({})); @@ -70,4 +74,24 @@ describe('PrCommitView', function() { assert.lengthOf(wrapper.find('.github-PrCommitView-moreText'), 0); assert.deepEqual(wrapper.find('.github-PrCommitView-moreButton').text(), 'show more...'); }); + + describe('if PR is checked out', function() { + it('shows message headlines as clickable', function() { + const wrapper = shallow(buildApp({})); + assert.isTrue(wrapper.find('.github-PrCommitView-messageHeadline').at(0).hasClass('clickable')); + }); + + it('opens a commit with the full sha when title is clicked', function() { + const openCommit = sinon.spy(); + const wrapper = shallow(buildApp({sha: 'longsha123'}, {openCommit})); + wrapper.find('.github-PrCommitView-messageHeadline').at(0).simulate('click'); + assert.isTrue(openCommit.calledWith({sha: 'longsha123'})); + }); + }); + + it('does not show message headlines as clickable if PR is not checked out', function() { + const wrapper = shallow(buildApp({}, {onBranch: false})); + assert.isFalse(wrapper.find('.github-PrCommitView-messageHeadline').at(0).hasClass('clickable')); + }); + }); From b720c7da8374d70ff239256d1b6877bb9e129594 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 30 Nov 2018 15:39:32 -0500 Subject: [PATCH 1294/4053] Incremental improvement to GitTab focus I've done a pass through the GitTab components and improved the focus management code somewhat. It's still ref soup, imperative, and pretty verbose, but at least now it's a bit more internally consistent. GitTab components that may receive focus implement four methods: * `getFocus(element)` returns the logical focus symbol corresponding to a DOM element, or null if the element is unrecognized. * `setFocus(symbol)` brings focus to the DOM element corresponding to a logical focus symbol. It returns true if an element was found and focused successfully and false otherwise. * `advanceFocusFrom(lastFocus)` returns a Promise that resolves to the logical focus symbol after a given symbol. * `retreatFocusFrom(lastFocus)` returns a Promise that resolves to the logical focus symbol before a given symbol. --- keymaps/git.cson | 1 + lib/controllers/commit-controller.js | 18 +- lib/controllers/git-tab-controller.js | 2 +- lib/controllers/recent-commits-controller.js | 12 +- lib/views/commit-view.js | 84 +++---- lib/views/git-tab-view.js | 80 +++---- lib/views/recent-commits-view.js | 25 +- lib/views/staging-view.js | 45 +++- test/controllers/commit-controller.test.js | 27 +-- .../recent-commits-controller.test.js | 16 +- test/views/commit-view.test.js | 215 ++++++++---------- test/views/git-tab-view.test.js | 99 +++++--- test/views/recent-commits-view.test.js | 28 +++ test/views/staging-view.test.js | 130 +++++++---- 14 files changed, 441 insertions(+), 341 deletions(-) diff --git a/keymaps/git.cson b/keymaps/git.cson index 16f31c82d6..d7b6d2c1d8 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -39,6 +39,7 @@ '.github-CommitView-editor atom-text-editor:not([mini])': 'cmd-enter': 'github:commit' 'ctrl-enter': 'github:commit' + 'tab': 'core:focus-next' 'shift-tab': 'core:focus-previous' '.github-CommitView-commitPreview': diff --git a/lib/controllers/commit-controller.js b/lib/controllers/commit-controller.js index b07be5d280..7183eeb31a 100644 --- a/lib/controllers/commit-controller.js +++ b/lib/controllers/commit-controller.js @@ -76,7 +76,7 @@ export default class CommitController extends React.Component { }), this.props.workspace.onDidDestroyPaneItem(async ({item}) => { if (this.props.repository.isPresent() && item.getPath && item.getPath() === this.getCommitMessagePath() && - this.getCommitMessageEditors().length === 0) { + this.getCommitMessageEditors().length === 0) { // we closed the last editor pointing to the commit message file try { this.commitMessageBuffer.setText(await fs.readFile(this.getCommitMessagePath(), {encoding: 'utf8'})); @@ -252,24 +252,20 @@ export default class CommitController extends React.Component { this.grammarSubscription.dispose(); } - rememberFocus(event) { - return this.refCommitView.map(view => view.rememberFocus(event)).getOr(null); + getFocus(element) { + return this.refCommitView.map(view => view.getFocus(element)).getOr(null); } setFocus(focus) { return this.refCommitView.map(view => view.setFocus(focus)).getOr(false); } - advanceFocus(...args) { - return this.refCommitView.map(view => view.advanceFocus(...args)).getOr(false); + advanceFocusFrom(...args) { + return this.refCommitView.map(view => view.advanceFocusFrom(...args)).getOr(false); } - retreatFocus(...args) { - return this.refCommitView.map(view => view.retreatFocus(...args)).getOr(false); - } - - hasFocusAtBeginning() { - return this.refCommitView.map(view => view.hasFocusAtBeginning()).getOr(false); + retreatFocusFrom(...args) { + return this.refCommitView.map(view => view.retreatFocusFrom(...args)).getOr(false); } toggleCommitPreview() { diff --git a/lib/controllers/git-tab-controller.js b/lib/controllers/git-tab-controller.js index c1d732df4f..f70da000dc 100644 --- a/lib/controllers/git-tab-controller.js +++ b/lib/controllers/git-tab-controller.js @@ -338,7 +338,7 @@ export default class GitTabController extends React.Component { } rememberLastFocus(event) { - this.lastFocus = this.refView.map(view => view.rememberFocus(event)).getOr(null) || GitTabView.focus.STAGING; + this.lastFocus = this.refView.map(view => view.getFocus(event.target)).getOr(null) || GitTabView.focus.STAGING; } restoreFocus() { diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index ac4b6d9743..2967983062 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -94,11 +94,19 @@ export default class RecentCommitsController extends React.Component { } } - rememberFocus(event) { - return this.refView.map(view => view.rememberFocus(event)).getOr(null); + getFocus(element) { + return this.refView.map(view => view.getFocus(element)).getOr(null); } setFocus(focus) { return this.refView.map(view => view.setFocus(focus)).getOr(false); } + + advanceFocusFrom(focus) { + return this.refView.map(view => view.advanceFocusFrom(focus)).getOr(Promise.resolve(false)); + } + + retreatFocusFrom(focus) { + return this.refView.map(view => view.retreatFocusFrom(focus)).getOr(Promise.resolve(false)); + } } diff --git a/lib/views/commit-view.js b/lib/views/commit-view.js index 6013162cfa..fc35d53704 100644 --- a/lib/views/commit-view.js +++ b/lib/views/commit-view.js @@ -7,6 +7,8 @@ import Select from 'react-select'; import Tooltip from '../atom/tooltip'; import AtomTextEditor from '../atom/atom-text-editor'; import CoAuthorForm from './co-author-form'; +import RecentCommitsView from './recent-commits-view'; +import StagingView from './staging-view'; import Commands, {Command} from '../atom/commands'; import RefHolder from '../models/ref-holder'; import Author from '../models/author'; @@ -30,6 +32,10 @@ export default class CommitView extends React.Component { COMMIT_BUTTON: Symbol('commit-button'), }; + static firstFocus = CommitView.focus.COMMIT_PREVIEW_BUTTON; + + static lastFocus = CommitView.focus.COMMIT_BUTTON; + static propTypes = { workspace: PropTypes.object.isRequired, config: PropTypes.object.isRequired, @@ -569,15 +575,7 @@ export default class CommitView extends React.Component { return this.refRoot.map(element => element.contains(document.activeElement)).getOr(false); } - hasFocusEditor() { - return this.refEditorComponent.map(editor => editor.contains(document.activeElement)).getOr(false); - } - - hasFocusAtBeginning() { - return this.refCommitPreviewButton.map(button => button.contains(document.activeElement)).getOr(false); - } - - getFocus(element = document.activeElement) { + getFocus(element) { if (this.refCommitPreviewButton.map(button => button.contains(element)).getOr(false)) { return CommitView.focus.COMMIT_PREVIEW_BUTTON; } @@ -601,10 +599,6 @@ export default class CommitView extends React.Component { return null; } - rememberFocus(event) { - return this.getFocus(event.target); - } - setFocus(focus) { let fallback = false; const focusElement = element => { @@ -657,19 +651,23 @@ export default class CommitView extends React.Component { return false; } - advanceFocus(event) { + advanceFocusFrom(focus) { const f = this.constructor.focus; - const current = this.getFocus(); - if (current === f.EDITOR) { - // Let the editor handle it - return true; - } let next = null; - switch (current) { + switch (focus) { case f.COMMIT_PREVIEW_BUTTON: next = f.EDITOR; break; + case f.EDITOR: + if (this.state.showCoAuthorInput) { + next = f.COAUTHOR_INPUT; + } else if (this.props.isMerging) { + next = f.ABORT_MERGE_BUTTON; + } else { + next = f.COMMIT_BUTTON; + } + break; case f.COAUTHOR_INPUT: next = this.props.isMerging ? f.ABORT_MERGE_BUTTON : f.COMMIT_BUTTON; break; @@ -677,57 +675,41 @@ export default class CommitView extends React.Component { next = f.COMMIT_BUTTON; break; case f.COMMIT_BUTTON: - // End of tab navigation. Prevent cycling. - event.stopPropagation(); - return true; + next = RecentCommitsView.firstFocus; + break; } - if (next !== null) { - this.setFocus(next); - event.stopPropagation(); - - return true; - } else { - return false; - } + return Promise.resolve(next); } - retreatFocus(event) { + retreatFocusFrom(focus) { const f = this.constructor.focus; - const current = this.getFocus(); - let next = null; - switch (current) { + let previous = null; + switch (focus) { case f.COMMIT_BUTTON: if (this.props.isMerging) { - next = f.ABORT_MERGE_BUTTON; + previous = f.ABORT_MERGE_BUTTON; } else if (this.state.showCoAuthorInput) { - next = f.COAUTHOR_INPUT; + previous = f.COAUTHOR_INPUT; } else { - next = f.EDITOR; + previous = f.EDITOR; } break; case f.ABORT_MERGE_BUTTON: - next = this.state.showCoAuthorInput ? f.COAUTHOR_INPUT : f.EDITOR; + previous = this.state.showCoAuthorInput ? f.COAUTHOR_INPUT : f.EDITOR; break; case f.COAUTHOR_INPUT: - next = f.EDITOR; + previous = f.EDITOR; break; case f.EDITOR: - next = f.COMMIT_PREVIEW_BUTTON; + previous = f.COMMIT_PREVIEW_BUTTON; break; case f.COMMIT_PREVIEW_BUTTON: - // Allow the GitTabView to retreat focus back to the last StagingView list. - return false; + previous = StagingView.lastFocus; + break; } - if (next !== null) { - this.setFocus(next); - event.stopPropagation(); - - return true; - } else { - return false; - } + return Promise.resolve(previous); } } diff --git a/lib/views/git-tab-view.js b/lib/views/git-tab-view.js index 1330e299c0..4ec74fde66 100644 --- a/lib/views/git-tab-view.js +++ b/lib/views/git-tab-view.js @@ -224,35 +224,22 @@ export default class GitTabView extends React.Component { this.props.initializeRepo(initPath); } - rememberFocus(event) { - let currentFocus = null; - - currentFocus = this.props.refStagingView.map(view => view.rememberFocus(event)).getOr(null); - - if (!currentFocus) { - currentFocus = this.refCommitController.map(controller => controller.rememberFocus(event)).getOr(null); - } - - if (!currentFocus) { - currentFocus = this.refRecentCommitsController.map(controller => controller.rememberFocus(event)).getOr(null); + getFocus(element) { + for (const ref of [this.props.refStagingView, this.refCommitController, this.refRecentCommitsController]) { + const focus = ref.map(sub => sub.getFocus(element)).getOr(null); + if (focus !== null) { + return focus; + } } - - return currentFocus; + return null; } setFocus(focus) { - if (this.props.refStagingView.map(view => view.setFocus(focus)).getOr(false)) { - return true; - } - - if (this.refCommitController.map(controller => controller.setFocus(focus)).getOr(false)) { - return true; - } - - if (this.refRecentCommitsController.map(controller => controller.setFocus(focus)).getOr(false)) { - return true; + for (const ref of [this.props.refStagingView, this.refCommitController, this.refRecentCommitsController]) { + if (ref.map(sub => sub.setFocus(focus)).getOr(false)) { + return true; + } } - return false; } @@ -261,39 +248,34 @@ export default class GitTabView extends React.Component { } async advanceFocus(evt) { - // Advance focus within the CommitView if it's there - if (this.refCommitController.map(c => c.advanceFocus(evt)).getOr(false)) { - return; - } + const currentFocus = this.getFocus(document.activeElement); + let nextSeen = false; - // Advance focus to the next staging view list, if it's there - if (await this.props.refStagingView.map(view => view.activateNextList()).getOr(false)) { - evt.stopPropagation(); - return; - } - - // Advance focus from the staging view lists to the CommitView - if (this.refCommitController.map(c => c.setFocus(GitTabView.focus.COMMIT_PREVIEW_BUTTON)).getOr(false)) { - evt.stopPropagation(); + for (const subHolder of [this.props.refStagingView, this.refCommitController, this.refRecentCommitsController]) { + const next = await subHolder.map(sub => sub.advanceFocusFrom(currentFocus)).getOr(null); + if (next !== null && !nextSeen) { + nextSeen = true; + evt.stopPropagation(); + if (next !== currentFocus) { + this.setFocus(next); + } + } } } async retreatFocus(evt) { - // Retreat focus within the CommitView if it's there - if (this.refCommitController.map(c => c.retreatFocus(evt)).getOr(false)) { - return; - } + const currentFocus = this.getFocus(document.activeElement); + let previousSeen = false; - if (this.refCommitController.map(c => c.hasFocusAtBeginning()).getOr(false)) { - // Retreat focus from the beginning of the CommitView to the end of the StagingView - if (await this.props.refStagingView.map(view => view.activateLastList()).getOr(null)) { - this.setFocus(GitTabView.focus.STAGING); + for (const subHolder of [this.refRecentCommitsController, this.refCommitController, this.props.refStagingView]) { + const previous = await subHolder.map(sub => sub.retreatFocusFrom(currentFocus)).getOr(null); + if (previous !== null && !previousSeen) { + previousSeen = true; evt.stopPropagation(); + if (previous !== currentFocus) { + this.setFocus(previous); + } } - } else if (await this.props.refStagingView.map(c => c.activatePreviousList()).getOr(null)) { - // Retreat focus within the StagingView - this.setFocus(GitTabView.focus.STAGING); - evt.stopPropagation(); } } diff --git a/lib/views/recent-commits-view.js b/lib/views/recent-commits-view.js index dfd60105fc..ac64db3d40 100644 --- a/lib/views/recent-commits-view.js +++ b/lib/views/recent-commits-view.js @@ -7,6 +7,7 @@ import {emojify} from 'node-emoji'; import Commands, {Command} from '../atom/commands'; import RefHolder from '../models/ref-holder'; +import CommitView from './commit-view'; import Timeago from './timeago'; class RecentCommitView extends React.Component { @@ -130,6 +131,10 @@ export default class RecentCommitsView extends React.Component { RECENT_COMMIT: Symbol('recent_commit'), }; + static firstFocus = RecentCommitsView.focus.RECENT_COMMIT; + + static lastFocus = RecentCommitsView.focus.RECENT_COMMIT; + constructor(props) { super(props); this.refRoot = new RefHolder(); @@ -143,8 +148,8 @@ export default class RecentCommitsView extends React.Component { return false; } - rememberFocus(event) { - return this.refRoot.map(element => element.contains(event.target)).getOr(false) + getFocus(element) { + return this.refRoot.map(e => e.contains(element)).getOr(false) ? this.constructor.focus.RECENT_COMMIT : null; } @@ -198,4 +203,20 @@ export default class RecentCommitsView extends React.Component { } openSelectedCommit = () => this.props.openCommit({sha: this.props.selectedCommitSha, preserveFocus: false}) + + advanceFocusFrom(focus) { + if (focus === this.constructor.focus.RECENT_COMMIT) { + return Promise.resolve(this.constructor.focus.RECENT_COMMIT); + } + + return Promise.resolve(null); + } + + retreatFocusFrom(focus) { + if (focus === this.constructor.focus.RECENT_COMMIT) { + return Promise.resolve(CommitView.lastFocus); + } + + return Promise.resolve(null); + } } diff --git a/lib/views/staging-view.js b/lib/views/staging-view.js index 8629ff8953..474db1aaa0 100644 --- a/lib/views/staging-view.js +++ b/lib/views/staging-view.js @@ -12,6 +12,7 @@ import ObserveModel from './observe-model'; import MergeConflictListItemView from './merge-conflict-list-item-view'; import CompositeListSelection from '../models/composite-list-selection'; import ResolutionProgress from '../models/conflicts/resolution-progress'; +import CommitView from './commit-view'; import RefHolder from '../models/ref-holder'; import ChangedFileItem from '../items/changed-file-item'; import Commands, {Command} from '../atom/commands'; @@ -43,7 +44,7 @@ function calculateTruncatedLists(lists) { }, {source: {}}); } -const noop = () => {}; +const noop = () => { }; const MAXIMUM_LISTED_ENTRIES = 1000; @@ -76,6 +77,10 @@ export default class StagingView extends React.Component { STAGING: Symbol('staging'), }; + static firstFocus = StagingView.focus.STAGING; + + static lastFocus = StagingView.focus.STAGING; + constructor(props) { super(props); autobind( @@ -226,12 +231,12 @@ export default class StagingView extends React.Component {
    {this.renderTruncatedMessage(this.props.unstagedChanges)}
    - { this.renderMergeConflicts() } + {this.renderMergeConflicts()}
    - Staged Changes + Staged Changes
    ); } @@ -81,7 +81,7 @@ export default createFragmentContainer(BareCommitView, { } } authoredByCommitter - oid message messageHeadlineHTML commitUrl + sha:oid message messageHeadlineHTML commitUrl } `, }); From f8d1341342171fa670dd93e4907bdaef65fb6819 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 30 Nov 2018 22:11:30 +0100 Subject: [PATCH 1303/4053] drill em props! --- lib/views/issueish-detail-view.js | 2 ++ lib/views/issueish-timeline-view.js | 7 +++++++ lib/views/timeline-items/commit-view.js | 2 ++ lib/views/timeline-items/commits-view.js | 4 +++- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/views/issueish-detail-view.js b/lib/views/issueish-detail-view.js index 2b63163849..27312dff45 100644 --- a/lib/views/issueish-detail-view.js +++ b/lib/views/issueish-detail-view.js @@ -175,6 +175,8 @@ export class BareIssueishDetailView extends React.Component { {this.renderEmojiReactions(issueish)} diff --git a/lib/views/issueish-timeline-view.js b/lib/views/issueish-timeline-view.js index 0d376d958a..6523173807 100644 --- a/lib/views/issueish-timeline-view.js +++ b/lib/views/issueish-timeline-view.js @@ -77,6 +77,8 @@ export default class IssueishTimelineView extends React.Component { pullRequest: PropTypes.shape({ timeline: TimelineConnectionPropType, }), + onBranch: PropTypes.bool.isRequired, + openCommit: PropTypes.func.isRequired, } constructor(props) { @@ -98,6 +100,10 @@ export default class IssueishTimelineView extends React.Component {
    {groupedEdges.map(({type, edges}) => { const Component = timelineItems[type]; + const propsForCommits = { + onBranch: this.props.onBranch, + openCommit: this.props.openCommit, + }; if (Component) { return ( e.node)} issueish={issueish} switchToIssueish={this.props.switchToIssueish} + {...(Component === CommitsView && propsForCommits)} /> ); } else { diff --git a/lib/views/timeline-items/commit-view.js b/lib/views/timeline-items/commit-view.js index 988435f73f..6c902bc2ff 100644 --- a/lib/views/timeline-items/commit-view.js +++ b/lib/views/timeline-items/commit-view.js @@ -7,6 +7,8 @@ import Octicon from '../../atom/octicon'; export class BareCommitView extends React.Component { static propTypes = { item: PropTypes.object.isRequired, + onBranch: PropTypes.bool.isRequired, + openCommit: PropTypes.func.isRequired, } authoredByCommitter(commit) { diff --git a/lib/views/timeline-items/commits-view.js b/lib/views/timeline-items/commits-view.js index 7d296507cc..9118646abd 100644 --- a/lib/views/timeline-items/commits-view.js +++ b/lib/views/timeline-items/commits-view.js @@ -17,6 +17,8 @@ export class BareCommitsView extends React.Component { }).isRequired, }).isRequired, ).isRequired, + onBranch: PropTypes.bool.isRequired, + openCommit: PropTypes.func.isRequired, } render() { @@ -46,7 +48,7 @@ export class BareCommitsView extends React.Component { renderCommits() { return this.props.nodes.map(node => { - return ; + return ; }); } From e5d4317deb8d603da15ab30e195124411f3e6396 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 30 Nov 2018 22:20:44 +0100 Subject: [PATCH 1304/4053] open commit detail item when commit header is clicked from PR timeline. --- lib/views/timeline-items/commit-view.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/views/timeline-items/commit-view.js b/lib/views/timeline-items/commit-view.js index 6c902bc2ff..2b62988a01 100644 --- a/lib/views/timeline-items/commit-view.js +++ b/lib/views/timeline-items/commit-view.js @@ -1,6 +1,7 @@ import React from 'react'; import {graphql, createFragmentContainer} from 'react-relay'; import PropTypes from 'prop-types'; +import cx from 'classnames'; import Octicon from '../../atom/octicon'; @@ -31,6 +32,10 @@ export class BareCommitView extends React.Component { return false; } + openCommitDetailItem = () => { + return this.props.onBranch ? this.props.openCommit({sha: this.props.item.sha}) : null; + } + renderCommitter(commit) { if (!this.authoredByCommitter(commit)) { return ( @@ -57,9 +62,10 @@ export class BareCommitView extends React.Component { {this.renderCommitter(commit)} {commit.sha.slice(0, 8)}
    From a2babbb058f5706a61a0fadc77521b235c552f1a Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 30 Nov 2018 22:23:00 +0100 Subject: [PATCH 1305/4053] add some clickable styling --- styles/pr-timeline.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/styles/pr-timeline.less b/styles/pr-timeline.less index 72a07dbcc9..fcd9324095 100644 --- a/styles/pr-timeline.less +++ b/styles/pr-timeline.less @@ -104,6 +104,10 @@ overflow: hidden; max-width: 0; // this makes sure the ellipsis work in table-cell width: 100%; + &.clickable:hover { + cursor: pointer; + text-decoration: underline; + } } .commit-sha { From 97c42ad56cafc626a12a54a4c024f46fc2e16954 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 30 Nov 2018 14:29:49 -0800 Subject: [PATCH 1306/4053] tilde 1, relay 0. For now at least. I was afraid to rename the props from `issueish` to `pullRequest` because I thought it would fuck up Relay, but actually it wasn't too bad. --- .../issueishDetailContainerQuery.graphql.js | 498 +++++++++--------- ...eishDetailController_repository.graphql.js | 93 ++-- lib/controllers/issueish-detail-controller.js | 7 +- .../prDetailViewRefetchQuery.graphql.js | 24 +- ...js => prDetailView_pullRequest.graphql.js} | 10 +- lib/views/pr-detail-view.js | 44 +- 6 files changed, 360 insertions(+), 316 deletions(-) rename lib/views/__generated__/{prDetailView_issueish.graphql.js => prDetailView_pullRequest.graphql.js} (95%) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index f187ce54f6..f5ec6b4c46 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 6d9cf8ec92db318e118f5f32a23c5af4 + * @relayHash 76d06aecf543e593ddcbc07aca4e478a */ /* eslint-disable */ @@ -63,6 +63,12 @@ fragment issueishDetailController_repository_1mXVvq on Repository { number ...issueDetailView_issueish_4cAEh0 } + ... on Node { + id + } + } + pullRequest: issueOrPullRequest(number: $issueishNumber) { + __typename ... on PullRequest { title number @@ -78,7 +84,7 @@ fragment issueishDetailController_repository_1mXVvq on Repository { sshUrl id } - ...prDetailView_issueish_4cAEh0 + ...prDetailView_pullRequest_4cAEh0 } ... on Node { id @@ -143,7 +149,7 @@ fragment issueDetailView_issueish_4cAEh0 on Issue { } } -fragment prDetailView_issueish_4cAEh0 on PullRequest { +fragment prDetailView_pullRequest_4cAEh0 on PullRequest { __typename ... on Node { id @@ -610,11 +616,10 @@ v7 = { }, v8 = [ { - "kind": "ScalarField", - "alias": null, - "name": "totalCount", - "args": null, - "storageKey": null + "kind": "Variable", + "name": "number", + "variableName": "issueishNumber", + "type": "Int!" } ], v9 = { @@ -627,95 +632,42 @@ v9 = { v10 = { "kind": "ScalarField", "alias": null, - "name": "url", + "name": "number", "args": null, "storageKey": null }, v11 = { "kind": "ScalarField", "alias": null, - "name": "isCrossRepository", + "name": "state", "args": null, "storageKey": null }, -v12 = [ - { - "kind": "Variable", - "name": "after", - "variableName": "commitCursor", - "type": "String" - }, - { - "kind": "Variable", - "name": "first", - "variableName": "commitCount", - "type": "Int" - } -], -v13 = { - "kind": "LinkedField", - "alias": null, - "name": "pageInfo", - "storageKey": null, - "args": null, - "concreteType": "PageInfo", - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "endCursor", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "hasNextPage", - "args": null, - "storageKey": null - } - ] -}, -v14 = { +v12 = { "kind": "ScalarField", "alias": null, - "name": "cursor", + "name": "bodyHTML", "args": null, "storageKey": null }, -v15 = { +v13 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", "args": null, "storageKey": null }, -v16 = { - "kind": "ScalarField", - "alias": null, - "name": "number", - "args": null, - "storageKey": null -}, -v17 = { - "kind": "ScalarField", - "alias": null, - "name": "state", - "args": null, - "storageKey": null -}, -v18 = { +v14 = { "kind": "ScalarField", "alias": null, - "name": "bodyHTML", + "name": "url", "args": null, "storageKey": null }, -v19 = [ - v10 +v15 = [ + v14 ], -v20 = { +v16 = { "kind": "LinkedField", "alias": null, "name": "author", @@ -726,21 +678,21 @@ v20 = { "selections": [ v4, v5, - v15, + v13, v2, { "kind": "InlineFragment", "type": "Bot", - "selections": v19 + "selections": v15 }, { "kind": "InlineFragment", "type": "User", - "selections": v19 + "selections": v15 } ] }, -v21 = [ +v17 = [ { "kind": "Variable", "name": "after", @@ -754,13 +706,52 @@ v21 = [ "type": "Int" } ], -v22 = [ +v18 = { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null + } + ] +}, +v19 = { + "kind": "ScalarField", + "alias": null, + "name": "cursor", + "args": null, + "storageKey": null +}, +v20 = { + "kind": "ScalarField", + "alias": null, + "name": "isCrossRepository", + "args": null, + "storageKey": null +}, +v21 = [ v4, v5, - v15, + v13, v2 ], -v23 = { +v22 = { "kind": "InlineFragment", "type": "CrossReferencedEvent", "selections": [ @@ -771,7 +762,7 @@ v23 = { "args": null, "storageKey": null }, - v11, + v20, { "kind": "LinkedField", "alias": null, @@ -780,7 +771,7 @@ v23 = { "args": null, "concreteType": null, "plural": false, - "selections": v22 + "selections": v21 }, { "kind": "LinkedField", @@ -818,9 +809,9 @@ v23 = { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v16, - v9, v10, + v9, + v14, { "kind": "ScalarField", "alias": "prState", @@ -834,9 +825,9 @@ v23 = { "kind": "InlineFragment", "type": "Issue", "selections": [ - v16, - v9, v10, + v9, + v14, { "kind": "ScalarField", "alias": "issueState", @@ -850,51 +841,20 @@ v23 = { } ] }, -v24 = { - "kind": "ScalarField", - "alias": null, - "name": "oid", - "args": null, - "storageKey": null -}, -v25 = [ - v24, +v23 = [ + v4, + v13, + v5, v2 ], -v26 = { - "kind": "LinkedField", - "alias": null, - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v25 -}, -v27 = { +v24 = { "kind": "ScalarField", "alias": null, "name": "createdAt", "args": null, "storageKey": null }, -v28 = [ - v4, - v15, - v5, - v2 -], -v29 = { - "kind": "LinkedField", - "alias": null, - "name": "actor", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v28 -}, -v30 = { +v25 = { "kind": "InlineFragment", "type": "IssueComment", "selections": [ @@ -906,14 +866,14 @@ v30 = { "args": null, "concreteType": null, "plural": false, - "selections": v28 + "selections": v23 }, - v18, - v27, - v10 + v12, + v24, + v14 ] }, -v31 = { +v26 = { "kind": "LinkedField", "alias": null, "name": "user", @@ -926,7 +886,14 @@ v31 = { v2 ] }, -v32 = { +v27 = { + "kind": "ScalarField", + "alias": null, + "name": "oid", + "args": null, + "storageKey": null +}, +v28 = { "kind": "InlineFragment", "type": "Commit", "selections": [ @@ -940,8 +907,8 @@ v32 = { "plural": false, "selections": [ v3, - v31, - v15 + v26, + v13 ] }, { @@ -954,8 +921,8 @@ v32 = { "plural": false, "selections": [ v3, - v15, - v31 + v13, + v26 ] }, { @@ -965,7 +932,7 @@ v32 = { "args": null, "storageKey": null }, - v24, + v27, { "kind": "ScalarField", "alias": null, @@ -989,7 +956,16 @@ v32 = { } ] }, -v33 = { +v29 = [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } +], +v30 = { "kind": "LinkedField", "alias": null, "name": "reactionGroups", @@ -1013,16 +989,54 @@ v33 = { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v8 + "selections": v29 } ] +}, +v31 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "commitCursor", + "type": "String" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "commitCount", + "type": "Int" + } +], +v32 = [ + v27, + v2 +], +v33 = { + "kind": "LinkedField", + "alias": null, + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v32 +}, +v34 = { + "kind": "LinkedField", + "alias": null, + "name": "actor", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v23 }; return { "kind": "Request", "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_4cAEh0 on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1102,14 +1116,82 @@ return { "alias": "issueish", "name": "issueOrPullRequest", "storageKey": null, - "args": [ + "args": v8, + "concreteType": null, + "plural": false, + "selections": [ + v4, + v2, { - "kind": "Variable", - "name": "number", - "variableName": "issueishNumber", - "type": "Int!" + "kind": "InlineFragment", + "type": "Issue", + "selections": [ + v9, + v10, + v11, + v12, + v16, + v14, + { + "kind": "LinkedField", + "alias": null, + "name": "timeline", + "storageKey": null, + "args": v17, + "concreteType": "IssueTimelineConnection", + "plural": false, + "selections": [ + v18, + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "IssueTimelineItemEdge", + "plural": true, + "selections": [ + v19, + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v4, + v2, + v22, + v25, + v28 + ] + } + ] + } + ] + }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "timeline", + "args": v17, + "handle": "connection", + "key": "IssueTimelineController_timeline", + "filters": null + }, + v30 + ] } - ], + ] + }, + { + "kind": "LinkedField", + "alias": "pullRequest", + "name": "issueOrPullRequest", + "storageKey": null, + "args": v8, "concreteType": null, "plural": false, "selections": [ @@ -1127,7 +1209,7 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v8 + "selections": v29 }, v9, { @@ -1148,7 +1230,7 @@ return { "selections": [ v3, v7, - v10, + v14, { "kind": "ScalarField", "alias": null, @@ -1159,7 +1241,7 @@ return { v2 ] }, - v11, + v20, { "kind": "ScalarField", "alias": null, @@ -1167,17 +1249,17 @@ return { "args": null, "storageKey": null }, - v10, + v14, { "kind": "LinkedField", "alias": null, "name": "commits", "storageKey": null, - "args": v12, + "args": v31, "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v13, + v18, { "kind": "LinkedField", "alias": null, @@ -1187,7 +1269,7 @@ return { "concreteType": "PullRequestCommitEdge", "plural": true, "selections": [ - v14, + v19, { "kind": "LinkedField", "alias": null, @@ -1216,7 +1298,7 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v15, + v13, v3, { "kind": "ScalarField", @@ -1248,7 +1330,7 @@ return { "args": null, "storageKey": null }, - v10 + v14 ] }, v2, @@ -1263,12 +1345,12 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v12, + "args": v31, "handle": "connection", "key": "prCommitsView_commits", "filters": null }, - v16, + v10, { "kind": "LinkedField", "alias": "recentCommits", @@ -1321,7 +1403,7 @@ return { "concreteType": "Status", "plural": false, "selections": [ - v17, + v11, { "kind": "LinkedField", "alias": null, @@ -1332,7 +1414,7 @@ return { "plural": true, "selections": [ v2, - v17, + v11, { "kind": "ScalarField", "alias": null, @@ -1369,8 +1451,8 @@ return { } ] }, - v17, - v18, + v11, + v12, { "kind": "ScalarField", "alias": null, @@ -1378,7 +1460,7 @@ return { "args": null, "storageKey": null }, - v20, + v16, { "kind": "LinkedField", "alias": null, @@ -1407,11 +1489,11 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v21, + "args": v17, "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v13, + v18, { "kind": "LinkedField", "alias": null, @@ -1421,7 +1503,7 @@ return { "concreteType": "PullRequestTimelineItemEdge", "plural": true, "selections": [ - v14, + v19, { "kind": "LinkedField", "alias": null, @@ -1433,12 +1515,12 @@ return { "selections": [ v4, v2, - v23, + v22, { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v26, + v33, { "kind": "LinkedField", "alias": null, @@ -1482,11 +1564,11 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v22 + "selections": v21 }, - v26, - v18, - v27, + v33, + v12, + v24, { "kind": "ScalarField", "alias": null, @@ -1513,7 +1595,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v29, + v34, { "kind": "LinkedField", "alias": null, @@ -1522,7 +1604,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v25 + "selections": v32 }, { "kind": "LinkedField", @@ -1532,17 +1614,17 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v25 + "selections": v32 }, - v27 + v24 ] }, { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v29, - v26, + v34, + v33, { "kind": "ScalarField", "alias": null, @@ -1550,11 +1632,11 @@ return { "args": null, "storageKey": null }, - v27 + v24 ] }, - v30, - v32 + v25, + v28 ] } ] @@ -1565,74 +1647,12 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v21, + "args": v17, "handle": "connection", "key": "prTimelineContainer_timeline", "filters": null }, - v33 - ] - }, - { - "kind": "InlineFragment", - "type": "Issue", - "selections": [ - v9, - v16, - v17, - v18, - v20, - v10, - { - "kind": "LinkedField", - "alias": null, - "name": "timeline", - "storageKey": null, - "args": v21, - "concreteType": "IssueTimelineConnection", - "plural": false, - "selections": [ - v13, - { - "kind": "LinkedField", - "alias": null, - "name": "edges", - "storageKey": null, - "args": null, - "concreteType": "IssueTimelineItemEdge", - "plural": true, - "selections": [ - v14, - { - "kind": "LinkedField", - "alias": null, - "name": "node", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v4, - v2, - v23, - v30, - v32 - ] - } - ] - } - ] - }, - { - "kind": "LinkedHandle", - "alias": null, - "name": "timeline", - "args": v21, - "handle": "connection", - "key": "IssueTimelineController_timeline", - "filters": null - }, - v33 + v30 ] } ] diff --git a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js index 560e15de5e..92a8971f02 100644 --- a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js +++ b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js @@ -10,7 +10,7 @@ import type { ConcreteFragment } from 'relay-runtime'; type issueDetailView_issueish$ref = any; type issueDetailView_repository$ref = any; -type prDetailView_issueish$ref = any; +type prDetailView_pullRequest$ref = any; type prDetailView_repository$ref = any; import type { FragmentReference } from "relay-runtime"; declare export opaque type issueishDetailController_repository$ref: FragmentReference; @@ -25,7 +25,14 @@ export type issueishDetailController_repository = {| +number: number, +$fragmentRefs: issueDetailView_issueish$ref, |} | {| + // This will never be '%other', but we need some + // value in case none of the concrete values match. + +__typename: "%other" + |}), + +pullRequest: ?({| +__typename: "PullRequest", + +title: string, + +number: number, +headRefName: string, +headRepository: ?{| +name: string, @@ -35,7 +42,7 @@ export type issueishDetailController_repository = {| +url: any, +sshUrl: any, |}, - +$fragmentRefs: prDetailView_issueish$ref, + +$fragmentRefs: prDetailView_pullRequest$ref, |} | {| // This will never be '%other', but we need some // value in case none of the concrete values match. @@ -73,21 +80,36 @@ v1 = { } ] }, -v2 = { +v2 = [ + { + "kind": "Variable", + "name": "number", + "variableName": "issueishNumber", + "type": "Int!" + } +], +v3 = { + "kind": "ScalarField", + "alias": null, + "name": "__typename", + "args": null, + "storageKey": null +}, +v4 = { "kind": "ScalarField", "alias": null, "name": "title", "args": null, "storageKey": null }, -v3 = { +v5 = { "kind": "ScalarField", "alias": null, "name": "number", "args": null, "storageKey": null }, -v4 = [ +v6 = [ { "kind": "Variable", "name": "commitCount", @@ -168,30 +190,42 @@ return { "alias": "issueish", "name": "issueOrPullRequest", "storageKey": null, - "args": [ + "args": v2, + "concreteType": null, + "plural": false, + "selections": [ + v3, { - "kind": "Variable", - "name": "number", - "variableName": "issueishNumber", - "type": "Int!" + "kind": "InlineFragment", + "type": "Issue", + "selections": [ + v4, + v5, + { + "kind": "FragmentSpread", + "name": "issueDetailView_issueish", + "args": v6 + } + ] } - ], + ] + }, + { + "kind": "LinkedField", + "alias": "pullRequest", + "name": "issueOrPullRequest", + "storageKey": null, + "args": v2, "concreteType": null, "plural": false, "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "__typename", - "args": null, - "storageKey": null - }, + v3, { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v2, - v3, + v4, + v5, { "kind": "ScalarField", "alias": null, @@ -228,21 +262,8 @@ return { }, { "kind": "FragmentSpread", - "name": "prDetailView_issueish", - "args": v4 - } - ] - }, - { - "kind": "InlineFragment", - "type": "Issue", - "selections": [ - v2, - v3, - { - "kind": "FragmentSpread", - "name": "issueDetailView_issueish", - "args": v4 + "name": "prDetailView_pullRequest", + "args": v6 } ] } @@ -252,5 +273,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '23d8c284628ab45da2d636dcfbbd8239'; +(node/*: any*/).hash = '7b81a0a36c9be005da2a5bb6a876972c'; module.exports = node; diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 8be5ea548a..ca4cd6c7f7 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -84,7 +84,7 @@ export class BareIssueishDetailController extends React.Component { return ( @@ -242,6 +242,9 @@ export default createFragmentContainer(BareIssueishDetailController, { commitCursor: $commitCursor, ) } + } + pullRequest: issueOrPullRequest(number: $issueishNumber) { + __typename ... on PullRequest { title number @@ -254,7 +257,7 @@ export default createFragmentContainer(BareIssueishDetailController, { url sshUrl } - ...prDetailView_issueish @arguments( + ...prDetailView_pullRequest @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor, commitCount: $commitCount, diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index 64d60f0047..314a5a0bf2 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash fd414a0501fa54acfda074aee07b1aa4 + * @relayHash de4a33fffa471708fdc3fd0ec577e16b */ /* eslint-disable */ @@ -9,7 +9,7 @@ /*:: import type { ConcreteRequest } from 'relay-runtime'; -type prDetailView_issueish$ref = any; +type prDetailView_pullRequest$ref = any; type prDetailView_repository$ref = any; export type prDetailViewRefetchQueryVariables = {| repoId: string, @@ -23,8 +23,8 @@ export type prDetailViewRefetchQueryResponse = {| +repository: ?{| +$fragmentRefs: prDetailView_repository$ref |}, - +issueish: ?{| - +$fragmentRefs: prDetailView_issueish$ref + +pullRequest: ?{| + +$fragmentRefs: prDetailView_pullRequest$ref |}, |}; export type prDetailViewRefetchQuery = {| @@ -48,9 +48,9 @@ query prDetailViewRefetchQuery( ...prDetailView_repository_3D8CP9 id } - issueish: node(id: $issueishId) { + pullRequest: node(id: $issueishId) { __typename - ...prDetailView_issueish_4cAEh0 + ...prDetailView_pullRequest_4cAEh0 id } } @@ -65,7 +65,7 @@ fragment prDetailView_repository_3D8CP9 on Repository { } } -fragment prDetailView_issueish_4cAEh0 on PullRequest { +fragment prDetailView_pullRequest_4cAEh0 on PullRequest { __typename ... on Node { id @@ -705,7 +705,7 @@ return { "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...prDetailView_issueish_4cAEh0\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_issueish_4cAEh0 on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_4cAEh0\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_4cAEh0 on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -735,7 +735,7 @@ return { }, { "kind": "LinkedField", - "alias": "issueish", + "alias": "pullRequest", "name": "node", "storageKey": null, "args": v4, @@ -744,7 +744,7 @@ return { "selections": [ { "kind": "FragmentSpread", - "name": "prDetailView_issueish", + "name": "prDetailView_pullRequest", "args": [ { "kind": "Variable", @@ -794,7 +794,7 @@ return { }, { "kind": "LinkedField", - "alias": "issueish", + "alias": "pullRequest", "name": "node", "storageKey": null, "args": v4, @@ -1467,5 +1467,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'ba6fcfcda973b63cbdd79918fb888605'; +(node/*: any*/).hash = '04dad90234c09010553beb02cf90cbb1'; module.exports = node; diff --git a/lib/views/__generated__/prDetailView_issueish.graphql.js b/lib/views/__generated__/prDetailView_pullRequest.graphql.js similarity index 95% rename from lib/views/__generated__/prDetailView_issueish.graphql.js rename to lib/views/__generated__/prDetailView_pullRequest.graphql.js index fe77fc3798..ae06b6754a 100644 --- a/lib/views/__generated__/prDetailView_issueish.graphql.js +++ b/lib/views/__generated__/prDetailView_pullRequest.graphql.js @@ -14,8 +14,8 @@ type prTimelineController_pullRequest$ref = any; export type PullRequestState = "CLOSED" | "MERGED" | "OPEN" | "%future added value"; export type ReactionContent = "CONFUSED" | "HEART" | "HOORAY" | "LAUGH" | "THUMBS_DOWN" | "THUMBS_UP" | "%future added value"; import type { FragmentReference } from "relay-runtime"; -declare export opaque type prDetailView_issueish$ref: FragmentReference; -export type prDetailView_issueish = {| +declare export opaque type prDetailView_pullRequest$ref: FragmentReference; +export type prDetailView_pullRequest = {| +id?: string, +isCrossRepository: boolean, +changedFiles: number, @@ -42,7 +42,7 @@ export type prDetailView_issueish = {| |}>, +__typename: "PullRequest", +$fragmentRefs: prCommitsView_pullRequest$ref & prStatusesView_pullRequest$ref & prTimelineController_pullRequest$ref, - +$refType: prDetailView_issueish$ref, + +$refType: prDetailView_pullRequest$ref, |}; */ @@ -69,7 +69,7 @@ v2 = [ ]; return { "kind": "Fragment", - "name": "prDetailView_issueish", + "name": "prDetailView_pullRequest", "type": "PullRequest", "metadata": null, "argumentDefinitions": [ @@ -288,5 +288,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '3f707e83b01af99f75dfc844ef0f32d6'; +(node/*: any*/).hash = '2b7cc9778a3440738f809f76fcd3fd25'; module.exports = node; diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index e524be50cc..2e0cb13a26 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -43,7 +43,7 @@ export class BarePullRequestDetailView extends React.Component { login: PropTypes.string, }), }), - issueish: PropTypes.shape({ + pullRequest: PropTypes.shape({ __typename: PropTypes.string.isRequired, id: PropTypes.string.isRequired, title: PropTypes.string, @@ -86,7 +86,7 @@ export class BarePullRequestDetailView extends React.Component { componentDidMount() { this.refresher = new PeriodicRefresher(BarePullRequestDetailView, { interval: () => 5 * 60 * 1000, - getCurrentId: () => this.props.issueish.id, + getCurrentId: () => this.props.pullRequest.id, refresh: this.refresh, minimumIntervalPerId: 2 * 60 * 1000, }); @@ -98,26 +98,26 @@ export class BarePullRequestDetailView extends React.Component { this.refresher.destroy(); } - renderPrMetadata(issueish, repo) { + renderPrMetadata(pullRequest, repo) { return (
    {issueish.author.login} wants to merge{' '} + href={pullRequest.author.url}>{pullRequest.author.login} wants to merge{' '} {issueish.countedCommits.totalCount} commits and{' '} + href={pullRequest.url + '/commits'}>{pullRequest.countedCommits.totalCount} commits and{' '} {issueish.changedFiles} changed files into{' '} - {issueish.isCrossRepository ? - `${repo.owner.login}/${issueish.baseRefName}` : issueish.baseRefName} from{' '} - {issueish.isCrossRepository ? - `${issueish.author.login}/${issueish.headRefName}` : issueish.headRefName} + href={pullRequest.url + '/files'}>{pullRequest.changedFiles} changed files into{' '} + {pullRequest.isCrossRepository ? + `${repo.owner.login}/${pullRequest.baseRefName}` : pullRequest.baseRefName} from{' '} + {pullRequest.isCrossRepository ? + `${pullRequest.author.login}/${pullRequest.headRefName}` : pullRequest.headRefName}
    ); } - renderPullRequestBody(issueish, childProps) { + renderPullRequestBody(pullRequest, childProps) { return ( @@ -139,10 +139,10 @@ export class BarePullRequestDetailView extends React.Component { {/* overview */} No description provided.'} + html={pullRequest.bodyHTML || 'No description provided.'} switchToIssueish={this.props.switchToIssueish} /> - +
    - +
    {/* commits */} - +
    ); @@ -167,10 +167,10 @@ export class BarePullRequestDetailView extends React.Component { render() { const repo = this.props.repository; - const issueish = this.props.issueish; + const pullRequest = this.props.pullRequest; const childProps = { - issue: issueish.__typename === 'Issue' ? issueish : null, - pullRequest: issueish.__typename === 'PullRequest' ? issueish : null, + issue: pullRequest.__typename === 'Issue' ? pullRequest : null, + pullRequest: pullRequest.__typename === 'PullRequest' ? pullRequest : null, }; return ( @@ -307,8 +307,8 @@ export default createRefetchContainer(BarePullRequestDetailView, { } `, - issueish: graphql` - fragment prDetailView_issueish on PullRequest + pullRequest: graphql` + fragment prDetailView_pullRequest on PullRequest @argumentDefinitions( timelineCount: {type: "Int!"}, timelineCursor: {type: "String"}, @@ -365,8 +365,8 @@ export default createRefetchContainer(BarePullRequestDetailView, { ) } - issueish:node(id: $issueishId) { - ...prDetailView_issueish @arguments( + pullRequest:node(id: $issueishId) { + ...prDetailView_pullRequest @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor, commitCount: $commitCount, From 9638361e0a3e16ecb90c683e278afcb19c34496a Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 30 Nov 2018 15:04:51 -0800 Subject: [PATCH 1307/4053] ok I think this shit actually works now? - Wasn't passing the right props into the PullRequestDetailView -Clean up some more issueish code --- lib/controllers/issueish-detail-controller.js | 16 ++++------ lib/views/pr-detail-view.js | 31 ++++++++++--------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index ca4cd6c7f7..bbaa3b32d4 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -84,7 +84,7 @@ export class BareIssueishDetailController extends React.Component { return ( @@ -102,11 +102,7 @@ export class BareIssueishDetailController extends React.Component { nextCheckoutOp() { const {repository} = this.props; - const {issueish} = repository; - - if (issueish.__typename !== 'PullRequest') { - return this.checkoutOp.disable(checkoutStates.HIDDEN, 'Cannot check out an issue'); - } + const {pullRequest} = repository; if (this.props.isAbsent) { return this.checkoutOp.disable(checkoutStates.HIDDEN, 'No repository found'); @@ -141,13 +137,13 @@ export class BareIssueishDetailController extends React.Component { const fromPullRefspec = headRemote.getOwner() === repository.owner.login && headRemote.getRepo() === repository.name && - headPush.getShortRemoteRef() === `pull/${issueish.number}/head`; + headPush.getShortRemoteRef() === `pull/${pullRequest.number}/head`; // (detect checkout from head repository) const fromHeadRepo = - headRemote.getOwner() === issueish.headRepository.owner.login && - headRemote.getRepo() === issueish.headRepository.name && - headPush.getShortRemoteRef() === issueish.headRefName; + headRemote.getOwner() === pullRequest.headRepository.owner.login && + headRemote.getRepo() === pullRequest.headRepository.name && + headPush.getShortRemoteRef() === pullRequest.headRefName; if (fromPullRefspec || fromHeadRepo) { return this.checkoutOp.disable(checkoutStates.CURRENT, 'Current'); diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 2e0cb13a26..01b7286215 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -166,6 +166,7 @@ export class BarePullRequestDetailView extends React.Component { } render() { + console.log(this.props); const repo = this.props.repository; const pullRequest = this.props.pullRequest; const childProps = { @@ -180,14 +181,14 @@ export class BarePullRequestDetailView extends React.Component {
    - {this.renderPrMetadata(issueish, repo)} + {this.renderPrMetadata(pullRequest, repo)}
    - {this.renderPullRequestBody(issueish, childProps)} + {this.renderPullRequestBody(pullRequest, childProps)} @@ -274,7 +275,7 @@ export class BarePullRequestDetailView extends React.Component { } recordOpenInBrowserEvent() { - addEvent('open-issueish-in-browser', {package: 'github', component: this.constructor.name}); + addEvent('open-pull-request-in-browser', {package: 'github', component: this.constructor.name}); } refresh() { @@ -285,7 +286,7 @@ export class BarePullRequestDetailView extends React.Component { this.setState({refreshing: true}); this.props.relay.refetch({ repoId: this.props.repository.id, - issueishId: this.props.issueish.id, + issueishId: this.props.pullRequest.id, timelineCount: 100, timelineCursor: null, commitCount: 100, From a8fe2e6a8b4d2ccb5afd00ddb29232768f3e74c0 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 30 Nov 2018 15:53:50 -0800 Subject: [PATCH 1308/4053] rename `issueish` to `issue` --- .../issueishDetailContainerQuery.graphql.js | 12 ++--- ...eishDetailController_repository.graphql.js | 12 ++--- lib/controllers/issueish-detail-controller.js | 27 ++++++---- .../issueDetailViewRefetchQuery.graphql.js | 24 ++++----- ...ql.js => issueDetailView_issue.graphql.js} | 10 ++-- lib/views/issue-detail-view.js | 50 +++++++++---------- 6 files changed, 70 insertions(+), 65 deletions(-) rename lib/views/__generated__/{issueDetailView_issueish.graphql.js => issueDetailView_issue.graphql.js} (94%) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index f5ec6b4c46..b0930609aa 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 76d06aecf543e593ddcbc07aca4e478a + * @relayHash 08475467ae7fd142f34c5eefed3103b1 */ /* eslint-disable */ @@ -56,12 +56,12 @@ fragment issueishDetailController_repository_1mXVvq on Repository { login id } - issueish: issueOrPullRequest(number: $issueishNumber) { + issue: issueOrPullRequest(number: $issueishNumber) { __typename ... on Issue { title number - ...issueDetailView_issueish_4cAEh0 + ...issueDetailView_issue_4cAEh0 } ... on Node { id @@ -112,7 +112,7 @@ fragment prDetailView_repository on Repository { } } -fragment issueDetailView_issueish_4cAEh0 on Issue { +fragment issueDetailView_issue_4cAEh0 on Issue { __typename ... on Node { id @@ -1036,7 +1036,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issueish: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issueish_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_4cAEh0 on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_4cAEh0 on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n abbreviatedOid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1113,7 +1113,7 @@ return { v7, { "kind": "LinkedField", - "alias": "issueish", + "alias": "issue", "name": "issueOrPullRequest", "storageKey": null, "args": v8, diff --git a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js index 92a8971f02..e36d6c2e87 100644 --- a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js +++ b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js @@ -8,7 +8,7 @@ /*:: import type { ConcreteFragment } from 'relay-runtime'; -type issueDetailView_issueish$ref = any; +type issueDetailView_issue$ref = any; type issueDetailView_repository$ref = any; type prDetailView_pullRequest$ref = any; type prDetailView_repository$ref = any; @@ -19,11 +19,11 @@ export type issueishDetailController_repository = {| +owner: {| +login: string |}, - +issueish: ?({| + +issue: ?({| +__typename: "Issue", +title: string, +number: number, - +$fragmentRefs: issueDetailView_issueish$ref, + +$fragmentRefs: issueDetailView_issue$ref, |} | {| // This will never be '%other', but we need some // value in case none of the concrete values match. @@ -187,7 +187,7 @@ return { v1, { "kind": "LinkedField", - "alias": "issueish", + "alias": "issue", "name": "issueOrPullRequest", "storageKey": null, "args": v2, @@ -203,7 +203,7 @@ return { v5, { "kind": "FragmentSpread", - "name": "issueDetailView_issueish", + "name": "issueDetailView_issue", "args": v6 } ] @@ -273,5 +273,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '7b81a0a36c9be005da2a5bb6a876972c'; +(node/*: any*/).hash = '616ab785cf6824cb91ed3002553abb70'; module.exports = node; diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index bbaa3b32d4..a433145ae5 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -16,7 +16,8 @@ export class BareIssueishDetailController extends React.Component { owner: PropTypes.shape({ login: PropTypes.string.isRequired, }).isRequired, - issueish: PropTypes.any, // FIXME from IssueishPaneItemContainer.propTypes + pullRequest: PropTypes.any, // FIXME + issue: PropTypes.any, // FIXME from IssueishPaneItemContainer.propTypes }), issueishNumber: PropTypes.number.isRequired, @@ -74,12 +75,12 @@ export class BareIssueishDetailController extends React.Component { render() { const {repository} = this.props; - if (!repository || !repository.issueish) { + if (!repository || !repository.issue || !repository.pullRequest) { return
    Issue/PR #{this.props.issueishNumber} not found
    ; // TODO: no PRs } this.checkoutOp = this.nextCheckoutOp(); - const isPr = repository.issueish.__typename === 'PullRequest'; + const isPr = repository.pullRequest.__typename === 'PullRequest'; if (isPr) { return ( ); @@ -104,6 +105,10 @@ export class BareIssueishDetailController extends React.Component { const {repository} = this.props; const {pullRequest} = repository; + if (pullRequest.__typename !== 'PullRequest') { + return this.checkoutOp.disable(checkoutStates.HIDDEN, 'Cannot check out an issue'); + } + if (this.props.isAbsent) { return this.checkoutOp.disable(checkoutStates.HIDDEN, 'No repository found'); } @@ -154,10 +159,10 @@ export class BareIssueishDetailController extends React.Component { async checkout() { const {repository} = this.props; - const {issueish} = repository; - const {headRepository} = issueish; + const {pullRequest} = repository; + const {headRepository} = pullRequest; - const fullHeadRef = `refs/heads/${issueish.headRefName}`; + const fullHeadRef = `refs/heads/${pullRequest.headRefName}`; let sourceRemoteName, localRefName; @@ -200,10 +205,10 @@ export class BareIssueishDetailController extends React.Component { await this.props.fetch(fullHeadRef, {remoteName: sourceRemoteName}); // Check out the local ref and set it up to track the head ref. - await this.props.checkout(`pr-${issueish.number}/${headRepository.owner.login}/${issueish.headRefName}`, { + await this.props.checkout(`pr-${pullRequest.number}/${headRepository.owner.login}/${pullRequest.headRefName}`, { createNew: true, track: true, - startPoint: `refs/remotes/${sourceRemoteName}/${issueish.headRefName}`, + startPoint: `refs/remotes/${sourceRemoteName}/${pullRequest.headRefName}`, }); incrementCounter('checkout-pr'); @@ -226,12 +231,12 @@ export default createFragmentContainer(BareIssueishDetailController, { owner { login } - issueish: issueOrPullRequest(number: $issueishNumber) { + issue: issueOrPullRequest(number: $issueishNumber) { __typename ... on Issue { title number - ...issueDetailView_issueish @arguments( + ...issueDetailView_issue @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor, commitCount: $commitCount, diff --git a/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js index 6bb9113ca0..4264033230 100644 --- a/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 02f94074fbd53cdf8b87c5e9896a53b7 + * @relayHash 99b7f4f56ff728971b2c5939dc9ef2b5 */ /* eslint-disable */ @@ -9,7 +9,7 @@ /*:: import type { ConcreteRequest } from 'relay-runtime'; -type issueDetailView_issueish$ref = any; +type issueDetailView_issue$ref = any; type issueDetailView_repository$ref = any; export type issueDetailViewRefetchQueryVariables = {| repoId: string, @@ -21,8 +21,8 @@ export type issueDetailViewRefetchQueryResponse = {| +repository: ?{| +$fragmentRefs: issueDetailView_repository$ref |}, - +issueish: ?{| - +$fragmentRefs: issueDetailView_issueish$ref + +issue: ?{| + +$fragmentRefs: issueDetailView_issue$ref |}, |}; export type issueDetailViewRefetchQuery = {| @@ -44,9 +44,9 @@ query issueDetailViewRefetchQuery( ...issueDetailView_repository_3D8CP9 id } - issueish: node(id: $issueishId) { + issue: node(id: $issueishId) { __typename - ...issueDetailView_issueish_3D8CP9 + ...issueDetailView_issue_3D8CP9 id } } @@ -61,7 +61,7 @@ fragment issueDetailView_repository_3D8CP9 on Repository { } } -fragment issueDetailView_issueish_3D8CP9 on Issue { +fragment issueDetailView_issue_3D8CP9 on Issue { __typename ... on Node { id @@ -407,7 +407,7 @@ return { "operationKind": "query", "name": "issueDetailViewRefetchQuery", "id": null, - "text": "query issueDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueDetailView_repository_3D8CP9\n id\n }\n issueish: node(id: $issueishId) {\n __typename\n ...issueDetailView_issueish_3D8CP9\n id\n }\n}\n\nfragment issueDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issueish_3D8CP9 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", + "text": "query issueDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueDetailView_repository_3D8CP9\n id\n }\n issue: node(id: $issueishId) {\n __typename\n ...issueDetailView_issue_3D8CP9\n id\n }\n}\n\nfragment issueDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_3D8CP9 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_item\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitView_item on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -434,7 +434,7 @@ return { }, { "kind": "LinkedField", - "alias": "issueish", + "alias": "issue", "name": "node", "storageKey": null, "args": v3, @@ -443,7 +443,7 @@ return { "selections": [ { "kind": "FragmentSpread", - "name": "issueDetailView_issueish", + "name": "issueDetailView_issue", "args": v2 } ] @@ -478,7 +478,7 @@ return { }, { "kind": "LinkedField", - "alias": "issueish", + "alias": "issue", "name": "node", "storageKey": null, "args": v3, @@ -849,5 +849,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'ae08e0a6982589986e579be429b2a460'; +(node/*: any*/).hash = '82666b2748036545eb131b3e34f48e72'; module.exports = node; diff --git a/lib/views/__generated__/issueDetailView_issueish.graphql.js b/lib/views/__generated__/issueDetailView_issue.graphql.js similarity index 94% rename from lib/views/__generated__/issueDetailView_issueish.graphql.js rename to lib/views/__generated__/issueDetailView_issue.graphql.js index 0bd4f26326..3ccf457baf 100644 --- a/lib/views/__generated__/issueDetailView_issueish.graphql.js +++ b/lib/views/__generated__/issueDetailView_issue.graphql.js @@ -12,8 +12,8 @@ type issueTimelineController_issue$ref = any; export type IssueState = "CLOSED" | "OPEN" | "%future added value"; export type ReactionContent = "CONFUSED" | "HEART" | "HOORAY" | "LAUGH" | "THUMBS_DOWN" | "THUMBS_UP" | "%future added value"; import type { FragmentReference } from "relay-runtime"; -declare export opaque type issueDetailView_issueish$ref: FragmentReference; -export type issueDetailView_issueish = {| +declare export opaque type issueDetailView_issue$ref: FragmentReference; +export type issueDetailView_issue = {| +id?: string, +state: IssueState, +number: number, @@ -33,7 +33,7 @@ export type issueDetailView_issueish = {| |}>, +__typename: "Issue", +$fragmentRefs: issueTimelineController_issue$ref, - +$refType: issueDetailView_issueish$ref, + +$refType: issueDetailView_issue$ref, |}; */ @@ -51,7 +51,7 @@ v1 = [ ]; return { "kind": "Fragment", - "name": "issueDetailView_issueish", + "name": "issueDetailView_issue", "type": "Issue", "metadata": null, "argumentDefinitions": [ @@ -205,5 +205,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '4fbcc89822253448caa73ca5f5871c06'; +(node/*: any*/).hash = 'e1cf4b71a99cbade6149738c70451892'; module.exports = node; diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 6b40dbc2cf..c57547287b 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -25,7 +25,7 @@ export class BareIssueDetailView extends React.Component { login: PropTypes.string, }), }), - issueish: PropTypes.shape({ + issue: PropTypes.shape({ __typename: PropTypes.string.isRequired, id: PropTypes.string.isRequired, title: PropTypes.string, @@ -63,7 +63,7 @@ export class BareIssueDetailView extends React.Component { componentDidMount() { this.refresher = new PeriodicRefresher(BareIssueDetailView, { interval: () => 5 * 60 * 1000, - getCurrentId: () => this.props.issueish.id, + getCurrentId: () => this.props.issue.id, refresh: this.refresh, minimumIntervalPerId: 2 * 60 * 1000, }); @@ -75,14 +75,14 @@ export class BareIssueDetailView extends React.Component { this.refresher.destroy(); } - renderIssueBody(issueish, childProps) { + renderIssueBody(issue, childProps) { return ( No description provided.'} + html={issue.bodyHTML || 'No description provided.'} switchToIssueish={this.props.switchToIssueish} /> - + @@ -105,14 +105,14 @@ export class BareIssueDetailView extends React.Component {
    - {this.renderIssueBody(issueish, childProps)} + {this.renderIssueBody(issue, childProps)} @@ -155,7 +155,7 @@ export class BareIssueDetailView extends React.Component { } recordOpenInBrowserEvent() { - addEvent('open-issueish-in-browser', {package: 'github', component: this.constructor.name}); + addEvent('open-issue-in-browser', {package: 'github', component: this.constructor.name}); } refresh() { @@ -166,7 +166,7 @@ export class BareIssueDetailView extends React.Component { this.setState({refreshing: true}); this.props.relay.refetch({ repoId: this.props.repository.id, - issueishId: this.props.issueish.id, + issueishId: this.props.issue.id, timelineCount: 100, timelineCursor: null, }, null, () => { @@ -186,8 +186,8 @@ export default createRefetchContainer(BareIssueDetailView, { } `, - issueish: graphql` - fragment issueDetailView_issueish on Issue + issue: graphql` + fragment issueDetailView_issue on Issue @argumentDefinitions( timelineCount: {type: "Int!"}, timelineCursor: {type: "String"}, @@ -233,8 +233,8 @@ export default createRefetchContainer(BareIssueDetailView, { ) } - issueish:node(id: $issueishId) { - ...issueDetailView_issueish @arguments( + issue:node(id: $issueishId) { + ...issueDetailView_issue @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor, ) From 2aea51bbefcca13890909db6f72292ae413f9767 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 15:59:58 -0800 Subject: [PATCH 1309/4053] Fix CommitView tests --- lib/views/timeline-items/commit-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/timeline-items/commit-view.js b/lib/views/timeline-items/commit-view.js index 2b62988a01..6a0f9c4584 100644 --- a/lib/views/timeline-items/commit-view.js +++ b/lib/views/timeline-items/commit-view.js @@ -67,7 +67,7 @@ export class BareCommitView extends React.Component { dangerouslySetInnerHTML={{__html: commit.messageHeadlineHTML}} onClick={this.openCommitDetailItem} /> - {commit.sha.slice(0, 8)} + {commit.oid.slice(0, 8)}
    ); } From 2b8696aa885053129b6fe77200af721588afaf6a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 16:27:49 -0800 Subject: [PATCH 1310/4053] Fix CommitDetailContainer test --- test/containers/commit-detail-container.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/containers/commit-detail-container.test.js b/test/containers/commit-detail-container.test.js index e4dff8718f..ac31fe34d5 100644 --- a/test/containers/commit-detail-container.test.js +++ b/test/containers/commit-detail-container.test.js @@ -35,6 +35,7 @@ describe('CommitDetailContainer', function() { config: atomEnv.config, destroy: () => {}, + surfaceCommit: () => {}, ...override, }; From 109c4d1045a89bd41fbd0ede0133067d94d72cc8 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 16:29:26 -0800 Subject: [PATCH 1311/4053] Fix GitStrategies test --- test/git-strategies.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 574dceadd4..39f909f891 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -163,7 +163,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; const workingDirPath = await cloneRepository('multiple-commits'); const git = createTestStrategy(workingDirPath); - const diffs = await git.getDiffForCommit('18920c90'); + const diffs = await git.getDiffsForCommit('18920c90'); assertDeepPropertyVals(diffs, [{ oldPath: 'file.txt', From 05845671b22ba368942560a037a44b86c853eae7 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 16:30:23 -0800 Subject: [PATCH 1312/4053] :fire: unnecessary focus test --- test/items/commit-detail-item.test.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/items/commit-detail-item.test.js b/test/items/commit-detail-item.test.js index eecbc66cf6..f86aab86e3 100644 --- a/test/items/commit-detail-item.test.js +++ b/test/items/commit-detail-item.test.js @@ -151,18 +151,4 @@ describe('CommitDetailItem', function() { assert.strictEqual(item.getWorkingDirectory(), '/dir7'); assert.strictEqual(item.getSha(), '420'); }); - - it('passes a focus() call to the component designated as its initial focus', async function() { - const wrapper = mount(buildPaneApp()); - const item = await open(wrapper); - wrapper.update(); - - const refHolder = wrapper.find('CommitDetailContainer').prop('refInitialFocus'); - const initialFocus = await refHolder.getPromise(); - sinon.spy(initialFocus, 'focus'); - - item.focus(); - - assert.isTrue(initialFocus.focus.called); - }); }); From dca78bc5a234000bc9d9e440d2952f369308a189 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 17:25:36 -0800 Subject: [PATCH 1313/4053] Fix HunkHeaderView test --- test/views/hunk-header-view.test.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/views/hunk-header-view.test.js b/test/views/hunk-header-view.test.js index 5bd0adf586..f408fc9067 100644 --- a/test/views/hunk-header-view.test.js +++ b/test/views/hunk-header-view.test.js @@ -120,12 +120,8 @@ describe('HunkHeaderView', function() { assert.isTrue(evt.stopPropagation.called); }); - it('does not render extra buttons when in a CommitPreviewItem or a CommitDetailItem', function() { - let wrapper = shallow(buildApp({itemType: CommitPreviewItem})); - assert.isFalse(wrapper.find('.github-HunkHeaderView-stageButton').exists()); - assert.isFalse(wrapper.find('.github-HunkHeaderView-discardButton').exists()); - - wrapper = shallow(buildApp({itemType: CommitDetailItem})); + it('does not render extra buttons when in a CommitDetailItem', function() { + const wrapper = shallow(buildApp({itemType: CommitDetailItem})); assert.isFalse(wrapper.find('.github-HunkHeaderView-stageButton').exists()); assert.isFalse(wrapper.find('.github-HunkHeaderView-discardButton').exists()); }); From 22754484b0f3333eab4452d16ac123c2c264b156 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 19:09:34 -0800 Subject: [PATCH 1314/4053] Fix GitTabView focus tests --- test/views/git-tab-view.test.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/test/views/git-tab-view.test.js b/test/views/git-tab-view.test.js index 5d07ce9b77..93710af685 100644 --- a/test/views/git-tab-view.test.js +++ b/test/views/git-tab-view.test.js @@ -153,15 +153,29 @@ describe('GitTabView', function() { stagingView = wrapper.prop('refStagingView').get(); event = {stopPropagation: sinon.spy()}; - sinon.stub(instance, 'setFocus'); + sinon.spy(instance, 'setFocus'); + }); + + it('focuses the enabled commit button if the recent commit view has focus', async function() { + const setFocus = sinon.spy(wrapper.find('CommitView').instance(), 'setFocus'); + + sinon.stub(instance, 'getFocus').returns(GitTabView.focus.RECENT_COMMIT); + sinon.stub(wrapper.find('CommitView').instance(), 'commitIsEnabled').returns(true); + + await wrapper.instance().retreatFocus(event); + + assert.isTrue(setFocus.calledWith(GitTabView.focus.COMMIT_BUTTON)); + assert.isTrue(event.stopPropagation.called); }); - it('focuses the commit button if the recent commit view has focus', async function() { + it('focuses the editor if the recent commit view has focus and the commit button is disabled', async function() { + const setFocus = sinon.spy(wrapper.find('CommitView').instance(), 'setFocus'); + sinon.stub(instance, 'getFocus').returns(GitTabView.focus.RECENT_COMMIT); await wrapper.instance().retreatFocus(event); - assert.isTrue(instance.setFocus.calledWith(GitTabView.focus.COMMIT_BUTTON)); + assert.isTrue(setFocus.calledWith(GitTabView.focus.EDITOR)); assert.isTrue(event.stopPropagation.called); }); From 75062d9071f3ceb7242e1aec1c6785bb4e37c27e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 19:11:44 -0800 Subject: [PATCH 1315/4053] :shirt: --- test/views/hunk-header-view.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/views/hunk-header-view.test.js b/test/views/hunk-header-view.test.js index f408fc9067..837948f099 100644 --- a/test/views/hunk-header-view.test.js +++ b/test/views/hunk-header-view.test.js @@ -5,7 +5,6 @@ import HunkHeaderView from '../../lib/views/hunk-header-view'; import RefHolder from '../../lib/models/ref-holder'; import Hunk from '../../lib/models/patch/hunk'; import CommitDetailItem from '../../lib/items/commit-detail-item'; -import CommitPreviewItem from '../../lib/items/commit-preview-item'; describe('HunkHeaderView', function() { let atomEnv, hunk; From 54163061b9ec3852ea8ac7a6e8f2dd5a14574785 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 19:32:57 -0800 Subject: [PATCH 1316/4053] Add OpenCommitDialog tests --- test/views/open-commit-dialog.test.js | 83 +++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 test/views/open-commit-dialog.test.js diff --git a/test/views/open-commit-dialog.test.js b/test/views/open-commit-dialog.test.js new file mode 100644 index 0000000000..ef6a446880 --- /dev/null +++ b/test/views/open-commit-dialog.test.js @@ -0,0 +1,83 @@ +import React from 'react'; +import {mount} from 'enzyme'; +import path from 'path'; + +import OpenCommitDialog from '../../lib/views/open-commit-dialog'; + +describe('OpenCommitDialog', function() { + let atomEnv, config, commandRegistry; + let app, wrapper, didAccept, didCancel; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + config = atomEnv.config; + commandRegistry = atomEnv.commands; + sinon.stub(config, 'get').returns(path.join('home', 'me', 'codes')); + + didAccept = sinon.stub(); + didCancel = sinon.stub(); + + app = ( + + ); + wrapper = mount(app); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + const setTextIn = function(selector, text) { + wrapper.find(selector).getDOMNode().getModel().setText(text); + }; + + describe('entering a commit sha', function() { + it("updates the project path automatically if it hasn't been modified", function() { + setTextIn('.github-CommitSha atom-text-editor', 'asdf1234'); + + assert.equal(wrapper.instance().getCommitSha(), 'asdf1234'); + }); + + it('does update the sha if it was modified automatically', function() { + setTextIn('.github-CommitSha atom-text-editor', 'asdf1234'); + assert.equal(wrapper.instance().getCommitSha(), 'asdf1234'); + + setTextIn('.github-CommitSha atom-text-editor', 'zxcv5678'); + assert.equal(wrapper.instance().getCommitSha(), 'zxcv5678'); + }); + }); + + describe('open button enablement', function() { + it('disables the open button with no commit sha', function() { + setTextIn('.github-CommitSha atom-text-editor', ''); + wrapper.update(); + + assert.isTrue(wrapper.find('button.icon-commit').prop('disabled')); + }); + + it('enables the open button when commit sha box is populated', function() { + setTextIn('.github-CommitSha atom-text-editor', 'asdf1234'); + wrapper.update(); + + assert.isFalse(wrapper.find('button.icon-commit').prop('disabled')); + }); + }); + + it('calls the acceptance callback', function() { + setTextIn('.github-CommitSha atom-text-editor', 'asdf1234'); + + wrapper.find('button.icon-commit').simulate('click'); + + assert.isTrue(didAccept.calledWith({sha: 'asdf1234'})); + }); + + it('calls the cancellation callback', function() { + wrapper.find('button.github-CancelButton').simulate('click'); + assert.isTrue(didCancel.called); + }); +}); From 5aae2c5f679be7e76c9e80045f0b27863857e3f8 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 19:40:02 -0800 Subject: [PATCH 1317/4053] Clean up OpenCommitDialog test --- test/views/open-commit-dialog.test.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/views/open-commit-dialog.test.js b/test/views/open-commit-dialog.test.js index ef6a446880..7d4637015c 100644 --- a/test/views/open-commit-dialog.test.js +++ b/test/views/open-commit-dialog.test.js @@ -5,21 +5,18 @@ import path from 'path'; import OpenCommitDialog from '../../lib/views/open-commit-dialog'; describe('OpenCommitDialog', function() { - let atomEnv, config, commandRegistry; + let atomEnv, commandRegistry; let app, wrapper, didAccept, didCancel; beforeEach(function() { atomEnv = global.buildAtomEnvironment(); - config = atomEnv.config; commandRegistry = atomEnv.commands; - sinon.stub(config, 'get').returns(path.join('home', 'me', 'codes')); didAccept = sinon.stub(); didCancel = sinon.stub(); app = ( Date: Fri, 30 Nov 2018 19:52:37 -0800 Subject: [PATCH 1318/4053] :fire: unnecessary code in OpenCommitDialog --- lib/views/open-commit-dialog.js | 21 +-------------------- test/views/open-commit-dialog.test.js | 1 - 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/lib/views/open-commit-dialog.js b/lib/views/open-commit-dialog.js index 47ab2d3ec2..d81c8038d7 100644 --- a/lib/views/open-commit-dialog.js +++ b/lib/views/open-commit-dialog.js @@ -75,16 +75,7 @@ export default class OpenCommitDialog extends React.Component { return; } - const parsed = this.parseSha(); - if (!parsed) { - this.setState({ - error: 'That is not a valid commit sha.', - }); - return; - } - const {sha} = parsed; - - this.props.didAccept({sha}); + this.props.didAccept({sha: this.getCommitSha()}); } cancel() { @@ -122,16 +113,6 @@ export default class OpenCommitDialog extends React.Component { this.setState({error: null}); } - parseSha() { - const sha = this.getCommitSha(); - // const matches = url.match(ISSUEISH_URL_REGEX); - // if (!matches) { - // return false; - // } - // const [_full, repoOwner, repoName, issueishNumber] = matches; // eslint-disable-line no-unused-vars - return {sha}; - } - getCommitSha() { return this.commitShaEditor ? this.commitShaEditor.getText() : ''; } diff --git a/test/views/open-commit-dialog.test.js b/test/views/open-commit-dialog.test.js index 7d4637015c..067ba51dd4 100644 --- a/test/views/open-commit-dialog.test.js +++ b/test/views/open-commit-dialog.test.js @@ -1,6 +1,5 @@ import React from 'react'; import {mount} from 'enzyme'; -import path from 'path'; import OpenCommitDialog from '../../lib/views/open-commit-dialog'; From a71ce1494ec364bdd92753a90f17950e2dd8763a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 19:53:16 -0800 Subject: [PATCH 1319/4053] Add tests for OpenIssueishDialog --- test/views/open-issueish-dialog.test.js | 95 +++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 test/views/open-issueish-dialog.test.js diff --git a/test/views/open-issueish-dialog.test.js b/test/views/open-issueish-dialog.test.js new file mode 100644 index 0000000000..b4caf29fe5 --- /dev/null +++ b/test/views/open-issueish-dialog.test.js @@ -0,0 +1,95 @@ +import React from 'react'; +import {mount} from 'enzyme'; + +import OpenIssueishDialog from '../../lib/views/open-issueish-dialog'; + +describe('OpenIssueishDialog', function() { + let atomEnv, commandRegistry; + let app, wrapper, didAccept, didCancel; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + commandRegistry = atomEnv.commands; + + didAccept = sinon.stub(); + didCancel = sinon.stub(); + + app = ( + + ); + wrapper = mount(app); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + const setTextIn = function(selector, text) { + wrapper.find(selector).getDOMNode().getModel().setText(text); + }; + + describe('entering a issueish url', function() { + it("updates the issue url automatically if it hasn't been modified", function() { + setTextIn('.github-IssueishUrl atom-text-editor', 'https://github.com/atom/github/pull/1807'); + + assert.equal(wrapper.instance().getIssueishUrl(), 'https://github.com/atom/github/pull/1807'); + }); + + it('does update the issue url if it was modified automatically', function() { + setTextIn('.github-IssueishUrl atom-text-editor', 'https://github.com/atom/github/pull/1807'); + assert.equal(wrapper.instance().getIssueishUrl(), 'https://github.com/atom/github/pull/1807'); + + setTextIn('.github-IssueishUrl atom-text-editor', 'https://github.com/atom/github/issues/1655'); + assert.equal(wrapper.instance().getIssueishUrl(), 'https://github.com/atom/github/issues/1655'); + }); + }); + + describe('open button enablement', function() { + it('disables the open button with no issue url', function() { + setTextIn('.github-IssueishUrl atom-text-editor', ''); + wrapper.update(); + + assert.isTrue(wrapper.find('button.icon-git-pull-request').prop('disabled')); + }); + + it('enables the open button when issue url box is populated', function() { + setTextIn('.github-IssueishUrl atom-text-editor', 'https://github.com/atom/github/pull/1807'); + wrapper.update(); + + assert.isFalse(wrapper.find('button.icon-git-pull-request').prop('disabled')); + }); + }); + + describe('parseUrl', function() { + it('returns an object with repo owner, repo name, and issueish number', function() { + setTextIn('.github-IssueishUrl atom-text-editor', 'https://github.com/atom/github/pull/1807'); + + assert.deepEqual(wrapper.instance().parseUrl(), { + repoOwner: 'atom', + repoName: 'github', + issueishNumber: '1807', + }); + }); + }); + + it('calls the acceptance callback', function() { + setTextIn('.github-IssueishUrl atom-text-editor', 'https://github.com/atom/github/pull/1807'); + + wrapper.find('button.icon-git-pull-request').simulate('click'); + + assert.isTrue(didAccept.calledWith({ + repoOwner: 'atom', + repoName: 'github', + issueishNumber: '1807', + })); + }); + + it('calls the cancellation callback', function() { + wrapper.find('button.github-CancelButton').simulate('click'); + assert.isTrue(didCancel.called); + }); +}); From 5d90e5bee91f5cd3d8a391ac7d5e6b5308607f2a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 30 Nov 2018 20:02:39 -0800 Subject: [PATCH 1320/4053] :fire: unnecessary check for commit sha. Handled by disabled button --- lib/views/open-commit-dialog.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/views/open-commit-dialog.js b/lib/views/open-commit-dialog.js index d81c8038d7..ba9c1ad488 100644 --- a/lib/views/open-commit-dialog.js +++ b/lib/views/open-commit-dialog.js @@ -71,10 +71,6 @@ export default class OpenCommitDialog extends React.Component { } accept() { - if (this.getCommitSha().length === 0) { - return; - } - this.props.didAccept({sha: this.getCommitSha()}); } From f3f392da2b4b2821eee6bfada2f5fce6b084343d Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 3 Dec 2018 16:42:09 +0100 Subject: [PATCH 1321/4053] unneccessary prop --- lib/controllers/issueish-detail-controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 4267f3b46f..4485e4b1f6 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -17,7 +17,6 @@ export class BareIssueishDetailController extends React.Component { login: PropTypes.string.isRequired, }).isRequired, issueish: PropTypes.any, // FIXME from IssueishPaneItemContainer.propTypes - getWorkingDirectoryPath: PropTypes.func.isRequired, }), issueishNumber: PropTypes.number.isRequired, From a830581f44c762ea7c326d0080afc6a948ed83c3 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 3 Dec 2018 16:42:24 +0100 Subject: [PATCH 1322/4053] get boolean properly --- lib/views/issueish-detail-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/issueish-detail-view.js b/lib/views/issueish-detail-view.js index 27312dff45..46752c4062 100644 --- a/lib/views/issueish-detail-view.js +++ b/lib/views/issueish-detail-view.js @@ -146,7 +146,7 @@ export class BareIssueishDetailView extends React.Component { renderPullRequestBody(issueish, childProps) { const {checkoutOp} = this.props; const reason = checkoutOp.why(); - const onBranch = reason && !reason({hidden: true, default: false}); // is there a more direct way than this? + const onBranch = !!reason && !reason({hidden: true, default: false}); // is there a more direct way than this? return ( From d5fa6506416f03a8a1fe31f6d767e69c81b80ff3 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 3 Dec 2018 17:08:33 +0100 Subject: [PATCH 1323/4053] sha not oid --- lib/views/timeline-items/commit-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/timeline-items/commit-view.js b/lib/views/timeline-items/commit-view.js index 6a0f9c4584..2b62988a01 100644 --- a/lib/views/timeline-items/commit-view.js +++ b/lib/views/timeline-items/commit-view.js @@ -67,7 +67,7 @@ export class BareCommitView extends React.Component { dangerouslySetInnerHTML={{__html: commit.messageHeadlineHTML}} onClick={this.openCommitDetailItem} /> - {commit.oid.slice(0, 8)} + {commit.sha.slice(0, 8)}
    ); } From 28dd5ce4e1680ac4637436eb8c8874c58d2202ff Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 3 Dec 2018 17:19:21 +0100 Subject: [PATCH 1324/4053] sha not oid x2 --- test/views/timeline-items/commit-view.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/views/timeline-items/commit-view.test.js b/test/views/timeline-items/commit-view.test.js index 8cb5445f8e..6baa9fcc92 100644 --- a/test/views/timeline-items/commit-view.test.js +++ b/test/views/timeline-items/commit-view.test.js @@ -19,7 +19,7 @@ describe('CommitView', function() { login: 'committer_login', }, }, - oid: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', + sha: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', message: 'commit message', messageHeadlineHTML: '

    html

    ', commitURL: 'https://github.com/aaa/bbb/commit/123abc', @@ -44,7 +44,7 @@ describe('CommitView', function() { name: 'committer_name', avatarUrl: '', user: null, }, - oid: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', + sha: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', authoredByCommitter: false, message: 'commit message', messageHeadlineHTML: '

    html

    ', @@ -72,7 +72,7 @@ describe('CommitView', function() { name: 'author_name', avatarUrl: '', user: null, }, - oid: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', + sha: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', authoredByCommitter: true, message: 'commit message', messageHeadlineHTML: '

    html

    ', @@ -100,7 +100,7 @@ describe('CommitView', function() { name: 'GitHub', avatarUrl: '', user: null, }, - oid: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', + sha: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', authoredByCommitter: false, message: 'commit message', messageHeadlineHTML: '

    html

    ', @@ -128,7 +128,7 @@ describe('CommitView', function() { name: 'Someone', email: 'noreply@github.com', avatarUrl: '', user: null, }, - oid: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', + sha: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', authoredByCommitter: false, message: 'commit message', messageHeadlineHTML: '

    html

    ', @@ -158,7 +158,7 @@ describe('CommitView', function() { login: 'committer_login', }, }, - oid: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', + sha: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', authoredByCommitter: false, message: 'commit message', messageHeadlineHTML: '

    html

    ', @@ -186,7 +186,7 @@ describe('CommitView', function() { name: 'author_name', avatarUrl: '', user: null, }, - oid: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', + sha: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', authoredByCommitter: true, message: 'full message', messageHeadlineHTML: '

    html

    ', @@ -211,7 +211,7 @@ describe('CommitView', function() { name: 'author_name', avatarUrl: '', user: null, }, - oid: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', + sha: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', authoredByCommitter: true, message: 'full message', messageHeadlineHTML: '

    inner HTML

    ', @@ -234,7 +234,7 @@ describe('CommitView', function() { name: 'author_name', avatarUrl: '', user: null, }, - oid: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', + sha: 'e6c80aa37dc6f7a5e5491e0ed6e00ec2c812b1a5', authoredByCommitter: true, message: 'full message', messageHeadlineHTML: '

    inner HTML

    ', From 24a5fae814569a8f8e90aa6928b4c3bc03f691d2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 3 Dec 2018 11:37:32 -0500 Subject: [PATCH 1325/4053] Test coverate for RecentCommitController's workspace tracking --- .../recent-commits-controller.test.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/controllers/recent-commits-controller.test.js b/test/controllers/recent-commits-controller.test.js index afe5780bb0..9e345ab931 100644 --- a/test/controllers/recent-commits-controller.test.js +++ b/test/controllers/recent-commits-controller.test.js @@ -2,6 +2,8 @@ import React from 'react'; import {shallow, mount} from 'enzyme'; import RecentCommitsController from '../../lib/controllers/recent-commits-controller'; +import CommitDetailItem from '../../lib/items/commit-detail-item'; +import URIPattern from '../../lib/atom/uri-pattern'; import {commitBuilder} from '../builder/commit'; import {cloneRepository, buildRepository} from '../helpers'; import * as reporterProxy from '../../lib/reporter-proxy'; @@ -162,4 +164,37 @@ describe('RecentCommitsController', function() { await wrapper.instance().retreatFocusFrom(RecentCommitsController.focus.RECENT_COMMIT); assert.isTrue(retreatFocusSpy.calledWith(RecentCommitsController.focus.RECENT_COMMIT)); }); + + describe('workspace tracking', function() { + beforeEach(function() { + const pattern = new URIPattern(CommitDetailItem.uriPattern); + // Prevent the Workspace from normalizing CommitDetailItem URIs + atomEnv.workspace.addOpener(uri => { + if (pattern.matches(uri).ok()) { + return { + getURI() { return uri; }, + }; + } + return undefined; + }); + }); + + it('updates the selected sha when its CommitDetailItem is activated', async function() { + const wrapper = shallow(app); + await atomEnv.workspace.open(CommitDetailItem.buildURI(workdirPath, 'abcdef')); + assert.strictEqual(wrapper.find('RecentCommitsView').prop('selectedCommitSha'), 'abcdef'); + }); + + it('silently disregards items with no getURI method', async function() { + const wrapper = shallow(app); + await atomEnv.workspace.open({item: {}}); + assert.strictEqual(wrapper.find('RecentCommitsView').prop('selectedCommitSha'), ''); + }); + + it('silently disregards items that do not match the CommitDetailItem URI pattern', async function() { + const wrapper = shallow(app); + await atomEnv.workspace.open(__filename); + assert.strictEqual(wrapper.find('RecentCommitsView').prop('selectedCommitSha'), ''); + }); + }); }); From 8afc601eb993d0504ea10ae098d5c23ff05d1cb3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 3 Dec 2018 11:57:50 -0500 Subject: [PATCH 1326/4053] Full coverage for RecentCommitsController --- .../recent-commits-controller.test.js | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/test/controllers/recent-commits-controller.test.js b/test/controllers/recent-commits-controller.test.js index 9e345ab931..e70d003cd1 100644 --- a/test/controllers/recent-commits-controller.test.js +++ b/test/controllers/recent-commits-controller.test.js @@ -144,25 +144,66 @@ describe('RecentCommitsController', function() { }); }); - it('forwards focus management methods to its view', async function() { - const wrapper = mount(app); + describe('focus management', function() { + it('forwards focus management methods to its view', async function() { + const wrapper = mount(app); + + const setFocusSpy = sinon.spy(wrapper.find('RecentCommitsView').instance(), 'setFocus'); + const rememberFocusSpy = sinon.spy(wrapper.find('RecentCommitsView').instance(), 'getFocus'); + const advanceFocusSpy = sinon.spy(wrapper.find('RecentCommitsView').instance(), 'advanceFocusFrom'); + const retreatFocusSpy = sinon.spy(wrapper.find('RecentCommitsView').instance(), 'retreatFocusFrom'); + + wrapper.instance().setFocus(RecentCommitsController.focus.RECENT_COMMIT); + assert.isTrue(setFocusSpy.calledWith(RecentCommitsController.focus.RECENT_COMMIT)); + + wrapper.instance().getFocus(document.body); + assert.isTrue(rememberFocusSpy.calledWith(document.body)); - const setFocusSpy = sinon.spy(wrapper.find('RecentCommitsView').instance(), 'setFocus'); - const rememberFocusSpy = sinon.spy(wrapper.find('RecentCommitsView').instance(), 'getFocus'); - const advanceFocusSpy = sinon.spy(wrapper.find('RecentCommitsView').instance(), 'advanceFocusFrom'); - const retreatFocusSpy = sinon.spy(wrapper.find('RecentCommitsView').instance(), 'retreatFocusFrom'); + await wrapper.instance().advanceFocusFrom(RecentCommitsController.focus.RECENT_COMMIT); + assert.isTrue(advanceFocusSpy.calledWith(RecentCommitsController.focus.RECENT_COMMIT)); - wrapper.instance().setFocus(RecentCommitsController.focus.RECENT_COMMIT); - assert.isTrue(setFocusSpy.calledWith(RecentCommitsController.focus.RECENT_COMMIT)); + await wrapper.instance().retreatFocusFrom(RecentCommitsController.focus.RECENT_COMMIT); + assert.isTrue(retreatFocusSpy.calledWith(RecentCommitsController.focus.RECENT_COMMIT)); + }); + + it('selects the first commit when focus enters the component', async function() { + const commits = ['0', '1', '2'].map(s => commitBuilder().sha(s).build()); + const wrapper = mount(React.cloneElement(app, {commits, selectedCommitSha: ''})); + const stateSpy = sinon.spy(wrapper.instance(), 'setSelectedCommitIndex'); + sinon.stub(wrapper.find('RecentCommitsView').instance(), 'setFocus').returns(true); - wrapper.instance().getFocus(document.body); - assert.isTrue(rememberFocusSpy.calledWith(document.body)); + assert.isTrue(wrapper.instance().setFocus(RecentCommitsController.focus.RECENT_COMMIT)); + assert.isTrue(stateSpy.called); + await stateSpy.lastCall.returnValue; + wrapper.update(); + + assert.strictEqual(wrapper.find('RecentCommitsView').prop('selectedCommitSha'), '0'); + }); - await wrapper.instance().advanceFocusFrom(RecentCommitsController.focus.RECENT_COMMIT); - assert.isTrue(advanceFocusSpy.calledWith(RecentCommitsController.focus.RECENT_COMMIT)); + it('leaves an existing commit selection alone', function() { + const commits = ['0', '1', '2'].map(s => commitBuilder().sha(s).build()); + const wrapper = mount(React.cloneElement(app, {commits})); + wrapper.setState({selectedCommitSha: '2'}); + const stateSpy = sinon.spy(wrapper.instance(), 'setSelectedCommitIndex'); + sinon.stub(wrapper.find('RecentCommitsView').instance(), 'setFocus').returns(true); - await wrapper.instance().retreatFocusFrom(RecentCommitsController.focus.RECENT_COMMIT); - assert.isTrue(retreatFocusSpy.calledWith(RecentCommitsController.focus.RECENT_COMMIT)); + assert.isTrue(wrapper.instance().setFocus(RecentCommitsController.focus.RECENT_COMMIT)); + assert.isFalse(stateSpy.called); + + assert.strictEqual(wrapper.find('RecentCommitsView').prop('selectedCommitSha'), '2'); + }); + + it('disregards an unrecognized focus', function() { + const commits = ['0', '1', '2'].map(s => commitBuilder().sha(s).build()); + const wrapper = mount(React.cloneElement(app, {commits, selectedCommitSha: ''})); + const stateSpy = sinon.spy(wrapper.instance(), 'setSelectedCommitIndex'); + sinon.stub(wrapper.find('RecentCommitsView').instance(), 'setFocus').returns(false); + + assert.isFalse(wrapper.instance().setFocus(Symbol('unrecognized'))); + assert.isFalse(stateSpy.called); + + assert.strictEqual(wrapper.find('RecentCommitsView').prop('selectedCommitSha'), ''); + }); }); describe('workspace tracking', function() { From 9de4e04a95fcaff759e54e0eaad9543677b57478 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 3 Dec 2018 10:24:30 -0800 Subject: [PATCH 1327/4053] remove `childProps` from `IssueDetailView` --- lib/views/issue-detail-view.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index c57547287b..a696ffacea 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -75,7 +75,7 @@ export class BareIssueDetailView extends React.Component { this.refresher.destroy(); } - renderIssueBody(issue, childProps) { + renderIssueBody(issue) { return ( @@ -94,10 +94,6 @@ export class BareIssueDetailView extends React.Component { render() { const repo = this.props.repository; const issue = this.props.issue; - const childProps = { - issue: issue.__typename === 'Issue' ? issue : null, - pullRequest: issue.__typename === 'PullRequest' ? issue : null, - }; return (
    @@ -136,7 +132,7 @@ export class BareIssueDetailView extends React.Component {
    - {this.renderIssueBody(issue, childProps)} + {this.renderIssueBody(issue)}
    - {this.renderPullRequestBody(pullRequest, childProps)} + {this.renderPullRequestBody(pullRequest)}
    From 52623a179dcfe1be6766c5e1c9e09ba9fbdf90a1 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 13 Dec 2018 13:02:52 -0800 Subject: [PATCH 1519/4053] add test for when fetch fails --- .../pr-changed-files-container.test.js | 111 ++++++++++-------- 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/test/containers/pr-changed-files-container.test.js b/test/containers/pr-changed-files-container.test.js index 6b0a24c3ea..e43ebcb225 100644 --- a/test/containers/pr-changed-files-container.test.js +++ b/test/containers/pr-changed-files-container.test.js @@ -21,6 +21,14 @@ describe('PullRequestChangedFilesContainer', function() { token="1234" endpoint={getEndpoint('github.com')} itemType={IssueishDetailItem} + destroy={() => {}} + shouldRefetch={false} + workspace={{}} + commands={{}} + keymaps={{}} + tooltips={{}} + config={{}} + localRepository={{}} {...overrideProps} /> ); @@ -33,58 +41,69 @@ describe('PullRequestChangedFilesContainer', function() { }); } - beforeEach(function() { - setDiffResponse(rawDiff); - sinon.stub(window, 'fetch').callsFake(() => Promise.resolve(diffResponse)); - }); - - it('renders a loading spinner if data has not yet been fetched', function() { - const wrapper = shallow(buildApp()); - assert.isTrue(wrapper.find('LoadingView').exists()); - }); - - it('passes extra props through to PullRequestChangedFilesController', async function() { - const extraProp = Symbol('really really extra'); - - const wrapper = shallow(buildApp({extraProp})); - await assert.async.isTrue(wrapper.update().find('MultiFilePatchController').exists()); - - const controller = wrapper.find('MultiFilePatchController'); - assert.strictEqual(controller.prop('extraProp'), extraProp); - }); + describe('when the data is able to be fetched successfully', function() { + beforeEach(function() { + setDiffResponse(rawDiff); + sinon.stub(window, 'fetch').callsFake(() => Promise.resolve(diffResponse)); + }); + it('renders a loading spinner if data has not yet been fetched', function() { + const wrapper = shallow(buildApp()); + assert.isTrue(wrapper.find('LoadingView').exists()); + }); + it('passes extra props through to PullRequestChangedFilesController', async function() { + const extraProp = Symbol('really really extra'); - it('builds the diff URL', function() { - const wrapper = shallow(buildApp({ - owner: 'smashwilson', - repo: 'pushbot', - number: 12, - endpoint: getEndpoint('github.com'), - })); + const wrapper = shallow(buildApp({extraProp})); + await assert.async.isTrue(wrapper.update().find('MultiFilePatchController').exists()); - const diffURL = wrapper.instance().getDiffURL(); - assert.strictEqual(diffURL, 'https://api.github.com/repos/smashwilson/pushbot/pulls/12'); - }); + const controller = wrapper.find('MultiFilePatchController'); + assert.strictEqual(controller.prop('extraProp'), extraProp); + }); - it('passes loaded diff data through to the controller', async function() { - const wrapper = shallow(buildApp({ - token: '4321', - })); - await assert.async.isTrue(wrapper.update().find('MultiFilePatchController').exists()); + it('builds the diff URL', function() { + const wrapper = shallow(buildApp({ + owner: 'smashwilson', + repo: 'pushbot', + number: 12, + endpoint: getEndpoint('github.com'), + })); - const controller = wrapper.find('MultiFilePatchController'); - const expected = buildMultiFilePatch(parseDiff(rawDiff)); - assert.isTrue(controller.prop('multiFilePatch').isEqual(expected)); + const diffURL = wrapper.instance().getDiffURL(); + assert.strictEqual(diffURL, 'https://api.github.com/repos/smashwilson/pushbot/pulls/12'); + }); - assert.deepEqual(window.fetch.lastCall.args, [ - 'https://api.github.com/repos/atom/github/pulls/1804', - { - headers: { - Accept: 'application/vnd.github.v3.diff', - Authorization: 'bearer 4321', + it('passes loaded diff data through to the controller', async function() { + const wrapper = shallow(buildApp({ + token: '4321', + })); + await assert.async.isTrue(wrapper.update().find('MultiFilePatchController').exists()); + + const controller = wrapper.find('MultiFilePatchController'); + const expected = buildMultiFilePatch(parseDiff(rawDiff)); + assert.isTrue(controller.prop('multiFilePatch').isEqual(expected)); + + assert.deepEqual(window.fetch.lastCall.args, [ + 'https://api.github.com/repos/atom/github/pulls/1804', + { + headers: { + Accept: 'application/vnd.github.v3.diff', + Authorization: 'bearer 4321', + }, }, - }, - ]); + ]); + }); }); - it('renders an error if fetch returns a non-ok response'); + describe('when fetch fails', function() { + it('renders an error if fetch returns a non-ok response', async function() { + const badResponse = new window.Response(rawDiff, { + status: 404, + statusText: 'oh noes', + headers: {'Content-type': 'text/plain'}, + }); + sinon.stub(window, 'fetch').callsFake(() => Promise.resolve(badResponse)); + const wrapper = shallow(buildApp()); + await assert.async.strictEqual(wrapper.update().instance().state.error, 'Unable to load diff for this PR.'); + }); + }); }); From 7f8707d9e33c50350cf0e2873154368bc9650e84 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 13 Dec 2018 22:17:19 +0100 Subject: [PATCH 1520/4053] update component atlas --- docs/react-component-atlas.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md index a619d910d8..fe8264a752 100644 --- a/docs/react-component-atlas.md +++ b/docs/react-component-atlas.md @@ -118,6 +118,13 @@ This is a high-level overview of the structure of the React component tree that > > > [``](/lib/views/pr-commit-view.js) > > > > > > Enumerate the commits associated with a pull request. +> > +> > > [``](/lib/containers/pr-changed-files-container.js) +> > > +> > > Show all the changes, separated by files, introduced in a pull request. +> > > +> > > > [``](/lib/controllers/multi-file-patch-controller.js) +> > > > [``](/lib/views/multi-file-patch-view.js) > > > [``](/lib/views/init-dialog.js) > > [``](/lib/views/clone-dialog.js) From 18a89737fa2e811b197bad073d1c4438e65e5262 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 13 Dec 2018 13:36:39 -0800 Subject: [PATCH 1521/4053] fix test to deal with improved error handling --- lib/containers/pr-changed-files-container.js | 2 +- test/containers/pr-changed-files-container.test.js | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/containers/pr-changed-files-container.js b/lib/containers/pr-changed-files-container.js index 95cccfbb9d..09e65a360f 100644 --- a/lib/containers/pr-changed-files-container.js +++ b/lib/containers/pr-changed-files-container.js @@ -84,7 +84,7 @@ export default class PullRequestChangedFilesContainer extends React.Component { const multiFilePatch = this.buildPatch(rawDiff); await new Promise(resolve => this.setState({isLoading: false, multiFilePatch}, resolve)); } else { - diffError(`Unable to fetch diff for this pull request${response ? ' : ' + response.statusText : ''}.`); + diffError(`Unable to fetch diff for this pull request${response ? ': ' + response.statusText : ''}.`); } } catch (e) { diffError('Unable to parse diff for this pull request.'); diff --git a/test/containers/pr-changed-files-container.test.js b/test/containers/pr-changed-files-container.test.js index e43ebcb225..e6d002971f 100644 --- a/test/containers/pr-changed-files-container.test.js +++ b/test/containers/pr-changed-files-container.test.js @@ -94,7 +94,7 @@ describe('PullRequestChangedFilesContainer', function() { }); }); - describe('when fetch fails', function() { + describe.only('when fetch fails', function() { it('renders an error if fetch returns a non-ok response', async function() { const badResponse = new window.Response(rawDiff, { status: 404, @@ -103,7 +103,10 @@ describe('PullRequestChangedFilesContainer', function() { }); sinon.stub(window, 'fetch').callsFake(() => Promise.resolve(badResponse)); const wrapper = shallow(buildApp()); - await assert.async.strictEqual(wrapper.update().instance().state.error, 'Unable to load diff for this PR.'); + const expectedErrorMessage = 'Unable to fetch diff for this pull request: oh noes.'; + await assert.async.deepEqual(wrapper.update().instance().state.error, expectedErrorMessage); + const errorView = wrapper.find('ErrorView'); + assert.deepEqual(errorView.prop('descriptions'), [expectedErrorMessage]); }); }); }); From 66e5a62cb41bb3dee097ca480e67d1c8b0b55f49 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 13 Dec 2018 13:41:17 -0800 Subject: [PATCH 1522/4053] :shirt: --- .../pr-changed-files-container.test.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/containers/pr-changed-files-container.test.js b/test/containers/pr-changed-files-container.test.js index e6d002971f..d45c2aa4e7 100644 --- a/test/containers/pr-changed-files-container.test.js +++ b/test/containers/pr-changed-files-container.test.js @@ -94,19 +94,19 @@ describe('PullRequestChangedFilesContainer', function() { }); }); - describe.only('when fetch fails', function() { + describe('when fetch fails', function() { it('renders an error if fetch returns a non-ok response', async function() { - const badResponse = new window.Response(rawDiff, { - status: 404, - statusText: 'oh noes', - headers: {'Content-type': 'text/plain'}, - }); - sinon.stub(window, 'fetch').callsFake(() => Promise.resolve(badResponse)); - const wrapper = shallow(buildApp()); - const expectedErrorMessage = 'Unable to fetch diff for this pull request: oh noes.'; - await assert.async.deepEqual(wrapper.update().instance().state.error, expectedErrorMessage); - const errorView = wrapper.find('ErrorView'); - assert.deepEqual(errorView.prop('descriptions'), [expectedErrorMessage]); + const badResponse = new window.Response(rawDiff, { + status: 404, + statusText: 'oh noes', + headers: {'Content-type': 'text/plain'}, }); + sinon.stub(window, 'fetch').callsFake(() => Promise.resolve(badResponse)); + const wrapper = shallow(buildApp()); + const expectedErrorMessage = 'Unable to fetch diff for this pull request: oh noes.'; + await assert.async.deepEqual(wrapper.update().instance().state.error, expectedErrorMessage); + const errorView = wrapper.find('ErrorView'); + assert.deepEqual(errorView.prop('descriptions'), [expectedErrorMessage]); + }); }); }); From aa5f337e75f72c07d2b4adf1e401904ea7ff126d Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 13 Dec 2018 14:00:07 -0800 Subject: [PATCH 1523/4053] test diff parsing error state --- .../pr-changed-files-container.test.js | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/containers/pr-changed-files-container.test.js b/test/containers/pr-changed-files-container.test.js index d45c2aa4e7..005baed4f9 100644 --- a/test/containers/pr-changed-files-container.test.js +++ b/test/containers/pr-changed-files-container.test.js @@ -94,7 +94,20 @@ describe('PullRequestChangedFilesContainer', function() { }); }); - describe('when fetch fails', function() { + describe('error states', function() { + async function assertErrorRendered(expectedErrorMessage, wrapper) { + await assert.async.deepEqual(wrapper.update().instance().state.error, expectedErrorMessage); + const errorView = wrapper.find('ErrorView'); + assert.deepEqual(errorView.prop('descriptions'), [expectedErrorMessage]); + } + it('renders an error if diff parsing fails', async function() { + setDiffResponse('bad diff no treat for you'); + sinon.stub(window, 'fetch').callsFake(() => Promise.resolve(diffResponse)); + const wrapper = shallow(buildApp()); + const expectedErrorMessage = 'Unable to parse diff for this pull request.'; + await assertErrorRendered('Unable to parse diff for this pull request.', wrapper); + }); + it('renders an error if fetch returns a non-ok response', async function() { const badResponse = new window.Response(rawDiff, { status: 404, @@ -103,10 +116,7 @@ describe('PullRequestChangedFilesContainer', function() { }); sinon.stub(window, 'fetch').callsFake(() => Promise.resolve(badResponse)); const wrapper = shallow(buildApp()); - const expectedErrorMessage = 'Unable to fetch diff for this pull request: oh noes.'; - await assert.async.deepEqual(wrapper.update().instance().state.error, expectedErrorMessage); - const errorView = wrapper.find('ErrorView'); - assert.deepEqual(errorView.prop('descriptions'), [expectedErrorMessage]); + await assertErrorRendered('Unable to fetch diff for this pull request: oh noes.', wrapper); }); }); }); From 424b5c41b9ba71b11829342abca7953ad4903eca Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 13 Dec 2018 14:40:13 -0800 Subject: [PATCH 1524/4053] :shirt: againnn --- test/containers/pr-changed-files-container.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/containers/pr-changed-files-container.test.js b/test/containers/pr-changed-files-container.test.js index 005baed4f9..78a98d1a71 100644 --- a/test/containers/pr-changed-files-container.test.js +++ b/test/containers/pr-changed-files-container.test.js @@ -104,7 +104,6 @@ describe('PullRequestChangedFilesContainer', function() { setDiffResponse('bad diff no treat for you'); sinon.stub(window, 'fetch').callsFake(() => Promise.resolve(diffResponse)); const wrapper = shallow(buildApp()); - const expectedErrorMessage = 'Unable to parse diff for this pull request.'; await assertErrorRendered('Unable to parse diff for this pull request.', wrapper); }); From 879c6389a4fbe461faf8c225c9a4c3388e8bdd4f Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 13 Dec 2018 14:47:27 -0800 Subject: [PATCH 1525/4053] istanbul ignore dev debugging code to log rate limits --- lib/relay-network-layer-manager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/relay-network-layer-manager.js b/lib/relay-network-layer-manager.js index fd6abf2861..f378fc9624 100644 --- a/lib/relay-network-layer-manager.js +++ b/lib/relay-network-layer-manager.js @@ -105,6 +105,7 @@ function createFetchQuery(url) { }); try { + /* istanbul ignore next */ atom && atom.inDevMode() && logRatelimitApi(response.headers); } catch (_e) { /* do nothing */ } From aeae3b35e2a4b92667d5842ceb2cf8da054a81f1 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 13 Dec 2018 16:03:10 -0800 Subject: [PATCH 1526/4053] add test for refetching data --- test/containers/pr-changed-files-container.test.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/containers/pr-changed-files-container.test.js b/test/containers/pr-changed-files-container.test.js index 78a98d1a71..c6f972dce7 100644 --- a/test/containers/pr-changed-files-container.test.js +++ b/test/containers/pr-changed-files-container.test.js @@ -92,7 +92,15 @@ describe('PullRequestChangedFilesContainer', function() { }, ]); }); - }); + it('re fetches data when shouldRefetch is true', async function() { + const wrapper = shallow(buildApp()); + await assert.async.isTrue(wrapper.update().find('MultiFilePatchController').exists()); + assert.strictEqual(window.fetch.callCount, 1); + wrapper.setProps({shouldRefetch: true}); + assert.isTrue(wrapper.instance().state.isLoading); + await assert.async.strictEqual(window.fetch.callCount, 2); + }); + }); describe('error states', function() { async function assertErrorRendered(expectedErrorMessage, wrapper) { From 714ec4b2f3696a4369766b138279f8b55e988044 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 13 Dec 2018 16:10:30 -0800 Subject: [PATCH 1527/4053] more istanbul ignoring --- lib/prop-types.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/prop-types.js b/lib/prop-types.js index bb3a298687..78fd797320 100644 --- a/lib/prop-types.js +++ b/lib/prop-types.js @@ -180,6 +180,7 @@ function createItemTypePropType(required) { } if (props[propName] === undefined || props[propName] === null) { + /* istanbul ignore else */ if (required) { return new Error(`Missing required prop ${propName} on component ${componentName}.`); } else { @@ -187,6 +188,7 @@ function createItemTypePropType(required) { } } + /* istanbul ignore if */ if (!lazyItemConstructors.has(props[propName])) { const choices = Array.from(lazyItemConstructors, each => each.name).join(', '); return new Error( From d5a0f630ceb8907110c934f7a4d48aa4ac10e07c Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 14 Dec 2018 11:41:19 +0900 Subject: [PATCH 1528/4053] Fix PrTimeline scrolling issues --- styles/pr-timeline.less | 1 + 1 file changed, 1 insertion(+) diff --git a/styles/pr-timeline.less b/styles/pr-timeline.less index 99c4e8f9f0..8a636612a5 100644 --- a/styles/pr-timeline.less +++ b/styles/pr-timeline.less @@ -3,6 +3,7 @@ @avatar-size: 20px; .github-PrTimeline { + position: relative; margin-top: @component-padding * 3; border-top: 1px solid mix(@text-color, @base-background-color, 15%); From d84707e0e41e0de254ed3994c381da74b7f8ca10 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 14 Dec 2018 19:57:30 +0900 Subject: [PATCH 1529/4053] Make header more compact --- lib/views/pr-detail-view.js | 97 +++++++++++++++--------------- styles/github-dotcom-markdown.less | 7 +++ styles/issueish-detail-view.less | 91 ++++++++++++++-------------- styles/pr-commit-view.less | 4 +- styles/pr-statuses.less | 1 - styles/pr-timeline.less | 19 +++--- 6 files changed, 111 insertions(+), 108 deletions(-) diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index bd9d9a8083..62dc2510c9 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -120,21 +120,20 @@ export class BarePullRequestDetailView extends React.Component { } renderPrMetadata(pullRequest, repo) { + // TODO: Move these infos to the tabs. E.g. Commits (3) + // {pullRequest.author.login} wants to merge{' '} + // {pullRequest.countedCommits.totalCount} commits and{' '} + // {pullRequest.changedFiles} changed files into{' '} return ( -
    - - {pullRequest.author.login} wants to merge{' '} - {pullRequest.countedCommits.totalCount} commits and{' '} - {pullRequest.changedFiles} changed files into{' '} - {pullRequest.isCrossRepository ? - `${repo.owner.login}/${pullRequest.baseRefName}` : pullRequest.baseRefName} from{' '} - {pullRequest.isCrossRepository ? - `${pullRequest.author.login}/${pullRequest.headRefName}` : pullRequest.headRefName} - -
    + + {pullRequest.isCrossRepository ? + `${repo.owner.login}/${pullRequest.baseRefName}` : pullRequest.baseRefName}{' ‹ '} + {pullRequest.isCrossRepository ? + `${pullRequest.author.login}/${pullRequest.headRefName}` : pullRequest.headRefName} + ); } @@ -167,18 +166,19 @@ export class BarePullRequestDetailView extends React.Component { {/* overview */} - No description provided.'} - switchToIssueish={this.props.switchToIssueish} - /> - - - +
    + No description provided.'} + switchToIssueish={this.props.switchToIssueish} + /> + + +
    {/* build status */} @@ -230,32 +230,30 @@ export class BarePullRequestDetailView extends React.Component {
    -
    + {this.renderPullRequestBody(pullRequest)}
    -
    +
    @@ -256,6 +256,8 @@ export class BarePullRequestDetailView extends React.Component { +
    +
    {this.renderPrMetadata(pullRequest, repo)}
    diff --git a/styles/issueish-detail-view.less b/styles/issueish-detail-view.less index e33131af99..b6ed2f685c 100644 --- a/styles/issueish-detail-view.less +++ b/styles/issueish-detail-view.less @@ -41,12 +41,18 @@ &-headerColumn { &.is-flexible { flex: 1; + display: flex; + flex-wrap: wrap; } } &-headerRow { display: flex; align-items: center; + margin: 2px 0; + &.is-fullwidth { + flex: 1 1 100%; + } } // Avatar ------------------------ @@ -57,7 +63,7 @@ } &-title { - margin: 0 0 .25em 0; + margin: 0; font-size: 1.25em; font-weight: 500; line-height: 1.3; From 3d5018aa2762f92dbe2dc462ba92874cbbc3bc32 Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 17 Dec 2018 16:51:22 +0900 Subject: [PATCH 1554/4053] Make headerLink wrap --- styles/issueish-detail-view.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/styles/issueish-detail-view.less b/styles/issueish-detail-view.less index b6ed2f685c..1f35fb3d2e 100644 --- a/styles/issueish-detail-view.less +++ b/styles/issueish-detail-view.less @@ -49,6 +49,7 @@ &-headerRow { display: flex; align-items: center; + flex-wrap: wrap; margin: 2px 0; &.is-fullwidth { flex: 1 1 100%; @@ -81,7 +82,6 @@ } &-headerLink { - margin-left: @component-padding; color: @text-color-subtle; } @@ -99,6 +99,7 @@ &-headerRefreshButton { display: inline-block; + margin-right: @component-padding; color: @text-color-subtle; cursor: pointer; From c62bb517e1d9297bd6734395656393e0e01f5809 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 17 Dec 2018 08:22:20 -0800 Subject: [PATCH 1555/4053] for realsies istanbul ignore relay-network-layer-manager --- .nycrc.json | 3 ++- lib/relay-network-layer-manager.js | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.nycrc.json b/.nycrc.json index b9380f0e5c..7135cec850 100644 --- a/.nycrc.json +++ b/.nycrc.json @@ -6,6 +6,7 @@ ], "exclude": [ "lib/views/git-cache-view.js", - "lib/views/git-timings-view.js" + "lib/views/git-timings-view.js", + "lib/relay-network-layer-manager.js" ] } diff --git a/lib/relay-network-layer-manager.js b/lib/relay-network-layer-manager.js index 555d86b931..fd6abf2861 100644 --- a/lib/relay-network-layer-manager.js +++ b/lib/relay-network-layer-manager.js @@ -1,4 +1,3 @@ -/* istanbul ignore next */ import util from 'util'; import {Environment, Network, RecordSource, Store} from 'relay-runtime'; import moment from 'moment'; From 14a2d270c8cdec24e0b065ea3df0ee1472a8b1f8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 12:20:39 -0500 Subject: [PATCH 1556/4053] IssueishDetailItem uses `github.com` as its host --- lib/controllers/root-controller.js | 2 +- test/controllers/root-controller.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index 2205e08611..b911f11b3e 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -588,7 +588,7 @@ export default class RootController extends React.Component { } acceptOpenIssueish({repoOwner, repoName, issueishNumber}) { - const uri = IssueishDetailItem.buildURI('https://api.github.com', repoOwner, repoName, issueishNumber); + const uri = IssueishDetailItem.buildURI('github.com', repoOwner, repoName, issueishNumber); this.setState({openIssueishDialogActive: false}); this.props.workspace.open(uri).then(() => { addEvent('open-issueish-in-pane', {package: 'github', from: 'dialog'}); diff --git a/test/controllers/root-controller.test.js b/test/controllers/root-controller.test.js index 5c016c7dd3..e3bf7c6530 100644 --- a/test/controllers/root-controller.test.js +++ b/test/controllers/root-controller.test.js @@ -428,7 +428,7 @@ describe('RootController', function() { resolveOpenIssueish(); await promise; - const uri = IssueishDetailItem.buildURI('https://api.github.com', repoOwner, repoName, issueishNumber); + const uri = IssueishDetailItem.buildURI('github.com', repoOwner, repoName, issueishNumber); assert.isTrue(openIssueishDetails.calledWith(uri)); From 3397d530bb778c9b98989be3e8e1f62845f83125 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 12:23:55 -0500 Subject: [PATCH 1557/4053] workdirPath is missing when looking at an issueish from another repo --- lib/controllers/issueish-detail-controller.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 360189ef3b..f1991ab5e3 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -32,6 +32,7 @@ export class BareIssueishDetailController extends React.Component { isAbsent: PropTypes.bool.isRequired, isLoading: PropTypes.bool.isRequired, isPresent: PropTypes.bool.isRequired, + workdirPath: PropTypes.string, // Connection information endpoint: EndpointPropType.isRequired, @@ -39,7 +40,6 @@ export class BareIssueishDetailController extends React.Component { // Atom environment workspace: PropTypes.object.isRequired, - workdirPath: PropTypes.string.isRequired, commands: PropTypes.object.isRequired, keymaps: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, @@ -277,6 +277,11 @@ export class BareIssueishDetailController extends React.Component { } openCommit = async ({sha}) => { + /* istanbul ignore if */ + if (!this.props.workdirPath) { + return; + } + const uri = CommitDetailItem.buildURI(this.props.workdirPath, sha); await this.props.workspace.open(uri, {pending: true}); addEvent('open-commit-in-pane', {package: 'github', from: this.constructor.name}); From 788dd705b8f2b108b48ce96d3036b76f83552643 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 12:26:15 -0500 Subject: [PATCH 1558/4053] onBranch and openCommit will not be set for issues --- lib/views/issueish-timeline-view.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/views/issueish-timeline-view.js b/lib/views/issueish-timeline-view.js index 6523173807..923f919abc 100644 --- a/lib/views/issueish-timeline-view.js +++ b/lib/views/issueish-timeline-view.js @@ -77,8 +77,13 @@ export default class IssueishTimelineView extends React.Component { pullRequest: PropTypes.shape({ timeline: TimelineConnectionPropType, }), - onBranch: PropTypes.bool.isRequired, - openCommit: PropTypes.func.isRequired, + onBranch: PropTypes.bool, + openCommit: PropTypes.func, + } + + static defaultProps = { + onBranch: false, + openCommit: () => {}, } constructor(props) { From e2bfeea6c08b95a0d18a85ed90a12f5a40edb63e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 13:51:07 -0500 Subject: [PATCH 1559/4053] Pass Endpoint into RemoteContainer separately --- lib/containers/remote-container.js | 25 ++++++++++++------------ lib/views/github-tab-view.js | 7 +++++-- test/containers/remote-container.test.js | 1 - 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/containers/remote-container.js b/lib/containers/remote-container.js index 1ef7a37033..e4aec30e9a 100644 --- a/lib/containers/remote-container.js +++ b/lib/containers/remote-container.js @@ -4,7 +4,9 @@ import {QueryRenderer, graphql} from 'react-relay'; import {incrementCounter} from '../reporter-proxy'; import {autobind} from '../helpers'; -import {RemotePropType, RemoteSetPropType, BranchSetPropType, OperationStateObserverPropType} from '../prop-types'; +import { + RemotePropType, RemoteSetPropType, BranchSetPropType, OperationStateObserverPropType, EndpointPropType, +} from '../prop-types'; import RelayNetworkLayerManager from '../relay-network-layer-manager'; import {UNAUTHENTICATED, INSUFFICIENT} from '../shared/keytar-strategy'; import RemoteController from '../controllers/remote-controller'; @@ -15,18 +17,21 @@ import GithubLoginView from '../views/github-login-view'; export default class RemoteContainer extends React.Component { static propTypes = { + // Connection loginModel: PropTypes.object.isRequired, + endpoint: EndpointPropType.isRequired, + // Repository attributes remoteOperationObserver: OperationStateObserverPropType.isRequired, + pushInProgress: PropTypes.bool.isRequired, workingDirectory: PropTypes.string.isRequired, workspace: PropTypes.object.isRequired, remote: RemotePropType.isRequired, remotes: RemoteSetPropType.isRequired, branches: BranchSetPropType.isRequired, - aheadCount: PropTypes.number, - pushInProgress: PropTypes.bool.isRequired, + // Action methods onPushBranch: PropTypes.func.isRequired, } @@ -37,7 +42,7 @@ export default class RemoteContainer extends React.Component { } fetchToken(loginModel) { - return loginModel.getToken(this.endpoint().getLoginAccount()); + return loginModel.getToken(this.props.endpoint.getLoginAccount()); } render() { @@ -67,7 +72,7 @@ export default class RemoteContainer extends React.Component { ); } - const environment = RelayNetworkLayerManager.getEnvironmentForHost(this.endpoint(), token); + const environment = RelayNetworkLayerManager.getEnvironmentForHost(this.props.endpoint, token); const query = graphql` query remoteContainerQuery($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { @@ -112,7 +117,7 @@ export default class RemoteContainer extends React.Component { return ( this.props.handlePushBranch(this.props.currentBranch, this.props.currentRemote)} remote={this.props.currentRemote} remotes={this.props.remotes} branches={this.props.branches} aheadCount={this.props.aheadCount} - pushInProgress={this.props.pushInProgress} + + onPushBranch={() => this.props.handlePushBranch(this.props.currentBranch, this.props.currentRemote)} /> ); } diff --git a/test/containers/remote-container.test.js b/test/containers/remote-container.test.js index 87c651a0c2..2d60943684 100644 --- a/test/containers/remote-container.test.js +++ b/test/containers/remote-container.test.js @@ -34,7 +34,6 @@ describe('RemoteContainer', function() { return ( Date: Mon, 17 Dec 2018 10:56:59 -0800 Subject: [PATCH 1560/4053] add tests for UserStore uncovered lines --- lib/models/user-store.js | 21 ++++++++------ test/models/user-store.test.js | 50 +++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/lib/models/user-store.js b/lib/models/user-store.js index 1f27ac7c7e..ae11ba48a2 100644 --- a/lib/models/user-store.js +++ b/lib/models/user-store.js @@ -113,6 +113,17 @@ export default class UserStore { ); } + async getToken(loginModel, loginAccount) { + if (!loginModel) { + return null; + } + const token = await loginModel.getToken(loginAccount); + if (token === UNAUTHENTICATED || token === INSUFFICIENT) { + return null; + } + return token; + } + async loadMentionableUsers(remote) { const cached = this.cache.get(remote); if (cached !== null) { @@ -121,14 +132,8 @@ export default class UserStore { } const endpoint = remote.getEndpoint(); - - const loginModel = this.loginObserver.getActiveModel(); - if (!loginModel) { - return; - } - - const token = await loginModel.getToken(endpoint.getLoginAccount()); - if (token === UNAUTHENTICATED || token === INSUFFICIENT) { + const token = await this.getToken(this.loginObserver.getActiveModel(), endpoint.getLoginAccount()); + if (!token) { return; } diff --git a/test/models/user-store.test.js b/test/models/user-store.test.js index 10ef5c993c..61f46a4694 100644 --- a/test/models/user-store.test.js +++ b/test/models/user-store.test.js @@ -3,7 +3,7 @@ import dedent from 'dedent-js'; import UserStore, {source} from '../../lib/models/user-store'; import Author, {nullAuthor} from '../../lib/models/author'; import GithubLoginModel from '../../lib/models/github-login-model'; -import {InMemoryStrategy} from '../../lib/shared/keytar-strategy'; +import {InMemoryStrategy, UNAUTHENTICATED} from '../../lib/shared/keytar-strategy'; import {expectRelayQuery} from '../../lib/relay-network-layer-manager'; import {cloneRepository, buildRepository, FAKE_USER} from '../helpers'; @@ -365,6 +365,51 @@ describe('UserStore', function() { ]); }); + describe.only('getToken', function() { + let repository, workdirPath; + beforeEach(async function() { + workdirPath = await cloneRepository('multiple-commits'); + repository = await buildRepository(workdirPath); + }) + it('returns null if loginModel is falsy', async function() { + store = new UserStore({repository, login, config}); + const token = await store.getToken(undefined, 'https://api.github.com'); + assert.isNull(token); + }); + + it('returns null if token is INSUFFICIENT', async function() { + const loginModel = new GithubLoginModel(InMemoryStrategy); + sinon.stub(loginModel, 'getScopes').returns(Promise.resolve(['repo', 'read:org'])); + + await loginModel.setToken('https://api.github.com', '1234'); + store = new UserStore({repository, loginModel, config}); + const token = await store.getToken(loginModel, 'https://api.github.com'); + assert.isNull(token); + }); + + it('returns null if token is UNAUTHENTICATED', async function() { + const loginModel = new GithubLoginModel(InMemoryStrategy); + sinon.stub(loginModel, 'getToken').returns(Promise.resolve(UNAUTHENTICATED)); + + store = new UserStore({repository, loginModel, config}); + const getToken = await store.getToken(loginModel, 'https://api.github.com'); + assert.isNull(getToken); + }); + + it('return token if token is sufficient and model is truthy', async function() { + const loginModel = new GithubLoginModel(InMemoryStrategy); + sinon.stub(loginModel, 'getScopes').returns(Promise.resolve(['repo', 'read:org', 'user:email'])); + + const expectedToken = '1234'; + await loginModel.setToken('https://api.github.com', expectedToken); + store = new UserStore({repository, loginModel, config}); + const actualToken = await store.getToken(loginModel, 'https://api.github.com'); + assert.strictEqual(expectedToken, actualToken); + }); + + }); + + describe('GraphQL response caching', function() { it('caches mentionable users acquired from GraphQL', async function() { await login.setToken('https://api.github.com', '1234'); @@ -384,6 +429,7 @@ describe('UserStore', function() { store = new UserStore({repository, login, config}); sinon.spy(store, 'loadUsers'); + sinon.spy(store, 'getToken'); // The first update is triggered by the commiter, the second from GraphQL results arriving. await nextUpdatePromise(); @@ -396,6 +442,8 @@ describe('UserStore', function() { await assert.async.strictEqual(store.loadUsers.callCount, 2); await store.loadUsers.returnValues[1]; + await assert.async.strictEqual(store.getToken.callCount, 1) + assert.deepEqual(store.getUsers(), [ new Author('smashwilson@github.com', 'Ash Wilson', 'smashwilson'), new Author('mona@lisa.com', 'Mona Lisa', 'octocat'), From 83c8ecb3a4e4529a617f7f0bdcff1c4c2f60ba73 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 17 Dec 2018 11:01:14 -0800 Subject: [PATCH 1561/4053] stupid .only --- test/models/user-store.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/user-store.test.js b/test/models/user-store.test.js index 61f46a4694..deec063032 100644 --- a/test/models/user-store.test.js +++ b/test/models/user-store.test.js @@ -365,7 +365,7 @@ describe('UserStore', function() { ]); }); - describe.only('getToken', function() { + describe('getToken', function() { let repository, workdirPath; beforeEach(async function() { workdirPath = await cloneRepository('multiple-commits'); From b745c3724f1a11825a9edb809459117fc2506908 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 17 Dec 2018 11:32:39 -0800 Subject: [PATCH 1562/4053] :shirt: I really need to fix my in editor linting. --- test/models/user-store.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/models/user-store.test.js b/test/models/user-store.test.js index deec063032..6e33d407d0 100644 --- a/test/models/user-store.test.js +++ b/test/models/user-store.test.js @@ -370,7 +370,7 @@ describe('UserStore', function() { beforeEach(async function() { workdirPath = await cloneRepository('multiple-commits'); repository = await buildRepository(workdirPath); - }) + }); it('returns null if loginModel is falsy', async function() { store = new UserStore({repository, login, config}); const token = await store.getToken(undefined, 'https://api.github.com'); @@ -442,7 +442,7 @@ describe('UserStore', function() { await assert.async.strictEqual(store.loadUsers.callCount, 2); await store.loadUsers.returnValues[1]; - await assert.async.strictEqual(store.getToken.callCount, 1) + await assert.async.strictEqual(store.getToken.callCount, 1); assert.deepEqual(store.getUsers(), [ new Author('smashwilson@github.com', 'Ash Wilson', 'smashwilson'), From b9b29c37f6f36c76468311f011a7790dbd37cdf6 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 17 Dec 2018 11:45:33 -0800 Subject: [PATCH 1563/4053] log console errors in `PullRequestChangedFilesContainer` --- lib/containers/pr-changed-files-container.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/containers/pr-changed-files-container.js b/lib/containers/pr-changed-files-container.js index e569f733fb..ece3bf4a82 100644 --- a/lib/containers/pr-changed-files-container.js +++ b/lib/containers/pr-changed-files-container.js @@ -64,8 +64,13 @@ export default class PullRequestChangedFilesContainer extends React.Component { } async fetchDiff() { - const diffError = message => new Promise(resolve => - this.setState({isLoading: false, error: message}, resolve)); + const diffError = (message, err = null) => new Promise(resolve => { + if (err) { + // eslint-disable-next-line no-console + console.error(err); + } + this.setState({isLoading: false, error: message}, resolve); + }); const url = this.getDiffURL(); const response = await fetch(url, { @@ -75,7 +80,7 @@ export default class PullRequestChangedFilesContainer extends React.Component { }, // eslint-disable-next-line handle-callback-err }).catch(err => { - diffError(`Network error encountered at fetching ${url}`); + diffError(`Network error encountered at fetching ${url}`, err); }); if (this.state.error) { return; @@ -88,8 +93,8 @@ export default class PullRequestChangedFilesContainer extends React.Component { } else { diffError(`Unable to fetch diff for this pull request${response ? ': ' + response.statusText : ''}.`); } - } catch (e) { - diffError('Unable to parse diff for this pull request.'); + } catch (err) { + diffError('Unable to parse diff for this pull request.', err); } } From f784275d4e8b1e3654d70c052525ed7db40e9825 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 15:03:18 -0500 Subject: [PATCH 1564/4053] Separate cmd- and ctrl- commands by platform --- keymaps/git.cson | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/keymaps/git.cson b/keymaps/git.cson index dddf1e91a0..0579826954 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -26,21 +26,33 @@ 'shift-tab': 'core:focus-previous' 'o': 'github:jump-to-file' 'left': 'core:move-left' +'.platform-darwin .github-StagingView': 'cmd-left': 'core:move-left' +'.platform-linux .github-StagingView, .platform-win32 .github-StagingView': + 'ctrl-left': 'core-move-left' '.github-CommitView button': 'tab': 'core:focus-next' 'shift-tab': 'core:focus-previous' -'.github-StagingView.unstaged-changes-focused': +'.platform-darwin .github-StagingView.unstaged-changes-focused': 'cmd-backspace': 'github:discard-changes-in-selected-files' +'.platform-linux .github-StagingView.unstaged-changes-focused': + 'ctrl-backspace': 'github:discard-changes-in-selected-files' +'.platform-win32 .github-StagingView.unstaged-changes-focused': + # Repeated to avoid line length 'ctrl-backspace': 'github:discard-changes-in-selected-files' '.github-CommitView-editor atom-text-editor:not([mini])': - 'cmd-enter': 'github:commit' - 'ctrl-enter': 'github:commit' 'tab': 'core:focus-next' 'shift-tab': 'core:focus-previous' +'.platform-darwin .github-CommitView-editor atom-text-editor:not([mini])': + 'cmd-enter': 'github:commit' +'.platform-win32 .github-CommitView-editor atom-text-editor:not([mini])': + 'ctrl-enter': 'github:commit' +'.platform-linux .github-CommitView-editor atom-text-editor:not([mini])': + # Repeated to avoid line length + 'ctrl-enter': 'github:commit' '.github-CommitView-commitPreview': 'cmd-left': 'github:dive' @@ -49,25 +61,38 @@ '.github-RecentCommits': 'enter': 'github:dive' - 'cmd-left': 'github:dive' - 'ctrl-left': 'github:dive' 'tab': 'core:focus-next' 'shift-tab': 'core:focus-previous' +'.platform-darwin .github-RecentCommits': + 'cmd-left': 'github:dive' +'.platform-win32 .github-RecentCommits, .platform-linux .github-RecentCommits': + 'ctrl-left': 'github:dive' -'.github-CommitDetailView': +'.platform-darwin .github-CommitDetailView': 'cmd-right': 'github:surface' +'.platform-win32 .github-CommitDetailView': + 'ctrl-right': 'github:surface' +'.platform-linux .github-CommitDetailView': 'ctrl-right': 'github:surface' '.github-FilePatchView atom-text-editor:not([mini])': +'.platform-darwin .github-FilePatchView atom-text-editor:not([mini])': 'cmd-/': 'github:toggle-patch-selection-mode' - 'ctrl-/': 'github:toggle-patch-selection-mode' 'cmd-backspace': 'github:discard-selected-lines' - 'ctrl-backspace': 'github:discard-selected-lines' - 'ctrl-enter': 'core:confirm' 'cmd-enter': 'core:confirm' 'cmd-right': 'github:surface' - 'ctrl-right': 'github:surface' 'cmd-o': 'github:jump-to-file' +'.platform-win32 .github-FilePatchView atom-text-editor:not([mini])': + 'ctrl-/': 'github:toggle-patch-selection-mode' + 'ctrl-backspace': 'github:discard-selected-lines' + 'ctrl-enter': 'core:confirm' + 'ctrl-right': 'github:surface' + 'ctrl-o': 'github:jump-to-file' +'.platform-linux .github-FilePatchView atom-text-editor:not([mini])': + 'ctrl-/': 'github:toggle-patch-selection-mode' + 'ctrl-backspace': 'github:discard-selected-lines' + 'ctrl-enter': 'core:confirm' + 'ctrl-right': 'github:surface' 'ctrl-o': 'github:jump-to-file' '.github-FilePatchView--hunkMode atom-text-editor:not([mini])': From d07dac77ec88fbb25eb6b34fb38f2432d9d82eed Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 15:03:43 -0500 Subject: [PATCH 1565/4053] Support unprefixed MultiFilePatch commands until they're editable --- keymaps/git.cson | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/keymaps/git.cson b/keymaps/git.cson index 0579826954..067b05035b 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -76,6 +76,13 @@ 'ctrl-right': 'github:surface' '.github-FilePatchView atom-text-editor:not([mini])': + # Only support unprefixed file patch keybindings until MultiFilePatch is + # editable + '/': 'github:toggle-patch-selection-mode' + 'backspace': 'github:discard-selected-lines' + 'enter': 'core:confirm' + 'right': 'github:surface' + 'o': 'github:jump-to-file' '.platform-darwin .github-FilePatchView atom-text-editor:not([mini])': 'cmd-/': 'github:toggle-patch-selection-mode' 'cmd-backspace': 'github:discard-selected-lines' From 214e3129ce037e4d38245fafa1ba02e04a92c70c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 15:10:59 -0500 Subject: [PATCH 1566/4053] CommitDetailView is not actually a focus target --- keymaps/git.cson | 7 ------- 1 file changed, 7 deletions(-) diff --git a/keymaps/git.cson b/keymaps/git.cson index 067b05035b..e031268f1b 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -68,13 +68,6 @@ '.platform-win32 .github-RecentCommits, .platform-linux .github-RecentCommits': 'ctrl-left': 'github:dive' -'.platform-darwin .github-CommitDetailView': - 'cmd-right': 'github:surface' -'.platform-win32 .github-CommitDetailView': - 'ctrl-right': 'github:surface' -'.platform-linux .github-CommitDetailView': - 'ctrl-right': 'github:surface' - '.github-FilePatchView atom-text-editor:not([mini])': # Only support unprefixed file patch keybindings until MultiFilePatch is # editable From cd9f9203563d67cd68541f9f7d7aef6a62716276 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 15:11:14 -0500 Subject: [PATCH 1567/4053] Dive from recent commits on left or enter --- keymaps/git.cson | 1 + 1 file changed, 1 insertion(+) diff --git a/keymaps/git.cson b/keymaps/git.cson index e031268f1b..bcd27f7bd4 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -60,6 +60,7 @@ 'enter': 'native!' '.github-RecentCommits': + 'left': 'github:dive' 'enter': 'github:dive' 'tab': 'core:focus-next' 'shift-tab': 'core:focus-previous' From 9a0344e95e580614cff0f081db280ce20d737e3a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 15:13:26 -0500 Subject: [PATCH 1568/4053] Split commitPreview bindings by platform --- keymaps/git.cson | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/keymaps/git.cson b/keymaps/git.cson index bcd27f7bd4..452c1f34a0 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -55,9 +55,13 @@ 'ctrl-enter': 'github:commit' '.github-CommitView-commitPreview': + 'enter': 'native!' +'.platform-darwin .github-CommitView-commitPreview': 'cmd-left': 'github:dive' +'.platform-win32 .github-CommitView-commitPreview': + 'ctrl-left': 'github:dive' +'.platform-linux .github-CommitView-commitPreview': 'ctrl-left': 'github:dive' - 'enter': 'native!' '.github-RecentCommits': 'left': 'github:dive' From 5b7c575cc07976fb2bbcb3bdf2c511cf3de64dcd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 15:15:09 -0500 Subject: [PATCH 1569/4053] Dive on unadorned "left" --- keymaps/git.cson | 1 + 1 file changed, 1 insertion(+) diff --git a/keymaps/git.cson b/keymaps/git.cson index 452c1f34a0..67c178042e 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -55,6 +55,7 @@ 'ctrl-enter': 'github:commit' '.github-CommitView-commitPreview': + 'left': 'github-dive' 'enter': 'native!' '.platform-darwin .github-CommitView-commitPreview': 'cmd-left': 'github:dive' From 54f00ba12e0dcb96b63a1a8983b750b348c2fbf7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 15:19:11 -0500 Subject: [PATCH 1570/4053] Dive from staged and unstaged file lists on enter --- keymaps/git.cson | 1 + 1 file changed, 1 insertion(+) diff --git a/keymaps/git.cson b/keymaps/git.cson index 67c178042e..ea44c08369 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -26,6 +26,7 @@ 'shift-tab': 'core:focus-previous' 'o': 'github:jump-to-file' 'left': 'core:move-left' + 'enter': 'core:move-left' '.platform-darwin .github-StagingView': 'cmd-left': 'core:move-left' '.platform-linux .github-StagingView, .platform-win32 .github-StagingView': From 9d50524426801240c8107b01f40120759c3808ef Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 15:24:35 -0500 Subject: [PATCH 1571/4053] Split .github-Git bindings --- keymaps/git.cson | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/keymaps/git.cson b/keymaps/git.cson index ea44c08369..4945d43710 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -17,8 +17,9 @@ 'alt-m enter': 'github:resolve-as-current' 'alt-m r': 'github:revert-current' -'.github-Git': +'.platform-darwin .github-Git': 'cmd-enter': 'github:commit' +'.platform-win32 .github-Git, .platform-linux .github-Git': 'ctrl-enter': 'github:commit' '.github-StagingView': From a5fe3a0b9afbcd5999be76ab26f630cf5dcd5045 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 17 Dec 2018 15:31:05 -0500 Subject: [PATCH 1572/4053] Let's not do that just yet --- keymaps/git.cson | 1 - 1 file changed, 1 deletion(-) diff --git a/keymaps/git.cson b/keymaps/git.cson index 4945d43710..887217721b 100644 --- a/keymaps/git.cson +++ b/keymaps/git.cson @@ -27,7 +27,6 @@ 'shift-tab': 'core:focus-previous' 'o': 'github:jump-to-file' 'left': 'core:move-left' - 'enter': 'core:move-left' '.platform-darwin .github-StagingView': 'cmd-left': 'core:move-left' '.platform-linux .github-StagingView, .platform-win32 .github-StagingView': From 1c58579e317349414badcb11528f73e6555e4af7 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 17 Dec 2018 13:30:00 -0800 Subject: [PATCH 1573/4053] add test for `UserStore.loadMentionableUsers` --- test/models/user-store.test.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/test/models/user-store.test.js b/test/models/user-store.test.js index 6e33d407d0..6d5d255928 100644 --- a/test/models/user-store.test.js +++ b/test/models/user-store.test.js @@ -408,7 +408,23 @@ describe('UserStore', function() { }); }); + describe('loadMentionableUsers', function() { + it('returns undefined if token is null', async function() { + const workdirPath = await cloneRepository('multiple-commits'); + const repository = await buildRepository(workdirPath); + await repository.setConfig('remote.origin.url', 'git@github.com:me/stuff.git'); + + store = new UserStore({repository, login, config}); + sinon.stub(store, 'getToken').returns(null); + + const remoteSet = await repository.getRemotes(); + const remote = remoteSet.byDotcomRepo.get('me/stuff')[0]; + + const users = await store.loadMentionableUsers(remote); + assert.notOk(users); + }); + }); describe('GraphQL response caching', function() { it('caches mentionable users acquired from GraphQL', async function() { @@ -431,7 +447,7 @@ describe('UserStore', function() { sinon.spy(store, 'loadUsers'); sinon.spy(store, 'getToken'); - // The first update is triggered by the commiter, the second from GraphQL results arriving. + // The first update is triggered by the committer, the second from GraphQL results arriving. await nextUpdatePromise(); await nextUpdatePromise(); From ac64240e656583d31d0d86d2388091c746b14d3e Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 17 Dec 2018 13:56:37 -0800 Subject: [PATCH 1574/4053] add tests for `ErrorView` --- lib/views/error-view.js | 2 +- test/views/error-view.test.js | 69 +++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 test/views/error-view.test.js diff --git a/lib/views/error-view.js b/lib/views/error-view.js index 21e47d2200..6d1228bdf6 100644 --- a/lib/views/error-view.js +++ b/lib/views/error-view.js @@ -36,7 +36,7 @@ export default class ErrorView extends React.Component { )} {this.props.logout && ( - + )}
    diff --git a/test/views/error-view.test.js b/test/views/error-view.test.js new file mode 100644 index 0000000000..f36a1c93a6 --- /dev/null +++ b/test/views/error-view.test.js @@ -0,0 +1,69 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import ErrorView from '../../lib/views/error-view'; + +describe('ErrorView', function() { + function buildApp(overrideProps = {}) { + return ( + {}} + logout={() => {}} + {...overrideProps} + /> + ); + } + it('renders title', function() { + const wrapper = shallow(buildApp()); + const title = wrapper.find('.github-Message-title'); + assert.strictEqual(title.text(), 'zomg'); + }); + + it('renders descriptions', function() { + const wrapper = shallow(buildApp()); + const descriptions = wrapper.find('.github-Message-description'); + assert.lengthOf(descriptions, 3); + assert.strictEqual(descriptions.at(0).text(), 'something'); + assert.strictEqual(descriptions.at(1).text(), 'went'); + assert.strictEqual(descriptions.at(2).text(), 'wrong'); + }); + + it('renders retry button that if retry prop is passed', function() { + const retrySpy = sinon.spy(); + const wrapper = shallow(buildApp({retry: retrySpy})); + + const retryButton = wrapper.find('.btn-primary'); + assert.strictEqual(retryButton.text(), 'Try Again'); + + assert.strictEqual(retrySpy.callCount, 0); + retryButton.simulate('click'); + assert.strictEqual(retrySpy.callCount, 1); + }); + + it('does not render retry button if retry prop is not passed', function() { + const wrapper = shallow(buildApp({retry: null})); + const retryButton = wrapper.find('.btn-primary'); + assert.lengthOf(retryButton, 0); + }); + + it('renders logout button if logout prop is passed', function() { + const logoutSpy = sinon.spy(); + const wrapper = shallow(buildApp({logout: logoutSpy})); + + const logoutButton = wrapper.find('.btn-logout'); + assert.strictEqual(logoutButton.text(), 'Logout'); + + assert.strictEqual(logoutSpy.callCount, 0); + logoutButton.simulate('click'); + assert.strictEqual(logoutSpy.callCount, 1); + }); + + it('does not render logout button if logout prop is not passed', function() { + const wrapper = shallow(buildApp({logout: null})); + const logoutButton = wrapper.find('.btn-logout'); + assert.lengthOf(logoutButton, 0); + }); +}); From 0e9e1cd98a9f14aa799bc592fdd713ee85cf8e89 Mon Sep 17 00:00:00 2001 From: simurai Date: Tue, 18 Dec 2018 10:52:26 +0900 Subject: [PATCH 1575/4053] Move dock styles to its own file --- styles/dock.less | 134 +++++++++++++++++++++++++++++++ styles/git-tab.less | 32 -------- styles/issueish-detail-view.less | 95 ---------------------- 3 files changed, 134 insertions(+), 127 deletions(-) create mode 100644 styles/dock.less diff --git a/styles/dock.less b/styles/dock.less new file mode 100644 index 0000000000..525c327b4e --- /dev/null +++ b/styles/dock.less @@ -0,0 +1,134 @@ +@import "variables"; + +// Dock overrides +// Here ALL the overrides when a pane/panel gets used as a dock item +// It's not too high of a priority, but still good to check (and fix) once in a while + + +// Git Panel (in bottom dock) ---------------------------------------------- + +atom-dock.bottom { + .github-Git { + flex-direction: row; + } + + .github-StagingView { + display: contents; // make these invisible to layout + } + + .github-Git > div { + flex: 1; + } + + .github-StagedChanges { + border-left: 1px solid @base-border-color; + border-right: 1px solid @base-border-color; + } + + .github-StagingView-header { + flex-wrap: wrap; + } + .github-StagingView-group:first-child .github-StagingView-header { + border-top: 1px solid @base-border-color; + } + + .github-RecentCommits { + max-height: none; + border-left: 1px solid @base-border-color; + } + +} + + +// GitHub Pane ---------------------------------------------- + +atom-dock .react-tabs__tab { + padding: none; + + &--selected { + font-weight: bold; + } +} + +atom-dock .github-IssueishDetailView { + padding: @component-padding; + font-size: @font-size; + background-color: @tool-panel-background-color; + + &-tab { + border: none; + } + + &-tablist { + border-bottom: none; + display: inline; + } + + &-header { + margin-bottom: @component-padding*1.5; + padding: 0; + border: none; + border-radius: 0; + } + + &-headerGroup { + margin: 0; + } + + &-avatarImage { + width: 24px; + height: 24px; + border-radius: @component-border-radius; + } + + &-title { + font-size: 1.25em; + font-weight: 500; + line-height: 1.3; + } + + &-reactions { + padding-top: @component-padding; + } + + &-buildStatus { + margin: @component-padding 0; + + &:empty { + display: none; + } + } + + .github-PrTimeline { + margin-top: @component-padding; + } + + .timeline-item { + margin: 0; + padding: @component-padding*1.5 0; + } + + .pre-timeline-item-icon { + position: relative; + margin: 0; + &:before { + vertical-align: middle; + } + } + + &-container > .github-DotComMarkdownHtml { + margin: @component-padding 0; + } + +} + +atom-dock .github-PrStatuses { + &-header, + &-list-item { + border-radius: 0; + padding-left: 0; + padding-right: 0; + border-left: none; + border-right: none; + } +} diff --git a/styles/git-tab.less b/styles/git-tab.less index a3bfb85e5d..54d6ff2bcf 100644 --- a/styles/git-tab.less +++ b/styles/git-tab.less @@ -45,35 +45,3 @@ } } } - -atom-dock.bottom { - .github-Git { - flex-direction: row; - } - - .github-StagingView { - display: contents; // make these invisible to layout - } - - .github-Git > div { - flex: 1; - } - - .github-StagedChanges { - border-left: 1px solid @base-border-color; - border-right: 1px solid @base-border-color; - } - - .github-StagingView-header { - flex-wrap: wrap; - } - .github-StagingView-group:first-child .github-StagingView-header { - border-top: 1px solid @base-border-color; - } - - .github-RecentCommits { - max-height: none; - border-left: 1px solid @base-border-color; - } - -} diff --git a/styles/issueish-detail-view.less b/styles/issueish-detail-view.less index 1f35fb3d2e..27c61d54b4 100644 --- a/styles/issueish-detail-view.less +++ b/styles/issueish-detail-view.less @@ -241,98 +241,3 @@ } } - - -// Dock Overrides ------------------------ -// When the IssueishDetailView is shown as a Dock item - -atom-dock .react-tabs__tab { - padding: none; - - &--selected { - font-weight: bold; - } -} - -atom-dock .github-IssueishDetailView { - padding: @component-padding; - font-size: @font-size; - background-color: @tool-panel-background-color; - - &-tab { - border: none; - } - - &-tablist { - border-bottom: none; - display: inline; - } - - &-header { - margin-bottom: @component-padding*1.5; - padding: 0; - border: none; - border-radius: 0; - } - - &-headerGroup { - margin: 0; - } - - &-avatarImage { - width: 24px; - height: 24px; - border-radius: @component-border-radius; - } - - &-title { - font-size: 1.25em; - font-weight: 500; - line-height: 1.3; - } - - &-reactions { - padding-top: @component-padding; - } - - &-buildStatus { - margin: @component-padding 0; - - &:empty { - display: none; - } - } - - .github-PrTimeline { - margin-top: @component-padding; - } - - .timeline-item { - margin: 0; - padding: @component-padding*1.5 0; - } - - .pre-timeline-item-icon { - position: relative; - margin: 0; - &:before { - vertical-align: middle; - } - } - - &-container > .github-DotComMarkdownHtml { - margin: @component-padding 0; - } - -} - -atom-dock .github-PrStatuses { - &-header, - &-list-item { - border-radius: 0; - padding-left: 0; - padding-right: 0; - border-left: none; - border-right: none; - } -} From 12c6df112342fa010ee7bba523caa05bd29c774b Mon Sep 17 00:00:00 2001 From: simurai Date: Tue, 18 Dec 2018 11:02:32 +0900 Subject: [PATCH 1576/4053] Separate left/right and bottom docks --- styles/dock.less | 139 ++++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/styles/dock.less b/styles/dock.less index 525c327b4e..8bdc4a7a3a 100644 --- a/styles/dock.less +++ b/styles/dock.less @@ -42,93 +42,96 @@ atom-dock.bottom { // GitHub Pane ---------------------------------------------- -atom-dock .react-tabs__tab { - padding: none; +atom-dock.left, +atom-dock.right { + .react-tabs__tab { + padding: none; - &--selected { - font-weight: bold; + &--selected { + font-weight: bold; + } } -} -atom-dock .github-IssueishDetailView { - padding: @component-padding; - font-size: @font-size; - background-color: @tool-panel-background-color; + .github-IssueishDetailView { + padding: @component-padding; + font-size: @font-size; + background-color: @tool-panel-background-color; - &-tab { - border: none; - } + &-tab { + border: none; + } - &-tablist { - border-bottom: none; - display: inline; - } + &-tablist { + border-bottom: none; + display: inline; + } - &-header { - margin-bottom: @component-padding*1.5; - padding: 0; - border: none; - border-radius: 0; - } + &-header { + margin-bottom: @component-padding*1.5; + padding: 0; + border: none; + border-radius: 0; + } - &-headerGroup { - margin: 0; - } + &-headerGroup { + margin: 0; + } - &-avatarImage { - width: 24px; - height: 24px; - border-radius: @component-border-radius; - } + &-avatarImage { + width: 24px; + height: 24px; + border-radius: @component-border-radius; + } - &-title { - font-size: 1.25em; - font-weight: 500; - line-height: 1.3; - } + &-title { + font-size: 1.25em; + font-weight: 500; + line-height: 1.3; + } - &-reactions { - padding-top: @component-padding; - } + &-reactions { + padding-top: @component-padding; + } - &-buildStatus { - margin: @component-padding 0; + &-buildStatus { + margin: @component-padding 0; - &:empty { - display: none; + &:empty { + display: none; + } } - } - .github-PrTimeline { - margin-top: @component-padding; - } + .github-PrTimeline { + margin-top: @component-padding; + } - .timeline-item { - margin: 0; - padding: @component-padding*1.5 0; - } + .timeline-item { + margin: 0; + padding: @component-padding*1.5 0; + } - .pre-timeline-item-icon { - position: relative; - margin: 0; - &:before { - vertical-align: middle; + .pre-timeline-item-icon { + position: relative; + margin: 0; + &:before { + vertical-align: middle; + } } - } - &-container > .github-DotComMarkdownHtml { - margin: @component-padding 0; - } + &-container > .github-DotComMarkdownHtml { + margin: @component-padding 0; + } -} + } -atom-dock .github-PrStatuses { - &-header, - &-list-item { - border-radius: 0; - padding-left: 0; - padding-right: 0; - border-left: none; - border-right: none; + .github-PrStatuses { + &-header, + &-list-item { + border-radius: 0; + padding-left: 0; + padding-right: 0; + border-left: none; + border-right: none; + } } } From 8b1055b5753f3c3643f3066101ade67b75f7e9a5 Mon Sep 17 00:00:00 2001 From: simurai Date: Tue, 18 Dec 2018 14:48:04 +0900 Subject: [PATCH 1577/4053] Restyle PR panes in docks --- styles/dock.less | 130 ++++++++++++++++--------------------- styles/pr-commit-view.less | 3 +- 2 files changed, 59 insertions(+), 74 deletions(-) diff --git a/styles/dock.less b/styles/dock.less index 8bdc4a7a3a..ad2ab5b7da 100644 --- a/styles/dock.less +++ b/styles/dock.less @@ -40,98 +40,82 @@ atom-dock.bottom { } -// GitHub Pane ---------------------------------------------- +// GitHub Pane (in left/right dock) ---------------------------------------------- -atom-dock.left, -atom-dock.right { - .react-tabs__tab { - padding: none; +atom-dock.left .github-IssueishDetailView, +atom-dock.right .github-IssueishDetailView { - &--selected { - font-weight: bold; - } - } - - .github-IssueishDetailView { + // Header + &-header { + display: block; padding: @component-padding; - font-size: @font-size; background-color: @tool-panel-background-color; + } + &-avatar { position: absolute; } + &-avatarImage { + width: 20px; + height: 20px; + } + &-headerRow:nth-child(1) { + padding-left: 25px; // space for avatar + overflow: hidden; + } + &-title { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + &-checkoutButton { + margin: @component-padding 0 0 0; + } - &-tab { - border: none; - } - - &-tablist { - border-bottom: none; - display: inline; - } - - &-header { - margin-bottom: @component-padding*1.5; - padding: 0; - border: none; - border-radius: 0; - } - - &-headerGroup { - margin: 0; - } - - &-avatarImage { - width: 24px; - height: 24px; - border-radius: @component-border-radius; - } - - &-title { - font-size: 1.25em; - font-weight: 500; - line-height: 1.3; - } - - &-reactions { - padding-top: @component-padding; - } - &-buildStatus { - margin: @component-padding 0; + &-tablist { flex-wrap: wrap; } + &-tab { padding: @component-padding/2 @component-padding; } + &-tab-icon { display: none; } - &:empty { - display: none; - } - } - .github-PrTimeline { - margin-top: @component-padding; - } + // [Overview] + &-overview { + font-size: .9em; + & > .github-DotComMarkdownHtml, .timeline-item { - margin: 0; - padding: @component-padding*1.5 0; - } - - .pre-timeline-item-icon { - position: relative; - margin: 0; - &:before { - vertical-align: middle; - } + padding: @component-padding; } - - &-container > .github-DotComMarkdownHtml { - margin: @component-padding 0; - } - } + + // [Build Status] + &-buildStatus { padding: 0; } .github-PrStatuses { + &-header { border-top: none; } &-header, &-list-item { border-radius: 0; - padding-left: 0; - padding-right: 0; border-left: none; border-right: none; } } + + + // [Commits] + .github-PrCommitsView-commitWrapper { + padding: 0; + } + .github-PrCommitView-container { + font-size: .9em; + border-left: none; + border-right: none; + + &:first-child { + border-top: none; + border-radius: 0; + } + &:last-child { + border-bottom: none; + border-radius: 0; + } + } + } diff --git a/styles/pr-commit-view.less b/styles/pr-commit-view.less index ce2c93caef..f39b4b3e33 100644 --- a/styles/pr-commit-view.less +++ b/styles/pr-commit-view.less @@ -39,9 +39,11 @@ &-messageHeadline.is-button { padding: 0; + margin-right: @default-padding/1.5; background-color: transparent; border: none; cursor: pointer; + text-align: left; color: @text-color-highlight; &:hover, @@ -71,7 +73,6 @@ &-moreButton { border: none; - margin-left: @default-padding/1.5; padding: 0em .2em; color: @text-color-subtle; font-style: italic; From 41f4045c32c7aadc8385e9353616b192fa679624 Mon Sep 17 00:00:00 2001 From: simurai Date: Tue, 18 Dec 2018 15:11:39 +0900 Subject: [PATCH 1578/4053] Style Commit Detail in dock --- styles/dock.less | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/styles/dock.less b/styles/dock.less index ad2ab5b7da..016e30f63d 100644 --- a/styles/dock.less +++ b/styles/dock.less @@ -119,3 +119,14 @@ atom-dock.right .github-IssueishDetailView { } } + + +// Commit Detail (in left/right dock) ---------------------------------------------- + +atom-dock.left .github-CommitDetailView, +atom-dock.right .github-CommitDetailView { + &-header { background-color: @tool-panel-background-color; } + &-commit { padding: @component-padding @component-padding 0 @component-padding; } + &-meta { margin-bottom: @component-padding; } + &-moreText { padding: @component-padding 0; } +} From 412cfae3dd823936b5fcbd912011b34d8895e3b2 Mon Sep 17 00:00:00 2001 From: simurai Date: Tue, 18 Dec 2018 19:47:28 +0900 Subject: [PATCH 1579/4053] Fix styling for issues --- lib/views/issue-detail-view.js | 40 ++++++++++++++++---------------- styles/issueish-detail-view.less | 15 +++++++++--- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 36842041fe..285e1e5344 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -71,7 +71,7 @@ export class BareIssueDetailView extends React.Component { renderIssueBody(issue) { return ( - +
    No description provided.'} switchToIssueish={this.props.switchToIssueish} @@ -81,7 +81,7 @@ export class BareIssueDetailView extends React.Component { issue={issue} switchToIssueish={this.props.switchToIssueish} /> - +
    ); } @@ -93,31 +93,30 @@ export class BareIssueDetailView extends React.Component {
    -
    + {this.renderIssueBody(issue)}
    diff --git a/styles/issueish-detail-view.less b/styles/issueish-detail-view.less index 27c61d54b4..89781361f1 100644 --- a/styles/issueish-detail-view.less +++ b/styles/issueish-detail-view.less @@ -119,9 +119,6 @@ } } - - - &-avatarImage { width: 40px; height: 40px; @@ -240,4 +237,16 @@ padding: @component-padding*2; } + + // Issue Body ------------------------ + + &-issueBody { + flex: 1; + overflow: auto; + + & > .github-DotComMarkdownHtml { + padding: @component-padding*2; + } + } + } From 54d31909764e0fdb47fdd600e184fe08a85706a7 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 18 Dec 2018 13:51:30 +0100 Subject: [PATCH 1580/4053] remove unused import --- lib/views/issue-detail-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/issue-detail-view.js b/lib/views/issue-detail-view.js index 285e1e5344..54d07e74cb 100644 --- a/lib/views/issue-detail-view.js +++ b/lib/views/issue-detail-view.js @@ -1,4 +1,4 @@ -import React, {Fragment} from 'react'; +import React from 'react'; import {graphql, createRefetchContainer} from 'react-relay'; import PropTypes from 'prop-types'; import cx from 'classnames'; From 56cf3aeb7432019b94876efa4df87bba7f26b703 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen <6842965+vanessayuenn@users.noreply.github.com> Date: Tue, 18 Dec 2018 14:04:01 +0100 Subject: [PATCH 1581/4053] remove status section --- docs/feature-requests/000-template.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/feature-requests/000-template.md b/docs/feature-requests/000-template.md index a976c58dec..7fe932ef19 100644 --- a/docs/feature-requests/000-template.md +++ b/docs/feature-requests/000-template.md @@ -6,10 +6,6 @@ For community contributors -- Please fill out Part 1 of the following template. # Feature title -## :tipping_hand_woman: Status - -Proposed - ## :memo: Summary One paragraph explanation of the feature. From f422a4b740e723b49d6e9c76d34a9673379cc405 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 18 Dec 2018 10:27:29 -0800 Subject: [PATCH 1582/4053] hide unstage / staging buttons for filemode and symlink changes --- lib/views/file-patch-meta-view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/file-patch-meta-view.js b/lib/views/file-patch-meta-view.js index 10dd2e723a..16b072440a 100644 --- a/lib/views/file-patch-meta-view.js +++ b/lib/views/file-patch-meta-view.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import cx from 'classnames'; import CommitDetailItem from '../items/commit-detail-item'; +import IssueishDetailItem from '../items/issueish-detail-item'; import {ItemTypePropType} from '../prop-types'; export default class FilePatchMetaView extends React.Component { @@ -18,7 +19,7 @@ export default class FilePatchMetaView extends React.Component { }; renderMetaControls() { - if (this.props.itemType === CommitDetailItem) { + if (this.props.itemType === CommitDetailItem || this.props.itemType === IssueishDetailItem) { return null; } return ( From edc4226f07ceeca50355fba483733f0388b70d33 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 18 Dec 2018 10:31:28 -0800 Subject: [PATCH 1583/4053] add tests for FilePatchHeaderView buttons --- test/views/file-patch-header-view.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/views/file-patch-header-view.test.js b/test/views/file-patch-header-view.test.js index cf778723cb..74f5111dd6 100644 --- a/test/views/file-patch-header-view.test.js +++ b/test/views/file-patch-header-view.test.js @@ -6,6 +6,7 @@ import FilePatchHeaderView from '../../lib/views/file-patch-header-view'; import ChangedFileItem from '../../lib/items/changed-file-item'; import CommitPreviewItem from '../../lib/items/commit-preview-item'; import CommitDetailItem from '../../lib/items/commit-detail-item'; +import IssueishDetailItem from '../../lib/items/issueish-detail-item'; describe('FilePatchHeaderView', function() { const relPath = path.join('dir', 'a.txt'); @@ -184,5 +185,11 @@ describe('FilePatchHeaderView', function() { const wrapper = shallow(buildApp({itemType: CommitDetailItem})); assert.isFalse(wrapper.find('.btn-group').exists()); }); + + it('does not render buttons when in an IssueishDetailItem', function() { + const wrapper = shallow(buildApp({itemType: IssueishDetailItem})); + assert.isFalse(wrapper.find('.btn-group').exists()); + }); + }); }); From b7c5c7f41751e449d6d30a40392c5bd92dd865e6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 19 Dec 2018 01:54:17 +0000 Subject: [PATCH 1584/4053] fix(package): update what-the-diff to version 0.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7acc28813..6ab320fd1c 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "tinycolor2": "1.4.1", "tree-kill": "1.2.1", "underscore-plus": "1.6.8", - "what-the-diff": "0.4.0", + "what-the-diff": "0.5.0", "what-the-status": "1.0.3", "yubikiri": "1.0.0" }, From 06ea6653f5535488faca3e177f7f36c82cc02624 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 19 Dec 2018 01:54:23 +0000 Subject: [PATCH 1585/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 865d361557..712f5bcef8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8344,9 +8344,9 @@ } }, "what-the-diff": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/what-the-diff/-/what-the-diff-0.4.0.tgz", - "integrity": "sha1-vIXGP7yWxA1H0p+EMpM0RV18PrY=" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/what-the-diff/-/what-the-diff-0.5.0.tgz", + "integrity": "sha512-+ZX92uzic8Ufbyvl128Rsi8Hf67lYMKA4MJBOUtDnA3PD+rQY0493G25KAKzb9qQ8NN5TcD6VyV/BqBFq1Ktuw==" }, "what-the-status": { "version": "1.0.3", From 66faaf96344cb0b4ed070ff5425005cce1bbf40f Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 19 Dec 2018 17:09:58 +0900 Subject: [PATCH 1586/4053] Update README screenshot With the changes from https://github.com/atom/github/pull/1829 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 975b89fbf8..b146ccf65f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The Atom GitHub package provides Git and GitHub integration for Atom. Check out git-integration -github-integration +github-integration ## Installation From 109820d857114ea065f773aeb0046f82b5ac3b58 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 19 Dec 2018 10:55:40 +0100 Subject: [PATCH 1587/4053] starts with test! --- test/views/file-patch-header-view.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/views/file-patch-header-view.test.js b/test/views/file-patch-header-view.test.js index 74f5111dd6..5cf5564823 100644 --- a/test/views/file-patch-header-view.test.js +++ b/test/views/file-patch-header-view.test.js @@ -60,6 +60,13 @@ describe('FilePatchHeaderView', function() { assert.strictEqual(wrapper.find('.github-FilePatchView-title').text(), `Staged Changes for ${relPath}`); }); }); + + it('renders title for a renamed file as oldPath → newPath', function() { + const oldPath = path.join('dir', 'a.txt'); + const newPath = path.join('dir', 'b.txt'); + const wrapper = shallow(buildApp({relPath: oldPath, newPath})); + assert.strictEqual(wrapper.find('.github-FilePatchView-title').text(), `${oldPath} → ${newPath}`); + }); }); describe('the button group', function() { From 0772a053b53350d178112cfa6c3458c2a6656a51 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 19 Dec 2018 10:55:51 +0100 Subject: [PATCH 1588/4053] handles rename of file --- lib/views/file-patch-header-view.js | 21 ++++++++++++++++----- lib/views/multi-file-patch-view.js | 1 + 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index eba8278094..1425221120 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -13,6 +13,7 @@ import {ItemTypePropType} from '../prop-types'; export default class FilePatchHeaderView extends React.Component { static propTypes = { relPath: PropTypes.string.isRequired, + newPath: PropTypes.string, stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), isPartiallyStaged: PropTypes.bool, hasHunks: PropTypes.bool.isRequired, @@ -51,16 +52,26 @@ export default class FilePatchHeaderView extends React.Component { if (this.props.itemType === ChangedFileItem) { const status = this.props.stagingStatus; return ( - {status[0].toUpperCase()}{status.slice(1)} Changes for {this.renderPath()} + {status[0].toUpperCase()}{status.slice(1)} Changes for {this.renderDisplayPath()} ); } else { - return this.renderPath(); + return this.renderDisplayPath(); } } - renderPath() { - const dirname = path.dirname(this.props.relPath); - const basename = path.basename(this.props.relPath); + renderDisplayPath() { + if (this.props.newPath && this.props.newPath !== this.props.relPath) { + const oldPath = this.renderPath(this.props.relPath); + const newPath = this.renderPath(this.props.newPath); + return {oldPath} {newPath}; + } else { + return this.renderPath(this.props.relPath); + } + } + + renderPath(filePath) { + const dirname = path.dirname(filePath); + const basename = path.basename(filePath); if (dirname === '.') { return {basename}; diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index b8d46976f8..479434e8be 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -316,6 +316,7 @@ export default class MultiFilePatchView extends React.Component { 0} From e558a36e1653e0321662bb9066afc5d780ba4a01 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 19 Dec 2018 16:52:43 +0100 Subject: [PATCH 1589/4053] add test for pruning file path prefix after parsing the diff --- test/containers/pr-changed-files-container.test.js | 10 +++++++++- test/fixtures/diffs/raw-diff.js | 14 +++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/test/containers/pr-changed-files-container.test.js b/test/containers/pr-changed-files-container.test.js index bcbe406bd9..0d63b22587 100644 --- a/test/containers/pr-changed-files-container.test.js +++ b/test/containers/pr-changed-files-container.test.js @@ -2,7 +2,7 @@ import React from 'react'; import {shallow} from 'enzyme'; import {parse as parseDiff} from 'what-the-diff'; -import rawDiff from '../fixtures/diffs/raw-diff'; +import {rawDiff, rawDiffWithPathPrefix} from '../fixtures/diffs/raw-diff'; import {buildMultiFilePatch} from '../../lib/models/patch'; import {getEndpoint} from '../../lib/models/endpoint'; @@ -72,6 +72,14 @@ describe('PullRequestChangedFilesContainer', function() { assert.strictEqual(diffURL, 'https://api.github.com/repos/smashwilson/pushbot/pulls/12'); }); + it('builds multifilepatch without the a/ and b/ prefixes in file paths', function() { + const wrapper = shallow(buildApp()); + const {filePatches} = wrapper.instance().buildPatch(rawDiffWithPathPrefix); + console.log(filePatches) + assert.notMatch(filePatches[0].newFile.path, /^[a|b]\//); + assert.notMatch(filePatches[0].oldFile.path, /^[a|b]\//); + }); + it('passes loaded diff data through to the controller', async function() { const wrapper = shallow(buildApp({ token: '4321', diff --git a/test/fixtures/diffs/raw-diff.js b/test/fixtures/diffs/raw-diff.js index 81f56d896f..0d57194fec 100644 --- a/test/fixtures/diffs/raw-diff.js +++ b/test/fixtures/diffs/raw-diff.js @@ -1,4 +1,5 @@ import dedent from 'dedent-js'; + const rawDiff = dedent` diff --git file.txt file.txt index 83db48f..bf269f4 100644 @@ -10,4 +11,15 @@ const rawDiff = dedent` +new line line3 `; -export default rawDiff; +const rawDiffWithPathPrefix = dedent` + diff --git a/badpath.txt b/badpath.txt + index af607bb..cfac420 100644 + --- a/badpath.txt + +++ b/badpath.txt + @@ -1,2 +1,3 @@ + line0 + -line1 + +line1.5 + +line2 +`; +export {rawDiff, rawDiffWithPathPrefix}; From 98a48ce35326839785caa309fe68718d998a2b9a Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 19 Dec 2018 16:53:44 +0100 Subject: [PATCH 1590/4053] prune prefix --- lib/containers/pr-changed-files-container.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/containers/pr-changed-files-container.js b/lib/containers/pr-changed-files-container.js index ece3bf4a82..beb3c5d38b 100644 --- a/lib/containers/pr-changed-files-container.js +++ b/lib/containers/pr-changed-files-container.js @@ -59,7 +59,16 @@ export default class PullRequestChangedFilesContainer extends React.Component { } buildPatch(rawDiff) { - const diffs = parseDiff(rawDiff); + const diffs = parseDiff(rawDiff).map(diff => { + // diff coming from API will have the defaul git diff prefixes a/ and b/ + // e.g. a/file1.js and b/file2.js + // see https://git-scm.com/docs/git-diff#_generating_patches_with_p + return { + ...diff, + newPath: diff.newPath ? diff.newPath.replace(/^[a|b]\//, '') : diff.newPath, + oldPath: diff.oldPath ? diff.oldPath.replace(/^[a|b]\//, '') : diff.oldPath, + }; + }); return buildMultiFilePatch(diffs); } From 0d1f597f0f00d66bb6b4c1e852b49418ada4e752 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 19 Dec 2018 16:53:53 +0100 Subject: [PATCH 1591/4053] no console log! --- test/containers/pr-changed-files-container.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/containers/pr-changed-files-container.test.js b/test/containers/pr-changed-files-container.test.js index 0d63b22587..af252cb56c 100644 --- a/test/containers/pr-changed-files-container.test.js +++ b/test/containers/pr-changed-files-container.test.js @@ -75,7 +75,6 @@ describe('PullRequestChangedFilesContainer', function() { it('builds multifilepatch without the a/ and b/ prefixes in file paths', function() { const wrapper = shallow(buildApp()); const {filePatches} = wrapper.instance().buildPatch(rawDiffWithPathPrefix); - console.log(filePatches) assert.notMatch(filePatches[0].newFile.path, /^[a|b]\//); assert.notMatch(filePatches[0].oldFile.path, /^[a|b]\//); }); From 267dbbb93acff5726eef3c90edb4a06ec9bbde95 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 19 Dec 2018 10:41:22 -0800 Subject: [PATCH 1592/4053] start on graphQL query for fetching inline comments --- .../issueishDetailContainerQuery.graphql.js | 151 +++++++++++------- lib/containers/issueish-detail-container.js | 8 +- ...eishDetailController_repository.graphql.js | 90 +++++++---- lib/controllers/issueish-detail-controller.js | 6 +- .../prDetailViewRefetchQuery.graphql.js | 123 +++++++++----- .../prDetailView_pullRequest.graphql.js | 27 +++- lib/views/pr-detail-view.js | 13 +- 7 files changed, 286 insertions(+), 132 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 517618ebe4..7b9148daa7 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 1f4afcef5bb1a9ba60f9a80a6c964d13 + * @relayHash b86bf34f5fb29cecb56582b73ffb540b */ /* eslint-disable */ @@ -18,6 +18,8 @@ export type issueishDetailContainerQueryVariables = {| timelineCursor?: ?string, commitCount: number, commitCursor?: ?string, + commentCount: number, + commentCursor?: ?string, |}; export type issueishDetailContainerQueryResponse = {| +repository: ?{| @@ -42,12 +44,12 @@ query issueishDetailContainerQuery( $commitCursor: String ) { repository(owner: $repoOwner, name: $repoName) { - ...issueishDetailController_repository_1mXVvq + ...issueishDetailController_repository_2LgaQ1 id } } -fragment issueishDetailController_repository_1mXVvq on Repository { +fragment issueishDetailController_repository_2LgaQ1 on Repository { ...issueDetailView_repository ...prDetailView_repository name @@ -84,7 +86,7 @@ fragment issueishDetailController_repository_1mXVvq on Repository { sshUrl id } - ...prDetailView_pullRequest_4cAEh0 + ...prDetailView_pullRequest_1Etigl } ... on Node { id @@ -149,13 +151,16 @@ fragment issueDetailView_issue_4cAEh0 on Issue { } } -fragment prDetailView_pullRequest_4cAEh0 on PullRequest { +fragment prDetailView_pullRequest_1Etigl on PullRequest { __typename ... on Node { id } isCrossRepository changedFiles + comments { + totalCount + } ...prCommitsView_pullRequest_38TpXw countedCommits: commits { totalCount @@ -556,6 +561,18 @@ var v0 = [ "name": "commitCursor", "type": "String", "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCursor", + "type": "String", + "defaultValue": null } ], v1 = [ @@ -1043,7 +1060,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_1mXVvq\n id\n }\n}\n\nfragment issueishDetailController_repository_1mXVvq on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_4cAEh0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_4cAEh0 on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_2LgaQ1\n id\n }\n}\n\nfragment issueishDetailController_repository_2LgaQ1 on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_1Etigl\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1065,6 +1082,18 @@ return { "kind": "FragmentSpread", "name": "issueishDetailController_repository", "args": [ + { + "kind": "Variable", + "name": "commentCount", + "variableName": "commentCount", + "type": null + }, + { + "kind": "Variable", + "name": "commentCursor", + "variableName": "commentCursor", + "type": null + }, { "kind": "Variable", "name": "commitCount", @@ -1208,55 +1237,6 @@ return { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - { - "kind": "LinkedField", - "alias": "countedCommits", - "name": "commits", - "storageKey": null, - "args": null, - "concreteType": "PullRequestCommitConnection", - "plural": false, - "selections": v29 - }, - v9, - { - "kind": "ScalarField", - "alias": null, - "name": "headRefName", - "args": null, - "storageKey": null - }, - { - "kind": "LinkedField", - "alias": null, - "name": "headRepository", - "storageKey": null, - "args": null, - "concreteType": "Repository", - "plural": false, - "selections": [ - v3, - v7, - v14, - { - "kind": "ScalarField", - "alias": null, - "name": "sshUrl", - "args": null, - "storageKey": null - }, - v2 - ] - }, - v20, - { - "kind": "ScalarField", - "alias": null, - "name": "changedFiles", - "args": null, - "storageKey": null - }, - v14, { "kind": "LinkedField", "alias": null, @@ -1358,7 +1338,66 @@ return { "key": "prCommitsView_commits", "filters": null }, + v9, + { + "kind": "ScalarField", + "alias": null, + "name": "headRefName", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "headRepository", + "storageKey": null, + "args": null, + "concreteType": "Repository", + "plural": false, + "selections": [ + v3, + v7, + v14, + { + "kind": "ScalarField", + "alias": null, + "name": "sshUrl", + "args": null, + "storageKey": null + }, + v2 + ] + }, + v20, + { + "kind": "ScalarField", + "alias": null, + "name": "changedFiles", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": null, + "args": null, + "concreteType": "IssueCommentConnection", + "plural": false, + "selections": v29 + }, + v14, v10, + { + "kind": "LinkedField", + "alias": "countedCommits", + "name": "commits", + "storageKey": null, + "args": null, + "concreteType": "PullRequestCommitConnection", + "plural": false, + "selections": v29 + }, { "kind": "LinkedField", "alias": "recentCommits", @@ -1672,5 +1711,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'a7a95576735d58263820790226b82e2f'; +(node/*: any*/).hash = 'b749679230ad5e75455f1923ba78b425'; module.exports = node; diff --git a/lib/containers/issueish-detail-container.js b/lib/containers/issueish-detail-container.js index c428b9fe47..2f8a2e40f1 100644 --- a/lib/containers/issueish-detail-container.js +++ b/lib/containers/issueish-detail-container.js @@ -121,7 +121,9 @@ export default class IssueishDetailContainer extends React.Component { $timelineCount: Int! $timelineCursor: String $commitCount: Int! - $commitCursor: String + $commitCursor: String, + $commentCount: Int!, + $commentCursor: String, ) { repository(owner: $repoOwner, name: $repoName) { ...issueishDetailController_repository @arguments( @@ -130,6 +132,8 @@ export default class IssueishDetailContainer extends React.Component { timelineCursor: $timelineCursor, commitCount: $commitCount, commitCursor: $commitCursor, + commentCount: $commentCount, + commentCursor: $commentCursor, ) } } @@ -142,6 +146,8 @@ export default class IssueishDetailContainer extends React.Component { timelineCursor: null, commitCount: 100, commitCursor: null, + commentCount: 100, + commentCursor: null, }; return ( diff --git a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js index e36d6c2e87..5f17d1be10 100644 --- a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js +++ b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js @@ -109,32 +109,30 @@ v5 = { "args": null, "storageKey": null }, -v6 = [ - { - "kind": "Variable", - "name": "commitCount", - "variableName": "commitCount", - "type": null - }, - { - "kind": "Variable", - "name": "commitCursor", - "variableName": "commitCursor", - "type": null - }, - { - "kind": "Variable", - "name": "timelineCount", - "variableName": "timelineCount", - "type": null - }, - { - "kind": "Variable", - "name": "timelineCursor", - "variableName": "timelineCursor", - "type": null - } -]; +v6 = { + "kind": "Variable", + "name": "commitCount", + "variableName": "commitCount", + "type": null +}, +v7 = { + "kind": "Variable", + "name": "commitCursor", + "variableName": "commitCursor", + "type": null +}, +v8 = { + "kind": "Variable", + "name": "timelineCount", + "variableName": "timelineCount", + "type": null +}, +v9 = { + "kind": "Variable", + "name": "timelineCursor", + "variableName": "timelineCursor", + "type": null +}; return { "kind": "Fragment", "name": "issueishDetailController_repository", @@ -170,6 +168,18 @@ return { "name": "commitCursor", "type": "String", "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCursor", + "type": "String", + "defaultValue": null } ], "selections": [ @@ -204,7 +214,12 @@ return { { "kind": "FragmentSpread", "name": "issueDetailView_issue", - "args": v6 + "args": [ + v6, + v7, + v8, + v9 + ] } ] } @@ -263,7 +278,24 @@ return { { "kind": "FragmentSpread", "name": "prDetailView_pullRequest", - "args": v6 + "args": [ + { + "kind": "Variable", + "name": "commentCount", + "variableName": "commentCount", + "type": null + }, + { + "kind": "Variable", + "name": "commentCursor", + "variableName": "commentCursor", + "type": null + }, + v6, + v7, + v8, + v9 + ] } ] } @@ -273,5 +305,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '616ab785cf6824cb91ed3002553abb70'; +(node/*: any*/).hash = '0a288c1ab2398af40de27baf5db2aacb'; module.exports = node; diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index f1991ab5e3..ae8bc47bda 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -287,7 +287,7 @@ export class BareIssueishDetailController extends React.Component { addEvent('open-commit-in-pane', {package: 'github', from: this.constructor.name}); } } - +// todo: we probably don't need to commit count and cursor in the issue fragment export default createFragmentContainer(BareIssueishDetailController, { repository: graphql` fragment issueishDetailController_repository on Repository @@ -297,6 +297,8 @@ export default createFragmentContainer(BareIssueishDetailController, { timelineCursor: {type: "String"}, commitCount: {type: "Int!"}, commitCursor: {type: "String"}, + commentCount: {type: "Int!"}, + commentCursor: {type: "String"}, ) { ...issueDetailView_repository ...prDetailView_repository @@ -336,6 +338,8 @@ export default createFragmentContainer(BareIssueishDetailController, { timelineCursor: $timelineCursor, commitCount: $commitCount, commitCursor: $commitCursor, + commentCount: $commentCount, + commentCursor: $commentCursor, ) } } diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index cd360364a9..eb62f5be7c 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash ca7d9bf45e372c3f19fe741b4132b7bd + * @relayHash 690cc435f3a8f5738ba609aa81c359b4 */ /* eslint-disable */ @@ -18,6 +18,8 @@ export type prDetailViewRefetchQueryVariables = {| timelineCursor?: ?string, commitCount: number, commitCursor?: ?string, + commentCount: number, + commentCursor?: ?string, |}; export type prDetailViewRefetchQueryResponse = {| +repository: ?{| @@ -50,7 +52,7 @@ query prDetailViewRefetchQuery( } pullRequest: node(id: $issueishId) { __typename - ...prDetailView_pullRequest_4cAEh0 + ...prDetailView_pullRequest_1Etigl id } } @@ -65,13 +67,16 @@ fragment prDetailView_repository_3D8CP9 on Repository { } } -fragment prDetailView_pullRequest_4cAEh0 on PullRequest { +fragment prDetailView_pullRequest_1Etigl on PullRequest { __typename ... on Node { id } isCrossRepository changedFiles + comments { + totalCount + } ...prCommitsView_pullRequest_38TpXw countedCommits: commits { totalCount @@ -444,6 +449,18 @@ var v0 = [ "name": "commitCursor", "type": "String", "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCursor", + "type": "String", + "defaultValue": null } ], v1 = [ @@ -531,14 +548,23 @@ v12 = { "args": null, "storageKey": null }, -v13 = { +v13 = [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } +], +v14 = { "kind": "ScalarField", "alias": null, "name": "url", "args": null, "storageKey": null }, -v14 = [ +v15 = [ { "kind": "Variable", "name": "after", @@ -552,7 +578,7 @@ v14 = [ "type": "Int" } ], -v15 = { +v16 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -577,36 +603,27 @@ v15 = { } ] }, -v16 = { +v17 = { "kind": "ScalarField", "alias": null, "name": "cursor", "args": null, "storageKey": null }, -v17 = { +v18 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", "args": null, "storageKey": null }, -v18 = { +v19 = { "kind": "ScalarField", "alias": "sha", "name": "oid", "args": null, "storageKey": null }, -v19 = [ - { - "kind": "ScalarField", - "alias": null, - "name": "totalCount", - "args": null, - "storageKey": null - } -], v20 = { "kind": "ScalarField", "alias": null, @@ -629,7 +646,7 @@ v22 = { "storageKey": null }, v23 = [ - v13 + v14 ], v24 = [ { @@ -648,7 +665,7 @@ v24 = [ v25 = [ v5, v8, - v17, + v18, v6 ], v26 = [ @@ -680,7 +697,7 @@ v28 = { }, v29 = [ v5, - v17, + v18, v8, v6 ], @@ -712,7 +729,7 @@ return { "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_4cAEh0\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_4cAEh0 on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_1Etigl\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -753,6 +770,18 @@ return { "kind": "FragmentSpread", "name": "prDetailView_pullRequest", "args": [ + { + "kind": "Variable", + "name": "commentCount", + "variableName": "commentCount", + "type": null + }, + { + "kind": "Variable", + "name": "commentCursor", + "variableName": "commentCursor", + "type": null + }, { "kind": "Variable", "name": "commitCount", @@ -824,17 +853,27 @@ return { "args": null, "storageKey": null }, - v13, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": null, + "args": null, + "concreteType": "IssueCommentConnection", + "plural": false, + "selections": v13 + }, + v14, { "kind": "LinkedField", "alias": null, "name": "commits", "storageKey": null, - "args": v14, + "args": v15, "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v15, + v16, { "kind": "LinkedField", "alias": null, @@ -844,7 +883,7 @@ return { "concreteType": "PullRequestCommitEdge", "plural": true, "selections": [ - v16, + v17, { "kind": "LinkedField", "alias": null, @@ -873,7 +912,7 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v17, + v18, v7, { "kind": "ScalarField", @@ -905,8 +944,8 @@ return { "args": null, "storageKey": null }, - v18, - v13 + v19, + v14 ] }, v6, @@ -921,7 +960,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v14, + "args": v15, "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -934,7 +973,7 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v19 + "selections": v13 }, { "kind": "LinkedField", @@ -1064,7 +1103,7 @@ return { "selections": [ v5, v8, - v17, + v18, v6, { "kind": "InlineFragment", @@ -1110,7 +1149,7 @@ return { "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v15, + v16, { "kind": "LinkedField", "alias": null, @@ -1120,7 +1159,7 @@ return { "concreteType": "PullRequestTimelineItemEdge", "plural": true, "selections": [ - v16, + v17, { "kind": "LinkedField", "alias": null, @@ -1192,7 +1231,7 @@ return { "selections": [ v11, v21, - v13, + v14, { "kind": "ScalarField", "alias": "prState", @@ -1208,7 +1247,7 @@ return { "selections": [ v11, v21, - v13, + v14, { "kind": "ScalarField", "alias": "issueState", @@ -1357,7 +1396,7 @@ return { }, v22, v28, - v13 + v14 ] }, { @@ -1375,7 +1414,7 @@ return { "selections": [ v7, v31, - v17 + v18 ] }, { @@ -1388,7 +1427,7 @@ return { "plural": false, "selections": [ v7, - v17, + v18, v31 ] }, @@ -1399,7 +1438,7 @@ return { "args": null, "storageKey": null }, - v18, + v19, { "kind": "ScalarField", "alias": null, @@ -1462,7 +1501,7 @@ return { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v19 + "selections": v13 } ] } @@ -1475,5 +1514,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '04dad90234c09010553beb02cf90cbb1'; +(node/*: any*/).hash = 'f928e12221da7ed9e70170512e98cbb2'; module.exports = node; diff --git a/lib/views/__generated__/prDetailView_pullRequest.graphql.js b/lib/views/__generated__/prDetailView_pullRequest.graphql.js index ae06b6754a..b510f50297 100644 --- a/lib/views/__generated__/prDetailView_pullRequest.graphql.js +++ b/lib/views/__generated__/prDetailView_pullRequest.graphql.js @@ -19,6 +19,9 @@ export type prDetailView_pullRequest = {| +id?: string, +isCrossRepository: boolean, +changedFiles: number, + +comments: {| + +totalCount: number + |}, +countedCommits: {| +totalCount: number |}, @@ -96,6 +99,18 @@ return { "name": "commitCursor", "type": "String", "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCursor", + "type": "String", + "defaultValue": null } ], "selections": [ @@ -127,6 +142,16 @@ return { "args": null, "storageKey": null }, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": null, + "args": null, + "concreteType": "IssueCommentConnection", + "plural": false, + "selections": v0 + }, { "kind": "FragmentSpread", "name": "prCommitsView_pullRequest", @@ -288,5 +313,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '2b7cc9778a3440738f809f76fcd3fd25'; +(node/*: any*/).hash = 'a1d134e400b0baa7d6a558165f406bb2'; module.exports = node; diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 2c9b63ce32..22d9f6e8a8 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -373,6 +373,8 @@ export default createRefetchContainer(BarePullRequestDetailView, { timelineCursor: {type: "String"}, commitCount: {type: "Int!"}, commitCursor: {type: "String"}, + commentCount: {type: "Int!"}, + commentCursor: {type: "String"} ) { __typename @@ -383,6 +385,9 @@ export default createRefetchContainer(BarePullRequestDetailView, { ... on PullRequest { isCrossRepository changedFiles + comments { + totalCount + } ...prCommitsView_pullRequest @arguments(commitCount: $commitCount, commitCursor: $commitCursor) countedCommits: commits { totalCount @@ -415,7 +420,9 @@ export default createRefetchContainer(BarePullRequestDetailView, { $timelineCount: Int!, $timelineCursor: String, $commitCount: Int!, - $commitCursor: String + $commitCursor: String, + $commentCount: Int!, + $commentCursor: String ) { repository:node(id: $repoId) { ...prDetailView_repository @arguments( @@ -429,7 +436,9 @@ export default createRefetchContainer(BarePullRequestDetailView, { timelineCount: $timelineCount, timelineCursor: $timelineCursor, commitCount: $commitCount, - commitCursor: $commitCursor + commitCursor: $commitCursor, + commentCount: $commentCount, + commentCursor: $commentCursor ) } } From 2e215699175ed1301ec3fc4fbd2a735571ebe4ca Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 19 Dec 2018 11:51:21 -0800 Subject: [PATCH 1593/4053] add query to fetch reviews in the PullRequestDetailView Co-Authored-By: Vanessa Yuen --- .../issueishDetailContainerQuery.graphql.js | 457 ++++++++++++--- .../prDetailViewRefetchQuery.graphql.js | 541 +++++++++++++----- .../prDetailView_pullRequest.graphql.js | 386 +++++++++++-- lib/views/pr-detail-view.js | 57 ++ 4 files changed, 1163 insertions(+), 278 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 7b9148daa7..235deacd1c 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash b86bf34f5fb29cecb56582b73ffb540b + * @relayHash ac44654a5df27b896a882aa457ebf399 */ /* eslint-disable */ @@ -161,6 +161,82 @@ fragment prDetailView_pullRequest_1Etigl on PullRequest { comments { totalCount } + reviews(first: 100) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + body + commitId: commit { + oid + id + } + state + submittedAt + login: author { + __typename + login + ... on Node { + id + } + } + author { + __typename + avatarUrl + ... on Node { + id + } + } + comments(first: 100) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + pullRequestId: pullRequest { + number + id + } + databaseId + login: author { + __typename + login + ... on Node { + id + } + } + author { + __typename + avatarUrl + ... on Node { + id + } + } + body + path + commitSha: commit { + oid + id + } + diffHunk + position + originalPosition + originalCommitId: originalCommit { + oid + id + } + replyTo { + id + } + createdAt + url + } + } + } + } ...prCommitsView_pullRequest_38TpXw countedCommits: commits { totalCount @@ -725,6 +801,20 @@ v17 = [ } ], v18 = { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null +}, +v19 = { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null +}, +v20 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -733,43 +823,31 @@ v18 = { "concreteType": "PageInfo", "plural": false, "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "endCursor", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "hasNextPage", - "args": null, - "storageKey": null - } + v18, + v19 ] }, -v19 = { +v21 = { "kind": "ScalarField", "alias": null, "name": "cursor", "args": null, "storageKey": null }, -v20 = { +v22 = { "kind": "ScalarField", "alias": null, "name": "isCrossRepository", "args": null, "storageKey": null }, -v21 = [ +v23 = [ v4, v5, v13, v2 ], -v22 = { +v24 = { "kind": "InlineFragment", "type": "CrossReferencedEvent", "selections": [ @@ -780,7 +858,7 @@ v22 = { "args": null, "storageKey": null }, - v20, + v22, { "kind": "LinkedField", "alias": null, @@ -789,7 +867,7 @@ v22 = { "args": null, "concreteType": null, "plural": false, - "selections": v21 + "selections": v23 }, { "kind": "LinkedField", @@ -859,20 +937,20 @@ v22 = { } ] }, -v23 = [ +v25 = [ v4, v13, v5, v2 ], -v24 = { +v26 = { "kind": "ScalarField", "alias": null, "name": "createdAt", "args": null, "storageKey": null }, -v25 = { +v27 = { "kind": "InlineFragment", "type": "IssueComment", "selections": [ @@ -884,14 +962,14 @@ v25 = { "args": null, "concreteType": null, "plural": false, - "selections": v23 + "selections": v25 }, v12, - v24, + v26, v14 ] }, -v26 = { +v28 = { "kind": "LinkedField", "alias": null, "name": "user", @@ -904,14 +982,14 @@ v26 = { v2 ] }, -v27 = { +v29 = { "kind": "ScalarField", "alias": "sha", "name": "oid", "args": null, "storageKey": null }, -v28 = { +v30 = { "kind": "InlineFragment", "type": "Commit", "selections": [ @@ -925,7 +1003,7 @@ v28 = { "plural": false, "selections": [ v3, - v26, + v28, v13 ] }, @@ -940,7 +1018,7 @@ v28 = { "selections": [ v3, v13, - v26 + v28 ] }, { @@ -950,7 +1028,7 @@ v28 = { "args": null, "storageKey": null }, - v27, + v29, { "kind": "ScalarField", "alias": null, @@ -974,7 +1052,7 @@ v28 = { } ] }, -v29 = [ +v31 = [ { "kind": "ScalarField", "alias": null, @@ -983,7 +1061,7 @@ v29 = [ "storageKey": null } ], -v30 = { +v32 = { "kind": "LinkedField", "alias": null, "name": "reactionGroups", @@ -1007,11 +1085,11 @@ v30 = { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v29 + "selections": v31 } ] }, -v31 = [ +v33 = [ { "kind": "Variable", "name": "after", @@ -1025,7 +1103,35 @@ v31 = [ "type": "Int" } ], -v32 = [ +v34 = [ + { + "kind": "Literal", + "name": "first", + "value": 100, + "type": "Int" + } +], +v35 = { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + v19, + v18 + ] +}, +v36 = { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null +}, +v37 = [ { "kind": "ScalarField", "alias": null, @@ -1035,7 +1141,45 @@ v32 = [ }, v2 ], -v33 = { +v38 = { + "kind": "LinkedField", + "alias": "login", + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v6 +}, +v39 = { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v4, + v13, + v2 + ] +}, +v40 = { + "kind": "ScalarField", + "alias": null, + "name": "path", + "args": null, + "storageKey": null +}, +v41 = { + "kind": "ScalarField", + "alias": null, + "name": "position", + "args": null, + "storageKey": null +}, +v42 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -1043,9 +1187,9 @@ v33 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v32 + "selections": v37 }, -v34 = { +v43 = { "kind": "LinkedField", "alias": null, "name": "actor", @@ -1053,14 +1197,14 @@ v34 = { "args": null, "concreteType": null, "plural": false, - "selections": v23 + "selections": v25 }; return { "kind": "Request", "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_2LgaQ1\n id\n }\n}\n\nfragment issueishDetailController_repository_2LgaQ1 on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_1Etigl\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_2LgaQ1\n id\n }\n}\n\nfragment issueishDetailController_repository_2LgaQ1 on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_1Etigl\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n body\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1177,7 +1321,7 @@ return { "concreteType": "IssueTimelineConnection", "plural": false, "selections": [ - v18, + v20, { "kind": "LinkedField", "alias": null, @@ -1187,7 +1331,7 @@ return { "concreteType": "IssueTimelineItemEdge", "plural": true, "selections": [ - v19, + v21, { "kind": "LinkedField", "alias": null, @@ -1199,9 +1343,9 @@ return { "selections": [ v4, v2, - v22, - v25, - v28 + v24, + v27, + v30 ] } ] @@ -1217,7 +1361,7 @@ return { "key": "IssueTimelineController_timeline", "filters": null }, - v30 + v32 ] } ] @@ -1242,11 +1386,11 @@ return { "alias": null, "name": "commits", "storageKey": null, - "args": v31, + "args": v33, "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v18, + v20, { "kind": "LinkedField", "alias": null, @@ -1256,7 +1400,7 @@ return { "concreteType": "PullRequestCommitEdge", "plural": true, "selections": [ - v19, + v21, { "kind": "LinkedField", "alias": null, @@ -1317,7 +1461,7 @@ return { "args": null, "storageKey": null }, - v27, + v29, v14 ] }, @@ -1333,7 +1477,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v31, + "args": v33, "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -1368,7 +1512,7 @@ return { v2 ] }, - v20, + v22, { "kind": "ScalarField", "alias": null, @@ -1384,7 +1528,149 @@ return { "args": null, "concreteType": "IssueCommentConnection", "plural": false, - "selections": v29 + "selections": v31 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "reviews", + "storageKey": "reviews(first:100)", + "args": v34, + "concreteType": "PullRequestReviewConnection", + "plural": false, + "selections": [ + v35, + { + "kind": "LinkedField", + "alias": null, + "name": "nodes", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReview", + "plural": true, + "selections": [ + v2, + v36, + { + "kind": "LinkedField", + "alias": "commitId", + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v37 + }, + v11, + { + "kind": "ScalarField", + "alias": null, + "name": "submittedAt", + "args": null, + "storageKey": null + }, + v38, + v39, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": "comments(first:100)", + "args": v34, + "concreteType": "PullRequestReviewCommentConnection", + "plural": false, + "selections": [ + v35, + { + "kind": "LinkedField", + "alias": null, + "name": "nodes", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": true, + "selections": [ + { + "kind": "LinkedField", + "alias": "commitSha", + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v37 + }, + v2, + { + "kind": "ScalarField", + "alias": null, + "name": "databaseId", + "args": null, + "storageKey": null + }, + v38, + v39, + v36, + v40, + { + "kind": "LinkedField", + "alias": "pullRequestId", + "name": "pullRequest", + "storageKey": null, + "args": null, + "concreteType": "PullRequest", + "plural": false, + "selections": [ + v10, + v2 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "diffHunk", + "args": null, + "storageKey": null + }, + v41, + { + "kind": "ScalarField", + "alias": null, + "name": "originalPosition", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "originalCommitId", + "name": "originalCommit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v37 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v2 + ] + }, + v26, + v14 + ] + } + ] + } + ] + } + ] }, v14, v10, @@ -1396,7 +1682,7 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v29 + "selections": v31 }, { "kind": "LinkedField", @@ -1540,7 +1826,7 @@ return { "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v18, + v20, { "kind": "LinkedField", "alias": null, @@ -1550,7 +1836,7 @@ return { "concreteType": "PullRequestTimelineItemEdge", "plural": true, "selections": [ - v19, + v21, { "kind": "LinkedField", "alias": null, @@ -1562,25 +1848,18 @@ return { "selections": [ v4, v2, - v22, + v24, { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v33, + v42, { "kind": "LinkedField", "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 100, - "type": "Int" - } - ], + "args": v34, "concreteType": "CommitCommentConnection", "plural": false, "selections": [ @@ -1611,25 +1890,13 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v21 + "selections": v23 }, - v33, + v42, v12, - v24, - { - "kind": "ScalarField", - "alias": null, - "name": "path", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "position", - "args": null, - "storageKey": null - } + v26, + v40, + v41 ] } ] @@ -1642,7 +1909,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v34, + v43, { "kind": "LinkedField", "alias": null, @@ -1651,7 +1918,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v32 + "selections": v37 }, { "kind": "LinkedField", @@ -1661,17 +1928,17 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v32 + "selections": v37 }, - v24 + v26 ] }, { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v34, - v33, + v43, + v42, { "kind": "ScalarField", "alias": null, @@ -1679,11 +1946,11 @@ return { "args": null, "storageKey": null }, - v24 + v26 ] }, - v25, - v28 + v27, + v30 ] } ] @@ -1699,7 +1966,7 @@ return { "key": "prTimelineContainer_timeline", "filters": null }, - v30 + v32 ] } ] diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index eb62f5be7c..ac451dad1e 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 690cc435f3a8f5738ba609aa81c359b4 + * @relayHash 3a8852bc38dab1b82916974271e7eec4 */ /* eslint-disable */ @@ -77,6 +77,82 @@ fragment prDetailView_pullRequest_1Etigl on PullRequest { comments { totalCount } + reviews(first: 100) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + body + commitId: commit { + oid + id + } + state + submittedAt + login: author { + __typename + login + ... on Node { + id + } + } + author { + __typename + avatarUrl + ... on Node { + id + } + } + comments(first: 100) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + pullRequestId: pullRequest { + number + id + } + databaseId + login: author { + __typename + login + ... on Node { + id + } + } + author { + __typename + avatarUrl + ... on Node { + id + } + } + body + path + commitSha: commit { + oid + id + } + diffHunk + position + originalPosition + originalCommitId: originalCommit { + oid + id + } + replyTo { + id + } + createdAt + url + } + } + } + } ...prCommitsView_pullRequest_38TpXw countedCommits: commits { totalCount @@ -537,7 +613,7 @@ v10 = { v11 = { "kind": "ScalarField", "alias": null, - "name": "number", + "name": "state", "args": null, "storageKey": null }, @@ -557,14 +633,125 @@ v13 = [ "storageKey": null } ], -v14 = { +v14 = [ + { + "kind": "Literal", + "name": "first", + "value": 100, + "type": "Int" + } +], +v15 = { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null +}, +v16 = { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null +}, +v17 = { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + v15, + v16 + ] +}, +v18 = { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null +}, +v19 = [ + { + "kind": "ScalarField", + "alias": null, + "name": "oid", + "args": null, + "storageKey": null + }, + v6 +], +v20 = { + "kind": "LinkedField", + "alias": "login", + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v9 +}, +v21 = { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null +}, +v22 = { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v21, + v6 + ] +}, +v23 = { + "kind": "ScalarField", + "alias": null, + "name": "path", + "args": null, + "storageKey": null +}, +v24 = { + "kind": "ScalarField", + "alias": null, + "name": "number", + "args": null, + "storageKey": null +}, +v25 = { + "kind": "ScalarField", + "alias": null, + "name": "position", + "args": null, + "storageKey": null +}, +v26 = { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null +}, +v27 = { "kind": "ScalarField", "alias": null, "name": "url", "args": null, "storageKey": null }, -v15 = [ +v28 = [ { "kind": "Variable", "name": "after", @@ -578,7 +765,7 @@ v15 = [ "type": "Int" } ], -v16 = { +v29 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -587,68 +774,42 @@ v16 = { "concreteType": "PageInfo", "plural": false, "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "endCursor", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "hasNextPage", - "args": null, - "storageKey": null - } + v16, + v15 ] }, -v17 = { +v30 = { "kind": "ScalarField", "alias": null, "name": "cursor", "args": null, "storageKey": null }, -v18 = { - "kind": "ScalarField", - "alias": null, - "name": "avatarUrl", - "args": null, - "storageKey": null -}, -v19 = { +v31 = { "kind": "ScalarField", "alias": "sha", "name": "oid", "args": null, "storageKey": null }, -v20 = { - "kind": "ScalarField", - "alias": null, - "name": "state", - "args": null, - "storageKey": null -}, -v21 = { +v32 = { "kind": "ScalarField", "alias": null, "name": "title", "args": null, "storageKey": null }, -v22 = { +v33 = { "kind": "ScalarField", "alias": null, "name": "bodyHTML", "args": null, "storageKey": null }, -v23 = [ - v14 +v34 = [ + v27 ], -v24 = [ +v35 = [ { "kind": "Variable", "name": "after", @@ -662,23 +823,13 @@ v24 = [ "type": "Int" } ], -v25 = [ +v36 = [ v5, v8, - v18, + v21, v6 ], -v26 = [ - { - "kind": "ScalarField", - "alias": null, - "name": "oid", - "args": null, - "storageKey": null - }, - v6 -], -v27 = { +v37 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -686,22 +837,15 @@ v27 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v26 + "selections": v19 }, -v28 = { - "kind": "ScalarField", - "alias": null, - "name": "createdAt", - "args": null, - "storageKey": null -}, -v29 = [ +v38 = [ v5, - v18, + v21, v8, v6 ], -v30 = { +v39 = { "kind": "LinkedField", "alias": null, "name": "actor", @@ -709,9 +853,9 @@ v30 = { "args": null, "concreteType": null, "plural": false, - "selections": v29 + "selections": v38 }, -v31 = { +v40 = { "kind": "LinkedField", "alias": null, "name": "user", @@ -729,7 +873,7 @@ return { "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_1Etigl\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_1Etigl\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n body\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -863,17 +1007,159 @@ return { "plural": false, "selections": v13 }, - v14, + { + "kind": "LinkedField", + "alias": null, + "name": "reviews", + "storageKey": "reviews(first:100)", + "args": v14, + "concreteType": "PullRequestReviewConnection", + "plural": false, + "selections": [ + v17, + { + "kind": "LinkedField", + "alias": null, + "name": "nodes", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReview", + "plural": true, + "selections": [ + v6, + v18, + { + "kind": "LinkedField", + "alias": "commitId", + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v19 + }, + v11, + { + "kind": "ScalarField", + "alias": null, + "name": "submittedAt", + "args": null, + "storageKey": null + }, + v20, + v22, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": "comments(first:100)", + "args": v14, + "concreteType": "PullRequestReviewCommentConnection", + "plural": false, + "selections": [ + v17, + { + "kind": "LinkedField", + "alias": null, + "name": "nodes", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": true, + "selections": [ + { + "kind": "LinkedField", + "alias": "commitSha", + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v19 + }, + v6, + { + "kind": "ScalarField", + "alias": null, + "name": "databaseId", + "args": null, + "storageKey": null + }, + v20, + v22, + v18, + v23, + { + "kind": "LinkedField", + "alias": "pullRequestId", + "name": "pullRequest", + "storageKey": null, + "args": null, + "concreteType": "PullRequest", + "plural": false, + "selections": [ + v24, + v6 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "diffHunk", + "args": null, + "storageKey": null + }, + v25, + { + "kind": "ScalarField", + "alias": null, + "name": "originalPosition", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "originalCommitId", + "name": "originalCommit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v19 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v6 + ] + }, + v26, + v27 + ] + } + ] + } + ] + } + ] + }, + v27, { "kind": "LinkedField", "alias": null, "name": "commits", "storageKey": null, - "args": v15, + "args": v28, "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v16, + v29, { "kind": "LinkedField", "alias": null, @@ -883,7 +1169,7 @@ return { "concreteType": "PullRequestCommitEdge", "plural": true, "selections": [ - v17, + v30, { "kind": "LinkedField", "alias": null, @@ -912,7 +1198,7 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v18, + v21, v7, { "kind": "ScalarField", @@ -944,8 +1230,8 @@ return { "args": null, "storageKey": null }, - v19, - v14 + v31, + v27 ] }, v6, @@ -960,7 +1246,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v15, + "args": v28, "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -1027,7 +1313,7 @@ return { "concreteType": "Status", "plural": false, "selections": [ - v20, + v11, { "kind": "LinkedField", "alias": null, @@ -1038,7 +1324,7 @@ return { "plural": true, "selections": [ v6, - v20, + v11, { "kind": "ScalarField", "alias": null, @@ -1075,9 +1361,9 @@ return { } ] }, - v20, - v21, - v22, + v24, + v32, + v33, { "kind": "ScalarField", "alias": null, @@ -1103,17 +1389,17 @@ return { "selections": [ v5, v8, - v18, + v21, v6, { "kind": "InlineFragment", "type": "Bot", - "selections": v23 + "selections": v34 }, { "kind": "InlineFragment", "type": "User", - "selections": v23 + "selections": v34 } ] }, @@ -1145,11 +1431,11 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v24, + "args": v35, "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v16, + v29, { "kind": "LinkedField", "alias": null, @@ -1159,7 +1445,7 @@ return { "concreteType": "PullRequestTimelineItemEdge", "plural": true, "selections": [ - v17, + v30, { "kind": "LinkedField", "alias": null, @@ -1191,7 +1477,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v25 + "selections": v36 }, { "kind": "LinkedField", @@ -1229,9 +1515,9 @@ return { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v11, - v21, - v14, + v24, + v32, + v27, { "kind": "ScalarField", "alias": "prState", @@ -1245,9 +1531,9 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v11, - v21, - v14, + v24, + v32, + v27, { "kind": "ScalarField", "alias": "issueState", @@ -1265,20 +1551,13 @@ return { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v27, + v37, { "kind": "LinkedField", "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 100, - "type": "Int" - } - ], + "args": v14, "concreteType": "CommitCommentConnection", "plural": false, "selections": [ @@ -1309,25 +1588,13 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v25 + "selections": v36 }, - v27, - v22, - v28, - { - "kind": "ScalarField", - "alias": null, - "name": "path", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "position", - "args": null, - "storageKey": null - } + v37, + v33, + v26, + v23, + v25 ] } ] @@ -1340,7 +1607,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v30, + v39, { "kind": "LinkedField", "alias": null, @@ -1349,7 +1616,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v26 + "selections": v19 }, { "kind": "LinkedField", @@ -1359,17 +1626,17 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v26 + "selections": v19 }, - v28 + v26 ] }, { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v30, - v27, + v39, + v37, { "kind": "ScalarField", "alias": null, @@ -1377,7 +1644,7 @@ return { "args": null, "storageKey": null }, - v28 + v26 ] }, { @@ -1392,11 +1659,11 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v29 + "selections": v38 }, - v22, - v28, - v14 + v33, + v26, + v27 ] }, { @@ -1413,8 +1680,8 @@ return { "plural": false, "selections": [ v7, - v31, - v18 + v40, + v21 ] }, { @@ -1427,8 +1694,8 @@ return { "plural": false, "selections": [ v7, - v18, - v31 + v21, + v40 ] }, { @@ -1438,7 +1705,7 @@ return { "args": null, "storageKey": null }, - v19, + v31, { "kind": "ScalarField", "alias": null, @@ -1472,7 +1739,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v24, + "args": v35, "handle": "connection", "key": "prTimelineContainer_timeline", "filters": null diff --git a/lib/views/__generated__/prDetailView_pullRequest.graphql.js b/lib/views/__generated__/prDetailView_pullRequest.graphql.js index b510f50297..28adeb4059 100644 --- a/lib/views/__generated__/prDetailView_pullRequest.graphql.js +++ b/lib/views/__generated__/prDetailView_pullRequest.graphql.js @@ -11,6 +11,7 @@ import type { ConcreteFragment } from 'relay-runtime'; type prCommitsView_pullRequest$ref = any; type prStatusesView_pullRequest$ref = any; type prTimelineController_pullRequest$ref = any; +export type PullRequestReviewState = "APPROVED" | "CHANGES_REQUESTED" | "COMMENTED" | "DISMISSED" | "PENDING" | "%future added value"; export type PullRequestState = "CLOSED" | "MERGED" | "OPEN" | "%future added value"; export type ReactionContent = "CONFUSED" | "HEART" | "HOORAY" | "LAUGH" | "THUMBS_DOWN" | "THUMBS_UP" | "%future added value"; import type { FragmentReference } from "relay-runtime"; @@ -22,6 +23,62 @@ export type prDetailView_pullRequest = {| +comments: {| +totalCount: number |}, + +reviews: ?{| + +pageInfo: {| + +hasNextPage: boolean, + +endCursor: ?string, + |}, + +nodes: ?$ReadOnlyArray, + |}, + |}>, + |}, +countedCommits: {| +totalCount: number |}, @@ -51,7 +108,14 @@ export type prDetailView_pullRequest = {| const node/*: ConcreteFragment*/ = (function(){ -var v0 = [ +var v0 = { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null +}, +v1 = [ { "kind": "ScalarField", "alias": null, @@ -60,15 +124,116 @@ var v0 = [ "storageKey": null } ], -v1 = { +v2 = [ + { + "kind": "Literal", + "name": "first", + "value": 100, + "type": "Int" + } +], +v3 = { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null + } + ] +}, +v4 = { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null +}, +v5 = { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null +}, +v6 = [ + { + "kind": "ScalarField", + "alias": null, + "name": "oid", + "args": null, + "storageKey": null + } +], +v7 = { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null +}, +v8 = { + "kind": "LinkedField", + "alias": "login", + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v7 + ] +}, +v9 = { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null +}, +v10 = { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v9 + ] +}, +v11 = { + "kind": "ScalarField", + "alias": null, + "name": "number", + "args": null, + "storageKey": null +}, +v12 = { "kind": "ScalarField", "alias": null, "name": "url", "args": null, "storageKey": null }, -v2 = [ - v1 +v13 = [ + v12 ]; return { "kind": "Fragment", @@ -114,13 +279,7 @@ return { } ], "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "number", - "args": null, - "storageKey": null - }, + v0, { "kind": "ScalarField", "alias": null, @@ -150,7 +309,166 @@ return { "args": null, "concreteType": "IssueCommentConnection", "plural": false, - "selections": v0 + "selections": v1 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "reviews", + "storageKey": "reviews(first:100)", + "args": v2, + "concreteType": "PullRequestReviewConnection", + "plural": false, + "selections": [ + v3, + { + "kind": "LinkedField", + "alias": null, + "name": "nodes", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReview", + "plural": true, + "selections": [ + v4, + v5, + { + "kind": "LinkedField", + "alias": "commitId", + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v6 + }, + v0, + { + "kind": "ScalarField", + "alias": null, + "name": "submittedAt", + "args": null, + "storageKey": null + }, + v8, + v10, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": "comments(first:100)", + "args": v2, + "concreteType": "PullRequestReviewCommentConnection", + "plural": false, + "selections": [ + v3, + { + "kind": "LinkedField", + "alias": null, + "name": "nodes", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": true, + "selections": [ + { + "kind": "LinkedField", + "alias": "commitSha", + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v6 + }, + v4, + { + "kind": "ScalarField", + "alias": null, + "name": "databaseId", + "args": null, + "storageKey": null + }, + v8, + v10, + v5, + { + "kind": "ScalarField", + "alias": null, + "name": "path", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "pullRequestId", + "name": "pullRequest", + "storageKey": null, + "args": null, + "concreteType": "PullRequest", + "plural": false, + "selections": [ + v11 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "diffHunk", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "position", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "originalPosition", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "originalCommitId", + "name": "originalCommit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v6 + }, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v4 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null + }, + v12 + ] + } + ] + } + ] + } + ] }, { "kind": "FragmentSpread", @@ -178,27 +496,15 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v0 + "selections": v1 }, { "kind": "FragmentSpread", "name": "prStatusesView_pullRequest", "args": null }, - { - "kind": "ScalarField", - "alias": null, - "name": "state", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "id", - "args": null, - "storageKey": null - }, + v4, + v11, { "kind": "ScalarField", "alias": null, @@ -236,29 +542,17 @@ return { "concreteType": null, "plural": false, "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "login", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "avatarUrl", - "args": null, - "storageKey": null - }, + v7, + v9, { "kind": "InlineFragment", "type": "Bot", - "selections": v2 + "selections": v13 }, { "kind": "InlineFragment", "type": "User", - "selections": v2 + "selections": v13 } ] }, @@ -280,7 +574,7 @@ return { } ] }, - v1, + v12, { "kind": "LinkedField", "alias": null, @@ -305,7 +599,7 @@ return { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v0 + "selections": v1 } ] } @@ -313,5 +607,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'a1d134e400b0baa7d6a558165f406bb2'; +(node/*: any*/).hash = '6b79c21bb0d8e16f2d4c094d0fd055c8'; module.exports = node; diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 22d9f6e8a8..63762085c3 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -218,6 +218,7 @@ export class BarePullRequestDetailView extends React.Component { render() { const repo = this.props.repository; const pullRequest = this.props.pullRequest; + console.log(pullRequest); return (
    @@ -388,6 +389,62 @@ export default createRefetchContainer(BarePullRequestDetailView, { comments { totalCount } + reviews(first: 100) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + body + commitId: commit { + oid + } + state + submittedAt + login: author { + login + } + author { + avatarUrl + } + comments(first: 100) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + pullRequestId: pullRequest { + number + } + databaseId + login: author { + login + } + author { + avatarUrl + } + body + path + commitSha: commit { + oid + } + diffHunk + position + originalPosition + originalCommitId: originalCommit { + oid + } + replyTo { + id + } + createdAt + url + } + } + } + } ...prCommitsView_pullRequest @arguments(commitCount: $commitCount, commitCursor: $commitCursor) countedCommits: commits { totalCount From a5ea73a6513524c8fa9d659f992259479ec635c3 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 19 Dec 2018 12:51:01 -0800 Subject: [PATCH 1594/4053] render some comments, woo Co-Authored-By: Vanessa Yuen --- lib/views/multi-file-patch-view.js | 94 ++++++++++++++++++------------ lib/views/pr-detail-view.js | 2 +- 2 files changed, 57 insertions(+), 39 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index b8d46976f8..96e57447e4 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -179,12 +179,28 @@ export default class MultiFilePatchView extends React.Component { {this.renderCommands()}
    + {this.renderComments()} {this.props.multiFilePatch.anyPresent() ? this.renderNonEmptyPatch() : this.renderEmptyPatch()}
    ); } + renderComments() { + if (this.props.reviews) { + return this.props.reviews.nodes.map(review => { + return review.comments.nodes.map(comment => { + return ( +
    + + {comment.body} +
    + ); + }); + }); + } + } + renderCommands() { if (this.props.itemType === CommitDetailItem || this.props.itemType === IssueishDetailItem) { return ( @@ -235,49 +251,50 @@ export default class MultiFilePatchView extends React.Component { renderNonEmptyPatch() { return ( - - - - - {this.props.config.get('github.showDiffIconGutter') && ( + + + + + {this.props.config.get('github.showDiffIconGutter') && ( + )} {this.props.multiFilePatch.getFilePatches().map(this.renderFilePatchDecorations)} @@ -305,6 +322,7 @@ export default class MultiFilePatchView extends React.Component { )} + ); } diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 63762085c3..80d3979fc1 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -209,6 +209,7 @@ export class BarePullRequestDetailView extends React.Component { destroy={this.props.destroy} shouldRefetch={this.state.refreshing} + reviews={this.props.pullRequest.reviews} /> @@ -218,7 +219,6 @@ export class BarePullRequestDetailView extends React.Component { render() { const repo = this.props.repository; const pullRequest = this.props.pullRequest; - console.log(pullRequest); return (
    From 346b2dce071f4a4d4a4f5f100806a022cfee69de Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 19 Dec 2018 12:55:13 -0800 Subject: [PATCH 1595/4053] maybe my avatar does not need to be the giantest. Co-Authored-By: Vanessa Yuen --- lib/views/multi-file-patch-view.js | 2 +- styles/file-patch-view.less | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 96e57447e4..6e975eafd5 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -192,7 +192,7 @@ export default class MultiFilePatchView extends React.Component { return review.comments.nodes.map(comment => { return (
    - + {comment.body}
    ); diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 62ba2b660b..208628f173 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -142,6 +142,11 @@ } } + &-commentAuthorAvatar { + height: 16px; + width: 16px; + } + &-metaDetails { padding: @component-padding; } From 8b8f11206aa74b1222c7696c0db877f0a50b97a0 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 19 Dec 2018 13:21:04 -0800 Subject: [PATCH 1596/4053] :fire: unnecessary fragment --- lib/views/multi-file-patch-view.js | 132 ++++++++++++++--------------- 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 6e975eafd5..60225d84a1 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -251,78 +251,76 @@ export default class MultiFilePatchView extends React.Component { renderNonEmptyPatch() { return ( - - - + + + + + {this.props.config.get('github.showDiffIconGutter') && ( - - {this.props.config.get('github.showDiffIconGutter') && ( - - )} - - {this.props.multiFilePatch.getFilePatches().map(this.renderFilePatchDecorations)} - - {this.renderLineDecorations( - Array.from(this.props.selectedRows, row => Range.fromObject([[row, 0], [row, Infinity]])), - 'github-FilePatchView-line--selected', - {gutter: true, icon: true, line: true}, - )} - - {this.renderDecorationsOnLayer( - this.props.multiFilePatch.getAdditionLayer(), - 'github-FilePatchView-line--added', - {icon: true, line: true}, - )} - {this.renderDecorationsOnLayer( - this.props.multiFilePatch.getDeletionLayer(), - 'github-FilePatchView-line--deleted', - {icon: true, line: true}, - )} - {this.renderDecorationsOnLayer( - this.props.multiFilePatch.getNoNewlineLayer(), - 'github-FilePatchView-line--nonewline', - {icon: true, line: true}, - )} - - - + )} + + {this.props.multiFilePatch.getFilePatches().map(this.renderFilePatchDecorations)} + + {this.renderLineDecorations( + Array.from(this.props.selectedRows, row => Range.fromObject([[row, 0], [row, Infinity]])), + 'github-FilePatchView-line--selected', + {gutter: true, icon: true, line: true}, + )} + + {this.renderDecorationsOnLayer( + this.props.multiFilePatch.getAdditionLayer(), + 'github-FilePatchView-line--added', + {icon: true, line: true}, + )} + {this.renderDecorationsOnLayer( + this.props.multiFilePatch.getDeletionLayer(), + 'github-FilePatchView-line--deleted', + {icon: true, line: true}, + )} + {this.renderDecorationsOnLayer( + this.props.multiFilePatch.getNoNewlineLayer(), + 'github-FilePatchView-line--nonewline', + {icon: true, line: true}, + )} + + ); } From 442033dc0875a208516861161c7094ac884a04fe Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 19 Dec 2018 14:10:26 -0800 Subject: [PATCH 1597/4053] fix borked indentation --- lib/views/multi-file-patch-view.js | 54 +++++++++++++++--------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 60225d84a1..a558509609 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -294,33 +294,33 @@ export default class MultiFilePatchView extends React.Component { onMouseDown={this.didMouseDownOnLineNumber} onMouseMove={this.didMouseMoveOnLineNumber} /> - )} - - {this.props.multiFilePatch.getFilePatches().map(this.renderFilePatchDecorations)} - - {this.renderLineDecorations( - Array.from(this.props.selectedRows, row => Range.fromObject([[row, 0], [row, Infinity]])), - 'github-FilePatchView-line--selected', - {gutter: true, icon: true, line: true}, - )} - - {this.renderDecorationsOnLayer( - this.props.multiFilePatch.getAdditionLayer(), - 'github-FilePatchView-line--added', - {icon: true, line: true}, - )} - {this.renderDecorationsOnLayer( - this.props.multiFilePatch.getDeletionLayer(), - 'github-FilePatchView-line--deleted', - {icon: true, line: true}, - )} - {this.renderDecorationsOnLayer( - this.props.multiFilePatch.getNoNewlineLayer(), - 'github-FilePatchView-line--nonewline', - {icon: true, line: true}, - )} - - + )} + + {this.props.multiFilePatch.getFilePatches().map(this.renderFilePatchDecorations)} + + {this.renderLineDecorations( + Array.from(this.props.selectedRows, row => Range.fromObject([[row, 0], [row, Infinity]])), + 'github-FilePatchView-line--selected', + {gutter: true, icon: true, line: true}, + )} + + {this.renderDecorationsOnLayer( + this.props.multiFilePatch.getAdditionLayer(), + 'github-FilePatchView-line--added', + {icon: true, line: true}, + )} + {this.renderDecorationsOnLayer( + this.props.multiFilePatch.getDeletionLayer(), + 'github-FilePatchView-line--deleted', + {icon: true, line: true}, + )} + {this.renderDecorationsOnLayer( + this.props.multiFilePatch.getNoNewlineLayer(), + 'github-FilePatchView-line--nonewline', + {icon: true, line: true}, + )} + + ); } From 1b1a7fc1466f89330aa4e4c108b5fa0d58fceac4 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 20 Dec 2018 18:37:05 +0100 Subject: [PATCH 1598/4053] get the comments to render as a decoration block --- lib/views/multi-file-patch-view.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index a558509609..74d02795de 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -179,7 +179,6 @@ export default class MultiFilePatchView extends React.Component { {this.renderCommands()}
    - {this.renderComments()} {this.props.multiFilePatch.anyPresent() ? this.renderNonEmptyPatch() : this.renderEmptyPatch()}
    @@ -190,12 +189,21 @@ export default class MultiFilePatchView extends React.Component { if (this.props.reviews) { return this.props.reviews.nodes.map(review => { return review.comments.nodes.map(comment => { - return ( -
    - - {comment.body} -
    - ); + if (comment.position !== null) { + console.log(comment); + const range = new Range([comment.position, 0], [comment.position, 0]) + return ( + + +
    + + {comment.body} +
    +
    +
    + ); + } + return null; }); }); } @@ -296,6 +304,7 @@ export default class MultiFilePatchView extends React.Component { /> )} + {this.renderComments()} {this.props.multiFilePatch.getFilePatches().map(this.renderFilePatchDecorations)} {this.renderLineDecorations( From d0adf4392cdc70724e6089a54c083a14c6eb50e4 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 20 Dec 2018 20:18:59 +0100 Subject: [PATCH 1599/4053] make comments show up at the (somewhat) correct location Co-Authored-By: Tilde Ann Thurium --- lib/models/patch/multi-file-patch.js | 4 ++++ lib/views/multi-file-patch-view.js | 11 ++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index c329d66ff4..7336ca1381 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -71,6 +71,10 @@ export default class MultiFilePatch { return this.filePatches; } + getFilePatchByPath(path) { + return this.filePatches.find(filePatch => filePatch.getPath() === path); + } + getPathSet() { return this.getFilePatches().reduce((pathSet, filePatch) => { for (const file of [filePatch.getOldFile(), filePatch.getNewFile()]) { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 985627c04c..ba7fe37b6d 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -1,7 +1,7 @@ import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; -import {Range} from 'atom'; +import {Point, Range} from 'atom'; import {CompositeDisposable} from 'event-kit'; import {autobind} from '../helpers'; @@ -186,12 +186,17 @@ export default class MultiFilePatchView extends React.Component { } renderComments() { + // 1. need to figure out which file patch the comment belongs to + // 2. how many hunks since beginning of file till comment + // 3. filePatch.beginRange + number of hunks + comment.position + if (this.props.reviews) { return this.props.reviews.nodes.map(review => { return review.comments.nodes.map(comment => { if (comment.position !== null) { - console.log(comment); - const range = new Range([comment.position, 0], [comment.position, 0]) + const filePatch = this.props.multiFilePatch.getFilePatchByPath(comment.path); + const commentStartPoint = filePatch.getStartRange().start.translate(new Point(comment.position, 0)); + const range = new Range(commentStartPoint, commentStartPoint); return ( From 5f4f5137bb97e70952b66f0e0742b5a0cea8bf71 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 20 Dec 2018 20:58:40 +0100 Subject: [PATCH 1600/4053] put in comment position also Co-Authored-By: Tilde Ann Thurium --- lib/views/multi-file-patch-view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index ba7fe37b6d..0b7282dda1 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -203,6 +203,7 @@ export default class MultiFilePatchView extends React.Component {
    {comment.body} + {comment.position}
    From b341c5dd9c1cca132a260a28fd5bce55abc541ac Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 20 Dec 2018 17:24:21 -0800 Subject: [PATCH 1601/4053] make `PullRequestCommentsView` into its own component. --- lib/views/multi-file-patch-view.js | 35 +++------------------------ lib/views/pr-comments-view.js | 38 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 32 deletions(-) create mode 100644 lib/views/pr-comments-view.js diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 0b7282dda1..2eaa0b89e8 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -1,7 +1,7 @@ import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; -import {Point, Range} from 'atom'; +import {Range} from 'atom'; import {CompositeDisposable} from 'event-kit'; import {autobind} from '../helpers'; @@ -15,6 +15,7 @@ import Commands, {Command} from '../atom/commands'; import FilePatchHeaderView from './file-patch-header-view'; import FilePatchMetaView from './file-patch-meta-view'; import HunkHeaderView from './hunk-header-view'; +import PullRequestCommentsView from './pr-comments-view'; import RefHolder from '../models/ref-holder'; import ChangedFileItem from '../items/changed-file-item'; import CommitDetailItem from '../items/commit-detail-item'; @@ -185,36 +186,6 @@ export default class MultiFilePatchView extends React.Component { ); } - renderComments() { - // 1. need to figure out which file patch the comment belongs to - // 2. how many hunks since beginning of file till comment - // 3. filePatch.beginRange + number of hunks + comment.position - - if (this.props.reviews) { - return this.props.reviews.nodes.map(review => { - return review.comments.nodes.map(comment => { - if (comment.position !== null) { - const filePatch = this.props.multiFilePatch.getFilePatchByPath(comment.path); - const commentStartPoint = filePatch.getStartRange().start.translate(new Point(comment.position, 0)); - const range = new Range(commentStartPoint, commentStartPoint); - return ( - - -
    - - {comment.body} - {comment.position} -
    -
    -
    - ); - } - return null; - }); - }); - } - } - renderCommands() { if (this.props.itemType === CommitDetailItem || this.props.itemType === IssueishDetailItem) { return ( @@ -310,7 +281,7 @@ export default class MultiFilePatchView extends React.Component { /> )} - {this.renderComments()} + {this.props.multiFilePatch.getFilePatches().map(this.renderFilePatchDecorations)} {this.renderLineDecorations( diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js new file mode 100644 index 0000000000..aab51871f8 --- /dev/null +++ b/lib/views/pr-comments-view.js @@ -0,0 +1,38 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Point, Range} from 'atom'; + +import Marker from '../atom/marker'; +import MarkerLayer from '../atom/marker-layer'; +import Decoration from '../atom/decoration'; + +import Timeago from './timeago'; + +export default class PullRequestCommentsView extends React.Component { + + render() { + + if (this.props.reviews) { + return this.props.reviews.nodes.map(review => { + return review.comments.nodes.map(comment => { + if (comment.position !== null) { + const filePatch = this.props.multiFilePatch.getFilePatchByPath(comment.path); + const commentStartPoint = filePatch.getStartRange().start.translate(new Point(comment.position, 0)); + const range = new Range(commentStartPoint, commentStartPoint); + return ( + + +
    + + {comment.body} +
    +
    +
    + ); + } + return null; + }); + }); + } + } +} From 847829c4f5aacc372c7e479ce7c7de9b30fee9b4 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 20 Dec 2018 17:44:04 -0800 Subject: [PATCH 1602/4053] render comment timestamp --- .../issueishDetailContainerQuery.graphql.js | 147 +++++++-------- .../prDetailViewRefetchQuery.graphql.js | 169 +++++++++--------- .../prDetailView_pullRequest.graphql.js | 62 ++++--- lib/views/pr-comments-view.js | 8 +- lib/views/pr-detail-view.js | 1 + 5 files changed, 203 insertions(+), 184 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 235deacd1c..1a24ef5d10 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash ac44654a5df27b896a882aa457ebf399 + * @relayHash 46254fc27ddba523ae7fba989c5e320a */ /* eslint-disable */ @@ -211,6 +211,7 @@ fragment prDetailView_pullRequest_1Etigl on PullRequest { author { __typename avatarUrl + login ... on Node { id } @@ -944,32 +945,33 @@ v25 = [ v2 ], v26 = { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v25 +}, +v27 = { "kind": "ScalarField", "alias": null, "name": "createdAt", "args": null, "storageKey": null }, -v27 = { +v28 = { "kind": "InlineFragment", "type": "IssueComment", "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v25 - }, - v12, v26, + v12, + v27, v14 ] }, -v28 = { +v29 = { "kind": "LinkedField", "alias": null, "name": "user", @@ -982,14 +984,14 @@ v28 = { v2 ] }, -v29 = { +v30 = { "kind": "ScalarField", "alias": "sha", "name": "oid", "args": null, "storageKey": null }, -v30 = { +v31 = { "kind": "InlineFragment", "type": "Commit", "selections": [ @@ -1003,7 +1005,7 @@ v30 = { "plural": false, "selections": [ v3, - v28, + v29, v13 ] }, @@ -1018,7 +1020,7 @@ v30 = { "selections": [ v3, v13, - v28 + v29 ] }, { @@ -1028,7 +1030,7 @@ v30 = { "args": null, "storageKey": null }, - v29, + v30, { "kind": "ScalarField", "alias": null, @@ -1052,7 +1054,7 @@ v30 = { } ] }, -v31 = [ +v32 = [ { "kind": "ScalarField", "alias": null, @@ -1061,7 +1063,7 @@ v31 = [ "storageKey": null } ], -v32 = { +v33 = { "kind": "LinkedField", "alias": null, "name": "reactionGroups", @@ -1085,11 +1087,11 @@ v32 = { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v31 + "selections": v32 } ] }, -v33 = [ +v34 = [ { "kind": "Variable", "name": "after", @@ -1103,7 +1105,7 @@ v33 = [ "type": "Int" } ], -v34 = [ +v35 = [ { "kind": "Literal", "name": "first", @@ -1111,7 +1113,7 @@ v34 = [ "type": "Int" } ], -v35 = { +v36 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -1124,14 +1126,14 @@ v35 = { v18 ] }, -v36 = { +v37 = { "kind": "ScalarField", "alias": null, "name": "body", "args": null, "storageKey": null }, -v37 = [ +v38 = [ { "kind": "ScalarField", "alias": null, @@ -1141,7 +1143,7 @@ v37 = [ }, v2 ], -v38 = { +v39 = { "kind": "LinkedField", "alias": "login", "name": "author", @@ -1151,20 +1153,6 @@ v38 = { "plural": false, "selections": v6 }, -v39 = { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v4, - v13, - v2 - ] -}, v40 = { "kind": "ScalarField", "alias": null, @@ -1187,7 +1175,7 @@ v42 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": v38 }, v43 = { "kind": "LinkedField", @@ -1204,7 +1192,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_2LgaQ1\n id\n }\n}\n\nfragment issueishDetailController_repository_2LgaQ1 on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_1Etigl\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n body\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_2LgaQ1\n id\n }\n}\n\nfragment issueishDetailController_repository_2LgaQ1 on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_1Etigl\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1344,8 +1332,8 @@ return { v4, v2, v24, - v27, - v30 + v28, + v31 ] } ] @@ -1361,7 +1349,7 @@ return { "key": "IssueTimelineController_timeline", "filters": null }, - v32 + v33 ] } ] @@ -1386,7 +1374,7 @@ return { "alias": null, "name": "commits", "storageKey": null, - "args": v33, + "args": v34, "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ @@ -1461,7 +1449,7 @@ return { "args": null, "storageKey": null }, - v29, + v30, v14 ] }, @@ -1477,7 +1465,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v33, + "args": v34, "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -1528,18 +1516,18 @@ return { "args": null, "concreteType": "IssueCommentConnection", "plural": false, - "selections": v31 + "selections": v32 }, { "kind": "LinkedField", "alias": null, "name": "reviews", "storageKey": "reviews(first:100)", - "args": v34, + "args": v35, "concreteType": "PullRequestReviewConnection", "plural": false, "selections": [ - v35, + v36, { "kind": "LinkedField", "alias": null, @@ -1550,7 +1538,7 @@ return { "plural": true, "selections": [ v2, - v36, + v37, { "kind": "LinkedField", "alias": "commitId", @@ -1559,7 +1547,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": v38 }, v11, { @@ -1569,18 +1557,31 @@ return { "args": null, "storageKey": null }, - v38, v39, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v4, + v13, + v2 + ] + }, { "kind": "LinkedField", "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v34, + "args": v35, "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ - v35, + v36, { "kind": "LinkedField", "alias": null, @@ -1598,7 +1599,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": v38 }, v2, { @@ -1608,9 +1609,9 @@ return { "args": null, "storageKey": null }, - v38, v39, - v36, + v26, + v37, v40, { "kind": "LinkedField", @@ -1648,7 +1649,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": v38 }, { "kind": "LinkedField", @@ -1662,7 +1663,7 @@ return { v2 ] }, - v26, + v27, v14 ] } @@ -1682,7 +1683,7 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v31 + "selections": v32 }, { "kind": "LinkedField", @@ -1859,7 +1860,7 @@ return { "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v34, + "args": v35, "concreteType": "CommitCommentConnection", "plural": false, "selections": [ @@ -1894,7 +1895,7 @@ return { }, v42, v12, - v26, + v27, v40, v41 ] @@ -1918,7 +1919,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": v38 }, { "kind": "LinkedField", @@ -1928,9 +1929,9 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": v38 }, - v26 + v27 ] }, { @@ -1946,11 +1947,11 @@ return { "args": null, "storageKey": null }, - v26 + v27 ] }, - v27, - v30 + v28, + v31 ] } ] @@ -1966,7 +1967,7 @@ return { "key": "prTimelineContainer_timeline", "filters": null }, - v32 + v33 ] } ] diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index ac451dad1e..da5b973fc9 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 3a8852bc38dab1b82916974271e7eec4 + * @relayHash 7c4ab71fc777de210cae63ab5e4481cc */ /* eslint-disable */ @@ -127,6 +127,7 @@ fragment prDetailView_pullRequest_1Etigl on PullRequest { author { __typename avatarUrl + login ... on Node { id } @@ -702,7 +703,13 @@ v21 = { "args": null, "storageKey": null }, -v22 = { +v22 = [ + v5, + v21, + v8, + v6 +], +v23 = { "kind": "LinkedField", "alias": null, "name": "author", @@ -710,48 +717,44 @@ v22 = { "args": null, "concreteType": null, "plural": false, - "selections": [ - v5, - v21, - v6 - ] + "selections": v22 }, -v23 = { +v24 = { "kind": "ScalarField", "alias": null, "name": "path", "args": null, "storageKey": null }, -v24 = { +v25 = { "kind": "ScalarField", "alias": null, "name": "number", "args": null, "storageKey": null }, -v25 = { +v26 = { "kind": "ScalarField", "alias": null, "name": "position", "args": null, "storageKey": null }, -v26 = { +v27 = { "kind": "ScalarField", "alias": null, "name": "createdAt", "args": null, "storageKey": null }, -v27 = { +v28 = { "kind": "ScalarField", "alias": null, "name": "url", "args": null, "storageKey": null }, -v28 = [ +v29 = [ { "kind": "Variable", "name": "after", @@ -765,7 +768,7 @@ v28 = [ "type": "Int" } ], -v29 = { +v30 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -778,38 +781,38 @@ v29 = { v15 ] }, -v30 = { +v31 = { "kind": "ScalarField", "alias": null, "name": "cursor", "args": null, "storageKey": null }, -v31 = { +v32 = { "kind": "ScalarField", "alias": "sha", "name": "oid", "args": null, "storageKey": null }, -v32 = { +v33 = { "kind": "ScalarField", "alias": null, "name": "title", "args": null, "storageKey": null }, -v33 = { +v34 = { "kind": "ScalarField", "alias": null, "name": "bodyHTML", "args": null, "storageKey": null }, -v34 = [ - v27 -], v35 = [ + v28 +], +v36 = [ { "kind": "Variable", "name": "after", @@ -823,13 +826,13 @@ v35 = [ "type": "Int" } ], -v36 = [ +v37 = [ v5, v8, v21, v6 ], -v37 = { +v38 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -839,12 +842,6 @@ v37 = { "plural": false, "selections": v19 }, -v38 = [ - v5, - v21, - v8, - v6 -], v39 = { "kind": "LinkedField", "alias": null, @@ -853,7 +850,7 @@ v39 = { "args": null, "concreteType": null, "plural": false, - "selections": v38 + "selections": v22 }, v40 = { "kind": "LinkedField", @@ -873,7 +870,7 @@ return { "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_1Etigl\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n body\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_1Etigl\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1047,7 +1044,20 @@ return { "storageKey": null }, v20, - v22, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v5, + v21, + v6 + ] + }, { "kind": "LinkedField", "alias": null, @@ -1086,9 +1096,9 @@ return { "storageKey": null }, v20, - v22, - v18, v23, + v18, + v24, { "kind": "LinkedField", "alias": "pullRequestId", @@ -1098,7 +1108,7 @@ return { "concreteType": "PullRequest", "plural": false, "selections": [ - v24, + v25, v6 ] }, @@ -1109,7 +1119,7 @@ return { "args": null, "storageKey": null }, - v25, + v26, { "kind": "ScalarField", "alias": null, @@ -1139,8 +1149,8 @@ return { v6 ] }, - v26, - v27 + v27, + v28 ] } ] @@ -1149,17 +1159,17 @@ return { } ] }, - v27, + v28, { "kind": "LinkedField", "alias": null, "name": "commits", "storageKey": null, - "args": v28, + "args": v29, "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v29, + v30, { "kind": "LinkedField", "alias": null, @@ -1169,7 +1179,7 @@ return { "concreteType": "PullRequestCommitEdge", "plural": true, "selections": [ - v30, + v31, { "kind": "LinkedField", "alias": null, @@ -1230,8 +1240,8 @@ return { "args": null, "storageKey": null }, - v31, - v27 + v32, + v28 ] }, v6, @@ -1246,7 +1256,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v28, + "args": v29, "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -1361,9 +1371,9 @@ return { } ] }, - v24, - v32, + v25, v33, + v34, { "kind": "ScalarField", "alias": null, @@ -1394,12 +1404,12 @@ return { { "kind": "InlineFragment", "type": "Bot", - "selections": v34 + "selections": v35 }, { "kind": "InlineFragment", "type": "User", - "selections": v34 + "selections": v35 } ] }, @@ -1431,11 +1441,11 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v35, + "args": v36, "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v29, + v30, { "kind": "LinkedField", "alias": null, @@ -1445,7 +1455,7 @@ return { "concreteType": "PullRequestTimelineItemEdge", "plural": true, "selections": [ - v30, + v31, { "kind": "LinkedField", "alias": null, @@ -1477,7 +1487,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v36 + "selections": v37 }, { "kind": "LinkedField", @@ -1515,9 +1525,9 @@ return { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v24, - v32, - v27, + v25, + v33, + v28, { "kind": "ScalarField", "alias": "prState", @@ -1531,9 +1541,9 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v24, - v32, - v27, + v25, + v33, + v28, { "kind": "ScalarField", "alias": "issueState", @@ -1551,7 +1561,7 @@ return { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v37, + v38, { "kind": "LinkedField", "alias": null, @@ -1588,13 +1598,13 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v36 + "selections": v37 }, - v37, - v33, - v26, - v23, - v25 + v38, + v34, + v27, + v24, + v26 ] } ] @@ -1628,7 +1638,7 @@ return { "plural": false, "selections": v19 }, - v26 + v27 ] }, { @@ -1636,7 +1646,7 @@ return { "type": "MergedEvent", "selections": [ v39, - v37, + v38, { "kind": "ScalarField", "alias": null, @@ -1644,26 +1654,17 @@ return { "args": null, "storageKey": null }, - v26 + v27 ] }, { "kind": "InlineFragment", "type": "IssueComment", "selections": [ - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v38 - }, - v33, - v26, - v27 + v23, + v34, + v27, + v28 ] }, { @@ -1705,7 +1706,7 @@ return { "args": null, "storageKey": null }, - v31, + v32, { "kind": "ScalarField", "alias": null, @@ -1739,7 +1740,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v35, + "args": v36, "handle": "connection", "key": "prTimelineContainer_timeline", "filters": null diff --git a/lib/views/__generated__/prDetailView_pullRequest.graphql.js b/lib/views/__generated__/prDetailView_pullRequest.graphql.js index 28adeb4059..3d3b3a848f 100644 --- a/lib/views/__generated__/prDetailView_pullRequest.graphql.js +++ b/lib/views/__generated__/prDetailView_pullRequest.graphql.js @@ -57,7 +57,8 @@ export type prDetailView_pullRequest = {| +login: string |}, +author: ?{| - +avatarUrl: any + +avatarUrl: any, + +login: string, |}, +body: string, +path: string, @@ -207,33 +208,21 @@ v9 = { "storageKey": null }, v10 = { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v9 - ] -}, -v11 = { "kind": "ScalarField", "alias": null, "name": "number", "args": null, "storageKey": null }, -v12 = { +v11 = { "kind": "ScalarField", "alias": null, "name": "url", "args": null, "storageKey": null }, -v13 = [ - v12 +v12 = [ + v11 ]; return { "kind": "Fragment", @@ -351,7 +340,18 @@ return { "storageKey": null }, v8, - v10, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v9 + ] + }, { "kind": "LinkedField", "alias": null, @@ -390,7 +390,19 @@ return { "storageKey": null }, v8, - v10, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v9, + v7 + ] + }, v5, { "kind": "ScalarField", @@ -408,7 +420,7 @@ return { "concreteType": "PullRequest", "plural": false, "selections": [ - v11 + v10 ] }, { @@ -461,7 +473,7 @@ return { "args": null, "storageKey": null }, - v12 + v11 ] } ] @@ -504,7 +516,7 @@ return { "args": null }, v4, - v11, + v10, { "kind": "ScalarField", "alias": null, @@ -547,12 +559,12 @@ return { { "kind": "InlineFragment", "type": "Bot", - "selections": v13 + "selections": v12 }, { "kind": "InlineFragment", "type": "User", - "selections": v13 + "selections": v12 } ] }, @@ -574,7 +586,7 @@ return { } ] }, - v12, + v11, { "kind": "LinkedField", "alias": null, @@ -607,5 +619,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '6b79c21bb0d8e16f2d4c094d0fd055c8'; +(node/*: any*/).hash = '9feb173b93416270e2ac66924239d1b1'; module.exports = node; diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index aab51871f8..6d9108a544 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -11,7 +11,6 @@ import Timeago from './timeago'; export default class PullRequestCommentsView extends React.Component { render() { - if (this.props.reviews) { return this.props.reviews.nodes.map(review => { return review.comments.nodes.map(comment => { @@ -19,11 +18,16 @@ export default class PullRequestCommentsView extends React.Component { const filePatch = this.props.multiFilePatch.getFilePatchByPath(comment.path); const commentStartPoint = filePatch.getStartRange().start.translate(new Point(comment.position, 0)); const range = new Range(commentStartPoint, commentStartPoint); + const author = comment.author; return (
    diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 80d3979fc1..1314a1029f 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -424,6 +424,7 @@ export default createRefetchContainer(BarePullRequestDetailView, { } author { avatarUrl + login } body path From 3259c9f519c9cd90dbed562a0eae63eb6a980041 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 21 Dec 2018 16:58:04 +0900 Subject: [PATCH 1603/4053] Only use UI font-size for the headers --- styles/file-patch-view.less | 2 +- styles/hunk-header-view.less | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 62ba2b660b..696bd143e8 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -42,6 +42,7 @@ border: 1px solid @base-border-color; border-radius: @component-border-radius; font-family: system-ui; + font-size: @font-size; background-color: @header-bg-color; cursor: default; @@ -248,7 +249,6 @@ // Readonly editor atom-text-editor[readonly] { - font-size: @font-size; // Use font-size form UI themes and not the editor config .cursors { display: none; } diff --git a/styles/hunk-header-view.less b/styles/hunk-header-view.less index 5d4bfa61de..ccdb4396ab 100644 --- a/styles/hunk-header-view.less +++ b/styles/hunk-header-view.less @@ -10,7 +10,7 @@ display: flex; align-items: stretch; - font-size: .9em; + font-size: @font-size; border: 1px solid @base-border-color; border-radius: @component-border-radius; background-color: @hunk-bg-color; From 2cd9b54afb370ae7a01b20337ecf4a0f7e5fa786 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 21 Dec 2018 17:00:40 +0900 Subject: [PATCH 1604/4053] :fire: Remove default font family Will inherit from the editor --- styles/hunk-header-view.less | 2 -- 1 file changed, 2 deletions(-) diff --git a/styles/hunk-header-view.less b/styles/hunk-header-view.less index ccdb4396ab..e31ea9b209 100644 --- a/styles/hunk-header-view.less +++ b/styles/hunk-header-view.less @@ -6,8 +6,6 @@ @hunk-bg-color-active: mix(@syntax-text-color, @syntax-background-color, 2%); .github-HunkHeaderView { - font-family: Menlo, Consolas, 'DejaVu Sans Mono', monospace; - display: flex; align-items: stretch; font-size: @font-size; From 793cc3342f479420e347a97d357bc78d65afc975 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 21 Dec 2018 17:01:13 +0900 Subject: [PATCH 1605/4053] Use font family from UI themes --- styles/file-patch-view.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 696bd143e8..a51fd5d820 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -41,7 +41,7 @@ padding-left: @component-padding; border: 1px solid @base-border-color; border-radius: @component-border-radius; - font-family: system-ui; + font-family: @font-family; font-size: @font-size; background-color: @header-bg-color; cursor: default; From 1a2ac47f0fea6f75abc6fc097694b09623071be7 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 21 Dec 2018 20:31:01 +0900 Subject: [PATCH 1606/4053] Style PrComment --- lib/views/pr-comments-view.js | 20 +++++++++++-------- styles/file-patch-view.less | 5 ----- styles/pr-comment.less | 36 +++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 styles/pr-comment.less diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index 6d9108a544..d5f87c6108 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -21,14 +21,18 @@ export default class PullRequestCommentsView extends React.Component { const author = comment.author; return ( - -
    -
    - - {author ? author.login : 'someone'} commented - {' '} -
    - {comment.body} + +
    +
    + + {author ? author.login : 'someone'} commented{' '} + + + +
    +
    + {comment.body} +
    diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 208628f173..62ba2b660b 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -142,11 +142,6 @@ } } - &-commentAuthorAvatar { - height: 16px; - width: 16px; - } - &-metaDetails { padding: @component-padding; } diff --git a/styles/pr-comment.less b/styles/pr-comment.less new file mode 100644 index 0000000000..7cb0e62654 --- /dev/null +++ b/styles/pr-comment.less @@ -0,0 +1,36 @@ +@import 'variables'; + +@avatar-size: 16px; + +.github-PrComment { + font-family: @font-family; + font-size: @font-size; + + &-wrapper { + max-width: 60em; + padding-right: @component-padding*2; + padding-bottom: @component-padding; + } + + &-header { + padding: @component-padding 0 @component-padding/2 0; + color: @text-color-subtle; + } + + &-avatar { + margin-right: 4px; + height: @avatar-size; + width: @avatar-size; + border-radius: @component-border-radius; + } + + &-timeAgo { + color: inherit; + } + + &-body { + margin-left: @avatar-size + 4px; // avatar + margin + font-size: 1.15em; + } + +} From b7e443d21248b79d97f009f461de2c0e14c8f9f1 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 21 Dec 2018 13:24:51 +0100 Subject: [PATCH 1607/4053] comment as its own class --- lib/views/pr-comments-view.js | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index d5f87c6108..04a99e78b6 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -18,22 +18,10 @@ export default class PullRequestCommentsView extends React.Component { const filePatch = this.props.multiFilePatch.getFilePatchByPath(comment.path); const commentStartPoint = filePatch.getStartRange().start.translate(new Point(comment.position, 0)); const range = new Range(commentStartPoint, commentStartPoint); - const author = comment.author; return ( -
    -
    - - {author ? author.login : 'someone'} commented{' '} - - - -
    -
    - {comment.body} -
    -
    +
    ); @@ -44,3 +32,24 @@ export default class PullRequestCommentsView extends React.Component { } } } + +class PullRequestCommentView extends React.Component { + + render() { + const author = this.props.comment.author; + return ( +
    +
    + {author.login} + {author ? author.login : 'someone'} commented{' '} + + + +
    +
    + {this.props.comment.body} {this.props.comment.position} +
    +
    + ) + } +} From 33f16d2bc2a945562286a238fed15f0d3a8b239c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 21 Dec 2018 08:53:59 -0500 Subject: [PATCH 1608/4053] Return `null` from render() when there are no comments --- lib/views/pr-comments-view.js | 45 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index 04a99e78b6..ba3775fead 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -3,44 +3,43 @@ import PropTypes from 'prop-types'; import {Point, Range} from 'atom'; import Marker from '../atom/marker'; -import MarkerLayer from '../atom/marker-layer'; import Decoration from '../atom/decoration'; import Timeago from './timeago'; export default class PullRequestCommentsView extends React.Component { - render() { - if (this.props.reviews) { - return this.props.reviews.nodes.map(review => { - return review.comments.nodes.map(comment => { - if (comment.position !== null) { - const filePatch = this.props.multiFilePatch.getFilePatchByPath(comment.path); - const commentStartPoint = filePatch.getStartRange().start.translate(new Point(comment.position, 0)); - const range = new Range(commentStartPoint, commentStartPoint); - return ( - - - - - - ); - } - return null; - }); - }); + if (!this.props.reviews) { + return null; } + + return this.props.reviews.nodes.map(review => { + return review.comments.nodes.map(comment => { + if (comment.position !== null) { + const filePatch = this.props.multiFilePatch.getFilePatchByPath(comment.path); + const commentStartPoint = filePatch.getStartRange().start.translate(new Point(comment.position, 0)); + const range = new Range(commentStartPoint, commentStartPoint); + return ( + + + + + + ); + } + return null; + }); + }); } } class PullRequestCommentView extends React.Component { - render() { const author = this.props.comment.author; return ( - ) + ); } } From 4efcb0584b043b307cfe57a3c0d649e2c6e3775c Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 21 Dec 2018 15:26:42 +0100 Subject: [PATCH 1609/4053] get bodyHTML for comment also --- .../issueishDetailContainerQuery.graphql.js | 6 +- .../prDetailViewRefetchQuery.graphql.js | 100 +++++++++--------- .../prDetailView_pullRequest.graphql.js | 37 ++++--- lib/views/pr-detail-view.js | 1 + 4 files changed, 76 insertions(+), 68 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 1a24ef5d10..55b80a18d2 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 46254fc27ddba523ae7fba989c5e320a + * @relayHash 063056b23fdd8f4f06939894757d5382 */ /* eslint-disable */ @@ -217,6 +217,7 @@ fragment prDetailView_pullRequest_1Etigl on PullRequest { } } body + bodyHTML path commitSha: commit { oid @@ -1192,7 +1193,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_2LgaQ1\n id\n }\n}\n\nfragment issueishDetailController_repository_2LgaQ1 on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_1Etigl\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_2LgaQ1\n id\n }\n}\n\nfragment issueishDetailController_repository_2LgaQ1 on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_1Etigl\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1612,6 +1613,7 @@ return { v39, v26, v37, + v12, v40, { "kind": "LinkedField", diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index da5b973fc9..34aff4255d 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 7c4ab71fc777de210cae63ab5e4481cc + * @relayHash 83a0f29490df40e31d9f5aee5cdbae2f */ /* eslint-disable */ @@ -133,6 +133,7 @@ fragment prDetailView_pullRequest_1Etigl on PullRequest { } } body + bodyHTML path commitSha: commit { oid @@ -722,39 +723,46 @@ v23 = { v24 = { "kind": "ScalarField", "alias": null, - "name": "path", + "name": "bodyHTML", "args": null, "storageKey": null }, v25 = { "kind": "ScalarField", "alias": null, - "name": "number", + "name": "path", "args": null, "storageKey": null }, v26 = { "kind": "ScalarField", "alias": null, - "name": "position", + "name": "number", "args": null, "storageKey": null }, v27 = { "kind": "ScalarField", "alias": null, - "name": "createdAt", + "name": "position", "args": null, "storageKey": null }, v28 = { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null +}, +v29 = { "kind": "ScalarField", "alias": null, "name": "url", "args": null, "storageKey": null }, -v29 = [ +v30 = [ { "kind": "Variable", "name": "after", @@ -768,7 +776,7 @@ v29 = [ "type": "Int" } ], -v30 = { +v31 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -781,36 +789,29 @@ v30 = { v15 ] }, -v31 = { +v32 = { "kind": "ScalarField", "alias": null, "name": "cursor", "args": null, "storageKey": null }, -v32 = { +v33 = { "kind": "ScalarField", "alias": "sha", "name": "oid", "args": null, "storageKey": null }, -v33 = { - "kind": "ScalarField", - "alias": null, - "name": "title", - "args": null, - "storageKey": null -}, v34 = { "kind": "ScalarField", "alias": null, - "name": "bodyHTML", + "name": "title", "args": null, "storageKey": null }, v35 = [ - v28 + v29 ], v36 = [ { @@ -870,7 +871,7 @@ return { "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_1Etigl\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_1Etigl\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1099,6 +1100,7 @@ return { v23, v18, v24, + v25, { "kind": "LinkedField", "alias": "pullRequestId", @@ -1108,7 +1110,7 @@ return { "concreteType": "PullRequest", "plural": false, "selections": [ - v25, + v26, v6 ] }, @@ -1119,7 +1121,7 @@ return { "args": null, "storageKey": null }, - v26, + v27, { "kind": "ScalarField", "alias": null, @@ -1149,8 +1151,8 @@ return { v6 ] }, - v27, - v28 + v28, + v29 ] } ] @@ -1159,17 +1161,17 @@ return { } ] }, - v28, + v29, { "kind": "LinkedField", "alias": null, "name": "commits", "storageKey": null, - "args": v29, + "args": v30, "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v30, + v31, { "kind": "LinkedField", "alias": null, @@ -1179,7 +1181,7 @@ return { "concreteType": "PullRequestCommitEdge", "plural": true, "selections": [ - v31, + v32, { "kind": "LinkedField", "alias": null, @@ -1240,8 +1242,8 @@ return { "args": null, "storageKey": null }, - v32, - v28 + v33, + v29 ] }, v6, @@ -1256,7 +1258,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v29, + "args": v30, "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -1371,9 +1373,9 @@ return { } ] }, - v25, - v33, + v26, v34, + v24, { "kind": "ScalarField", "alias": null, @@ -1445,7 +1447,7 @@ return { "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v30, + v31, { "kind": "LinkedField", "alias": null, @@ -1455,7 +1457,7 @@ return { "concreteType": "PullRequestTimelineItemEdge", "plural": true, "selections": [ - v31, + v32, { "kind": "LinkedField", "alias": null, @@ -1525,9 +1527,9 @@ return { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v25, - v33, - v28, + v26, + v34, + v29, { "kind": "ScalarField", "alias": "prState", @@ -1541,9 +1543,9 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v25, - v33, - v28, + v26, + v34, + v29, { "kind": "ScalarField", "alias": "issueState", @@ -1601,10 +1603,10 @@ return { "selections": v37 }, v38, - v34, - v27, v24, - v26 + v28, + v25, + v27 ] } ] @@ -1638,7 +1640,7 @@ return { "plural": false, "selections": v19 }, - v27 + v28 ] }, { @@ -1654,7 +1656,7 @@ return { "args": null, "storageKey": null }, - v27 + v28 ] }, { @@ -1662,9 +1664,9 @@ return { "type": "IssueComment", "selections": [ v23, - v34, - v27, - v28 + v24, + v28, + v29 ] }, { @@ -1706,7 +1708,7 @@ return { "args": null, "storageKey": null }, - v32, + v33, { "kind": "ScalarField", "alias": null, diff --git a/lib/views/__generated__/prDetailView_pullRequest.graphql.js b/lib/views/__generated__/prDetailView_pullRequest.graphql.js index 3d3b3a848f..e98a5c4a5f 100644 --- a/lib/views/__generated__/prDetailView_pullRequest.graphql.js +++ b/lib/views/__generated__/prDetailView_pullRequest.graphql.js @@ -61,6 +61,7 @@ export type prDetailView_pullRequest = {| +login: string, |}, +body: string, + +bodyHTML: any, +path: string, +commitSha: {| +oid: any @@ -210,19 +211,26 @@ v9 = { v10 = { "kind": "ScalarField", "alias": null, - "name": "number", + "name": "bodyHTML", "args": null, "storageKey": null }, v11 = { + "kind": "ScalarField", + "alias": null, + "name": "number", + "args": null, + "storageKey": null +}, +v12 = { "kind": "ScalarField", "alias": null, "name": "url", "args": null, "storageKey": null }, -v12 = [ - v11 +v13 = [ + v12 ]; return { "kind": "Fragment", @@ -404,6 +412,7 @@ return { ] }, v5, + v10, { "kind": "ScalarField", "alias": null, @@ -420,7 +429,7 @@ return { "concreteType": "PullRequest", "plural": false, "selections": [ - v10 + v11 ] }, { @@ -473,7 +482,7 @@ return { "args": null, "storageKey": null }, - v11 + v12 ] } ] @@ -516,7 +525,7 @@ return { "args": null }, v4, - v10, + v11, { "kind": "ScalarField", "alias": null, @@ -524,13 +533,7 @@ return { "args": null, "storageKey": null }, - { - "kind": "ScalarField", - "alias": null, - "name": "bodyHTML", - "args": null, - "storageKey": null - }, + v10, { "kind": "ScalarField", "alias": null, @@ -559,12 +562,12 @@ return { { "kind": "InlineFragment", "type": "Bot", - "selections": v12 + "selections": v13 }, { "kind": "InlineFragment", "type": "User", - "selections": v12 + "selections": v13 } ] }, @@ -586,7 +589,7 @@ return { } ] }, - v11, + v12, { "kind": "LinkedField", "alias": null, @@ -619,5 +622,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '9feb173b93416270e2ac66924239d1b1'; +(node/*: any*/).hash = 'ab20ccdc3ccc10843176a48cd07d03ae'; module.exports = node; diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 1314a1029f..f57a26ffc9 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -427,6 +427,7 @@ export default createRefetchContainer(BarePullRequestDetailView, { login } body + bodyHTML path commitSha: commit { oid From 6093aa4ef4a0f8f5f22de1332073c05f16a645d3 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 21 Dec 2018 15:32:30 +0100 Subject: [PATCH 1610/4053] use github markdown component to show the comments --- lib/views/pr-comments-view.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index ba3775fead..e0f21a094b 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -4,9 +4,10 @@ import {Point, Range} from 'atom'; import Marker from '../atom/marker'; import Decoration from '../atom/decoration'; - +import GithubDotcomMarkdown from './github-dotcom-markdown'; import Timeago from './timeago'; + export default class PullRequestCommentsView extends React.Component { render() { if (!this.props.reviews) { @@ -46,7 +47,7 @@ class PullRequestCommentView extends React.Component {
    - {this.props.comment.body} {this.props.comment.position} +
    ); From 7f4661437c4a8d630ae0110dfb65f02739548544 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 21 Dec 2018 15:32:52 +0100 Subject: [PATCH 1611/4053] drill switchToIssueish prop --- lib/views/multi-file-patch-view.js | 2 +- lib/views/pr-detail-view.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 2eaa0b89e8..7768d9d5e1 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -281,7 +281,7 @@ export default class MultiFilePatchView extends React.Component { /> )} - + {this.props.multiFilePatch.getFilePatches().map(this.renderFilePatchDecorations)} {this.renderLineDecorations( diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index f57a26ffc9..44560797bf 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -210,6 +210,7 @@ export class BarePullRequestDetailView extends React.Component { shouldRefetch={this.state.refreshing} reviews={this.props.pullRequest.reviews} + switchToIssueish={this.props.switchToIssueish} /> From 6c3a335d8bf86150d1789a625aaa83e775e129ee Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 21 Dec 2018 15:39:40 +0100 Subject: [PATCH 1612/4053] pass switchToIssueish through properly --- lib/controllers/multi-file-patch-controller.js | 1 + lib/views/pr-comments-view.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index cdba88def6..0214aa2db9 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -83,6 +83,7 @@ export default class MultiFilePatchController extends React.Component { discardRows={this.discardRows} selectNextHunk={this.selectNextHunk} selectPreviousHunk={this.selectPreviousHunk} + switchToIssueish={this.props.switchToIssueish} /> ); } diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index e0f21a094b..64750796b1 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -23,7 +23,7 @@ export default class PullRequestCommentsView extends React.Component { return ( - + ); From 43e679d01c38e6eec9e46c285806db78b09be214 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 21 Dec 2018 10:15:51 -0500 Subject: [PATCH 1613/4053] Start a (failing) test for comment positioning --- test/builder/pr.js | 130 ++++++++++++++++++++++++++++ test/views/pr-comments-view.test.js | 33 +++++++ 2 files changed, 163 insertions(+) create mode 100644 test/builder/pr.js create mode 100644 test/views/pr-comments-view.test.js diff --git a/test/builder/pr.js b/test/builder/pr.js new file mode 100644 index 0000000000..24758ece0b --- /dev/null +++ b/test/builder/pr.js @@ -0,0 +1,130 @@ +class CommentBuilder { + constructor() { + this._id = 0; + this._path = 'first.txt'; + this._position = 0; + this._authorLogin = 'someone'; + this._authorAvatarUrl = 'https://avatars3.githubusercontent.com/u/17565?s=32&v=4'; + this._url = 'https://github.com/atom/github/pull/1829/files#r242224689'; + this._createdAt = 0; + this._body = 'Lorem ipsum dolor sit amet, te urbanitas appellantur est.'; + } + + id(i) { + this._id = i; + return this; + } + + path(p) { + this._path = p; + return this; + } + + position(pos) { + this._position = pos; + return this; + } + + authorLogin(login) { + this._authorLogin = login; + return this; + } + + authorAvatarUrl(url) { + this._authorAvatarUrl = url; + return this; + } + + url(u) { + this._url = u; + return this; + } + + createdAt(ts) { + this._createdAt = ts; + return this; + } + + body(text) { + this._body = text; + return this; + } + + build() { + return { + id: this._id, + author: { + login: this._authorLogin, + avatarUrl: this._authorAvatarUrl, + }, + body: this._body, + path: this._path, + position: this._position, + createdAt: this._createdAt, + url: this._url, + }; + } +} + +class ReviewBuilder { + constructor() { + this.nextCommentID = 0; + this._id = 0; + this._comments = []; + } + + id(i) { + this._id = i; + return this; + } + + addComment(block = () => {}) { + const builder = new CommentBuilder(); + builder.id(this.nextCommentID); + this.nextCommentID++; + + block(builder); + this._comments.push(builder.build()); + + return this; + } + + build() { + return { + id: this._id, + comments: {nodes: this._comments}, + }; + } +} + +class PullRequestBuilder { + constructor() { + this.nextCommentID = 0; + this.nextReviewID = 0; + this._reviews = []; + } + + addReview(block = () => {}) { + const builder = new ReviewBuilder(); + builder.id(this.nextReviewID); + this.nextReviewID++; + + block(builder); + this._reviews.push(builder.build()); + return this; + } + + build() { + return { + reviews: {nodes: this._reviews}, + }; + } +} + +export function reviewBuilder() { + return new ReviewBuilder(); +} + +export function pullRequestBuilder() { + return new PullRequestBuilder(); +} diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js new file mode 100644 index 0000000000..36ab79dc61 --- /dev/null +++ b/test/views/pr-comments-view.test.js @@ -0,0 +1,33 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import {multiFilePatchBuilder} from '../builder/patch'; +import {pullRequestBuilder} from '../builder/pr'; +import PrCommentsView from '../../lib/views/pr-comments-view'; + +describe('PrCommentsView', function() { + it('adjusts the position for comments after hunk headers', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file.txt')); + fp.addHunk(h => h.oldRow(5).unchanged('1').added('2', '3', '4').unchanged('5')); + fp.addHunk(h => h.oldRow(20).unchanged('7').deleted('8', '9', '10').unchanged('11')); + fp.addHunk(h => h.oldRow(30).unchanged('13').added('14', '15').deleted('16').unchanged('17')); + }) + .build(); + + const pr = pullRequestBuilder() + .addReview(r => { + r.addComment(c => c.id(0).path('file.txt').position(2).body('one')); + r.addComment(c => c.id(1).path('file.txt').position(9).body('two')); + r.addComment(c => c.id(2).path('file.txt').position(15).body('three')); + }) + .build(); + + const wrapper = shallow(); + + assert.deepEqual(wrapper.find('Marker').at(0).prop('bufferRange').serialize(), [[1, 0], [1, 0]]); + assert.deepEqual(wrapper.find('Marker').at(1).prop('bufferRange').serialize(), [[7, 0], [7, 0]]); + assert.deepEqual(wrapper.find('Marker').at(2).prop('bufferRange').serialize(), [[12, 0], [12, 0]]); + }); +}); From 2ca9fa63d4c1dea775b12eca990c3cd1459d9ec0 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 21 Dec 2018 16:34:37 +0100 Subject: [PATCH 1614/4053] font is a bit large --- styles/pr-comment.less | 1 - 1 file changed, 1 deletion(-) diff --git a/styles/pr-comment.less b/styles/pr-comment.less index 7cb0e62654..427dd1a0b3 100644 --- a/styles/pr-comment.less +++ b/styles/pr-comment.less @@ -30,7 +30,6 @@ &-body { margin-left: @avatar-size + 4px; // avatar + margin - font-size: 1.15em; } } From ea4b7518a94597145df5b58156f061e27a8e63ed Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 21 Dec 2018 13:36:40 -0500 Subject: [PATCH 1615/4053] Don't re-invent the binary... tree... wheel? --- package-lock.json | 7 ++++++- package.json | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 712f5bcef8..b3a9b8401f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1623,6 +1623,11 @@ "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==", "dev": true }, + "bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha1-SfiW1uhYpKSZ34XDj7OZua/4QPg=" + }, "bl": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", @@ -5132,7 +5137,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { diff --git a/package.json b/package.json index 6ab320fd1c..29c0508186 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", "babel-plugin-transform-object-rest-spread": "6.26.0", "babel-preset-react": "6.24.1", + "bintrees": "1.0.2", "bytes": "^3.0.0", "classnames": "2.2.6", "compare-sets": "1.0.1", From 3b89168b4c666702e6383943a545319a9503d326 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 21 Dec 2018 13:37:06 -0500 Subject: [PATCH 1616/4053] Compute buffer rows for diff positions, given as filename + diff row --- lib/models/patch/multi-file-patch.js | 31 ++++++-- test/models/patch/multi-file-patch.test.js | 82 ++++++++++++++++++++++ 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 7336ca1381..132573214d 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,4 +1,5 @@ import {TextBuffer, Range} from 'atom'; +import {RBTree} from 'bintrees'; export default class MultiFilePatch { constructor({buffer, layers, filePatches}) { @@ -16,10 +17,27 @@ export default class MultiFilePatch { this.filePatchesByMarker = new Map(); this.hunksByMarker = new Map(); + // Store a map of {diffRow, offset} for each FilePatch where offset is the number of Hunk headers within the current + // FilePatch that occur before this row in the original diff output. + this.diffRowOffsetIndices = new Map(); + for (const filePatch of this.filePatches) { this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); - for (const hunk of filePatch.getHunks()) { + + let diffRow = 1; + const index = new RBTree((a, b) => a.diffRow - b.diffRow); + this.diffRowOffsetIndices.set(filePatch.getPath(), {startBufferRow: filePatch.getStartRange().start.row, index}); + + for (let hunkIndex = 0; hunkIndex < filePatch.getHunks().length; hunkIndex++) { + const hunk = filePatch.getHunks()[hunkIndex]; this.hunksByMarker.set(hunk.getMarker(), hunk); + + // Advance past the hunk body + diffRow += hunk.bufferRowCount(); + index.insert({diffRow, offset: hunkIndex + 1}); + + // Advance past the next hunk header + diffRow++; } } } @@ -71,10 +89,6 @@ export default class MultiFilePatch { return this.filePatches; } - getFilePatchByPath(path) { - return this.filePatches.find(filePatch => filePatch.getPath() === path); - } - getPathSet() { return this.getFilePatches().reduce((pathSet, filePatch) => { for (const file of [filePatch.getOldFile(), filePatch.getNewFile()]) { @@ -325,6 +339,13 @@ export default class MultiFilePatch { return false; } + getBufferRowForDiffPosition(fileName, diffRow) { + // TODO verify that this works on Windows + const {startBufferRow, index} = this.diffRowOffsetIndices.get(fileName); + const {offset} = index.lowerBound({diffRow}).data(); + return startBufferRow + diffRow - offset; + } + /* * Construct an apply-able patch String. */ diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 123812b2c2..767b17e233 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -275,25 +275,31 @@ describe('MultiFilePatch', function() { it('adopts a buffer from a previous patch', function() { const {multiFilePatch: lastMultiPatch, buffer: lastBuffer, layers: lastLayers} = multiFilePatchBuilder() .addFilePatch(fp => { + fp.setOldFile(f => f.path('A0.txt')); fp.addHunk(h => h.unchanged('a0').added('a1').deleted('a2').unchanged('a3')); }) .addFilePatch(fp => { + fp.setOldFile(f => f.path('A1.txt')); fp.addHunk(h => h.unchanged('a4').deleted('a5').unchanged('a6')); fp.addHunk(h => h.unchanged('a7').added('a8').unchanged('a9')); }) .addFilePatch(fp => { + fp.setOldFile(f => f.path('A2.txt')); fp.addHunk(h => h.oldRow(99).deleted('7').noNewline()); }) .build(); const {multiFilePatch: nextMultiPatch, buffer: nextBuffer, layers: nextLayers} = multiFilePatchBuilder() .addFilePatch(fp => { + fp.setOldFile(f => f.path('B0.txt')); fp.addHunk(h => h.unchanged('b0', 'b1').added('b2').unchanged('b3', 'b4')); }) .addFilePatch(fp => { + fp.setOldFile(f => f.path('B1.txt')); fp.addHunk(h => h.unchanged('b5', 'b6').added('b7')); }) .addFilePatch(fp => { + fp.setOldFile(f => f.path('B2.txt')); fp.addHunk(h => h.unchanged('b8', 'b9').deleted('b10').unchanged('b11')); fp.addHunk(h => h.oldRow(99).deleted('b12').noNewline()); }) @@ -352,6 +358,9 @@ describe('MultiFilePatch', function() { assertMarkedLayerRanges(lastLayers.noNewline, [ [[13, 0], [13, 26]], ]); + + assert.strictEqual(nextMultiPatch.getBufferRowForDiffPosition('B0.txt', 1), 0); + assert.strictEqual(nextMultiPatch.getBufferRowForDiffPosition('B2.txt', 5), 12); }); describe('derived patch generation', function() { @@ -694,4 +703,77 @@ describe('MultiFilePatch', function() { assert.isTrue(multiFilePatch.spansMultipleFiles([6, 10])); }); }); + + describe('diff position translation', function() { + it('offsets rows in the first hunk by the first hunk header', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file.txt')); + fp.addHunk(h => { + h.unchanged('1 (0)').added('2 (1)', '3 (2)').deleted('4 (3)', '5 (4)', '6 (5)').unchanged('7 (6)'); + }); + }) + .build(); + + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('file.txt', 1), 0); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('file.txt', 7), 6); + }); + + it('offsets rows by the number of hunks before the diff row', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file.txt')); + fp.addHunk(h => h.unchanged('0 (1)').added('1 (2)', '2 (3)').deleted('3 (4)').unchanged('4 (5)')); + fp.addHunk(h => h.unchanged('5 (7)').added('6 (8)', '7 (9)', '8 (10)').unchanged('9 (11)')); + fp.addHunk(h => h.unchanged('10 (13)').deleted('11 (14)').unchanged('12 (15)')); + }) + .build(); + + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('file.txt', 7), 5); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('file.txt', 11), 9); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('file.txt', 13), 10); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('file.txt', 15), 12); + }); + + it('resets the offset at the start of each file patch', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('0.txt')); + fp.addHunk(h => h.unchanged('0 (1)').added('1 (2)', '2 (3)').unchanged('3 (4)')); // Offset +1 + fp.addHunk(h => h.unchanged('4 (6)').deleted('5 (7)', '6 (8)', '7 (9)').unchanged('8 (10)')); // Offset +2 + fp.addHunk(h => h.unchanged('9 (12)').deleted('10 (13)').unchanged('11 (14)')); // Offset +3 + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('1.txt')); + fp.addHunk(h => h.unchanged('12 (1)').added('13 (2)').unchanged('14 (3)')); // Offset +1 + fp.addHunk(h => h.unchanged('15 (5)').deleted('16 (6)', '17 (7)', '18 (8)').unchanged('19 (9)')); // Offset +2 + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('2.txt')); + fp.addHunk(h => h.unchanged('20 (1)').added('21 (2)', '22 (3)', '23 (4)', '24 (5)').unchanged('25 (6)')); // Offset +1 + fp.addHunk(h => h.unchanged('26 (8)').deleted('27 (9)', '28 (10)').unchanged('29 (11)')); // Offset +2 + fp.addHunk(h => h.unchanged('30 (13)').added('31 (14)').unchanged('32 (15)')); // Offset +3 + }) + .build(); + + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('0.txt', 1), 0); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('0.txt', 4), 3); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('0.txt', 6), 4); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('0.txt', 10), 8); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('0.txt', 12), 9); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('0.txt', 14), 11); + + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('1.txt', 1), 12); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('1.txt', 3), 14); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('1.txt', 5), 15); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('1.txt', 9), 19); + + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('2.txt', 1), 20); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('2.txt', 6), 25); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('2.txt', 8), 26); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('2.txt', 11), 29); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('2.txt', 13), 30); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('2.txt', 15), 32); + }); + }); }); From ffbf81a887cb56430c6cb74c1d4da4ca5b3a2730 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 21 Dec 2018 13:50:53 -0500 Subject: [PATCH 1617/4053] Use `getBufferRowForDiffPosition` to position review comments --- lib/views/pr-comments-view.js | 29 ++++++++++++++++------------- test/views/pr-comments-view.test.js | 25 ++++++++++++++++--------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index 64750796b1..715376c371 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -2,12 +2,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Point, Range} from 'atom'; +import {toNativePathSep} from '../helpers'; import Marker from '../atom/marker'; import Decoration from '../atom/decoration'; import GithubDotcomMarkdown from './github-dotcom-markdown'; import Timeago from './timeago'; - export default class PullRequestCommentsView extends React.Component { render() { if (!this.props.reviews) { @@ -16,19 +16,22 @@ export default class PullRequestCommentsView extends React.Component { return this.props.reviews.nodes.map(review => { return review.comments.nodes.map(comment => { - if (comment.position !== null) { - const filePatch = this.props.multiFilePatch.getFilePatchByPath(comment.path); - const commentStartPoint = filePatch.getStartRange().start.translate(new Point(comment.position, 0)); - const range = new Range(commentStartPoint, commentStartPoint); - return ( - - - - - - ); + if (!comment.position) { + return null; } - return null; + + const nativePath = toNativePathSep(comment.path); + const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, comment.position); + const point = new Point(row, 0); + const range = new Range(point, point); + + return ( + + + + + + ); }); }); } diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index 36ab79dc61..13089158f3 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -9,25 +9,32 @@ describe('PrCommentsView', function() { it('adjusts the position for comments after hunk headers', function() { const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(fp => { - fp.setOldFile(f => f.path('file.txt')); - fp.addHunk(h => h.oldRow(5).unchanged('1').added('2', '3', '4').unchanged('5')); - fp.addHunk(h => h.oldRow(20).unchanged('7').deleted('8', '9', '10').unchanged('11')); - fp.addHunk(h => h.oldRow(30).unchanged('13').added('14', '15').deleted('16').unchanged('17')); + fp.setOldFile(f => f.path('file0.txt')); + fp.addHunk(h => h.oldRow(5).unchanged('0 (1)').added('1 (2)', '2 (3)', '3 (4)').unchanged('4 (5)')); + fp.addHunk(h => h.oldRow(20).unchanged('5 (7)').deleted('6 (8)', '7 (9)', '8 (10)').unchanged('9 (11)')); + fp.addHunk(h => { + h.oldRow(30).unchanged('10 (13)').added('11 (14)', '12 (15)').deleted('13 (16)').unchanged('14 (17)'); + }); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file1.txt')); + fp.addHunk(h => h.oldRow(5).unchanged('15 (1)').added('16 (2)').unchanged('17 (3)')); + fp.addHunk(h => h.oldRow(20).unchanged('18 (5)').deleted('19 (6)', '20 (7)', '21 (8)').unchanged('22 (9)')); }) .build(); const pr = pullRequestBuilder() .addReview(r => { - r.addComment(c => c.id(0).path('file.txt').position(2).body('one')); - r.addComment(c => c.id(1).path('file.txt').position(9).body('two')); - r.addComment(c => c.id(2).path('file.txt').position(15).body('three')); + r.addComment(c => c.id(0).path('file0.txt').position(2).body('one')); + r.addComment(c => c.id(1).path('file0.txt').position(15).body('three')); + r.addComment(c => c.id(1).path('file1.txt').position(7).body('three')); }) .build(); const wrapper = shallow(); assert.deepEqual(wrapper.find('Marker').at(0).prop('bufferRange').serialize(), [[1, 0], [1, 0]]); - assert.deepEqual(wrapper.find('Marker').at(1).prop('bufferRange').serialize(), [[7, 0], [7, 0]]); - assert.deepEqual(wrapper.find('Marker').at(2).prop('bufferRange').serialize(), [[12, 0], [12, 0]]); + assert.deepEqual(wrapper.find('Marker').at(1).prop('bufferRange').serialize(), [[12, 0], [12, 0]]); + assert.deepEqual(wrapper.find('Marker').at(2).prop('bufferRange').serialize(), [[20, 0], [20, 0]]); }); }); From 8984cbcf71c4352f0b0de56b2023e7ba01cc33b1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 21 Dec 2018 13:56:29 -0500 Subject: [PATCH 1618/4053] Position the decoration after its marker, not before --- lib/views/pr-comments-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index 715376c371..34cd615bfd 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -27,7 +27,7 @@ export default class PullRequestCommentsView extends React.Component { return ( - + From a00dbab2c2260a2b7ead93d623fc5cc3a0927603 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 27 Dec 2018 10:47:10 -0800 Subject: [PATCH 1619/4053] fix `IssueishDetailContainer` tests Co-Authored-By: Katrina Uychaco --- test/containers/issueish-detail-container.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/containers/issueish-detail-container.test.js b/test/containers/issueish-detail-container.test.js index 10728075c7..934f24df9f 100644 --- a/test/containers/issueish-detail-container.test.js +++ b/test/containers/issueish-detail-container.test.js @@ -30,6 +30,8 @@ describe('IssueishDetailContainer', function() { timelineCursor: null, commitCount: 100, commitCursor: null, + commentCount: 100, + commentCursor: null, }, }, { repository: { From 3f6123bfa32b2ff44994f12a9cebe1556e13982b Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 27 Dec 2018 10:59:19 -0800 Subject: [PATCH 1620/4053] fix console prop type warnings Co-Authored-By: Katrina Uychaco --- test/fixtures/props/issueish-pane-props.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/fixtures/props/issueish-pane-props.js b/test/fixtures/props/issueish-pane-props.js index 0224a05577..cabc7e4140 100644 --- a/test/fixtures/props/issueish-pane-props.js +++ b/test/fixtures/props/issueish-pane-props.js @@ -27,6 +27,15 @@ export function issueishDetailContainerProps(overrides = {}) { switchToIssueish: () => {}, onTitleChange: () => {}, + workspace: {}, + commands: {}, + keymaps: {}, + tooltips: {}, + config: {}, + destroy: () => {}, + + itemType: IssueishDetailItem, + ...overrides, }; } From baf6e6d04d437602454eb7a876eb23db51ef19a1 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 27 Dec 2018 11:05:23 -0800 Subject: [PATCH 1621/4053] fix integration tests for checking out a pr --- test/integration/checkout-pr.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integration/checkout-pr.test.js b/test/integration/checkout-pr.test.js index 6a0c6aa407..2fcfcdcec7 100644 --- a/test/integration/checkout-pr.test.js +++ b/test/integration/checkout-pr.test.js @@ -8,7 +8,7 @@ import {createRepositoryResult} from '../fixtures/factories/repository-result'; import IDGenerator from '../fixtures/factories/id-generator'; import {createPullRequestsResult, createPullRequestDetailResult} from '../fixtures/factories/pull-request-result'; -describe('integration: check out a pull request', function() { +describe.only('integration: check out a pull request', function() { let context, wrapper, atomEnv, workspaceElement, git, idGen, repositoryID; beforeEach(async function() { @@ -140,6 +140,8 @@ describe('integration: check out a pull request', function() { timelineCursor: null, commitCount: 100, commitCursor: null, + commentCount: 100, + commentCursor: null, }, }, result); } From 8e95936f2add649f385a0df87322c9eebb40e612 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 27 Dec 2018 11:07:11 -0800 Subject: [PATCH 1622/4053] never did get that git hook to work for stupid .only calls --- test/integration/checkout-pr.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/checkout-pr.test.js b/test/integration/checkout-pr.test.js index 2fcfcdcec7..a15a1adced 100644 --- a/test/integration/checkout-pr.test.js +++ b/test/integration/checkout-pr.test.js @@ -8,7 +8,7 @@ import {createRepositoryResult} from '../fixtures/factories/repository-result'; import IDGenerator from '../fixtures/factories/id-generator'; import {createPullRequestsResult, createPullRequestDetailResult} from '../fixtures/factories/pull-request-result'; -describe.only('integration: check out a pull request', function() { +describe('integration: check out a pull request', function() { let context, wrapper, atomEnv, workspaceElement, git, idGen, repositoryID; beforeEach(async function() { From 8055ee33d996db40c073bcca569a100ca757abc8 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 27 Dec 2018 11:46:57 -0800 Subject: [PATCH 1623/4053] add test for comment with position: null --- test/views/pr-comments-view.test.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index 13089158f3..ca71a0d31c 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -37,4 +37,29 @@ describe('PrCommentsView', function() { assert.deepEqual(wrapper.find('Marker').at(1).prop('bufferRange').serialize(), [[12, 0], [12, 0]]); assert.deepEqual(wrapper.find('Marker').at(2).prop('bufferRange').serialize(), [[20, 0], [20, 0]]); }); + it('does not render comment if position is null', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file0.txt')); + fp.addHunk(h => h.oldRow(5).unchanged('0 (1)').added('1 (2)', '2 (3)', '3 (4)').unchanged('4 (5)')); + fp.addHunk(h => h.oldRow(20).unchanged('5 (7)').deleted('6 (8)', '7 (9)', '8 (10)').unchanged('9 (11)')); + fp.addHunk(h => { + h.oldRow(30).unchanged('10 (13)').added('11 (14)', '12 (15)').deleted('13 (16)').unchanged('14 (17)'); + }); + }) + .build(); + + const pr = pullRequestBuilder() + .addReview(r => { + r.addComment(c => c.id(0).path('file0.txt').position(2).body('one')); + r.addComment(c => c.id(1).path('file0.txt').position(null).body('three')); + }) + .build(); + + const wrapper = shallow(); + + const comments = wrapper.find('PullRequestCommentView'); + assert.lengthOf(comments, 1); + assert.strictEqual(comments.at(0).prop('comment').body, 'one'); + }); }); From 12301c43d7765631428f362178126279b3b95743 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 27 Dec 2018 11:51:20 -0800 Subject: [PATCH 1624/4053] let's call it `PullRequestCommentView` just to be consistent --- test/views/pr-comments-view.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index ca71a0d31c..5617c02120 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -3,9 +3,9 @@ import {shallow} from 'enzyme'; import {multiFilePatchBuilder} from '../builder/patch'; import {pullRequestBuilder} from '../builder/pr'; -import PrCommentsView from '../../lib/views/pr-comments-view'; +import PullRequestCommentsView from '../../lib/views/pr-comments-view'; -describe('PrCommentsView', function() { +describe('PullRequestCommentsView', function() { it('adjusts the position for comments after hunk headers', function() { const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(fp => { @@ -31,7 +31,7 @@ describe('PrCommentsView', function() { }) .build(); - const wrapper = shallow(); + const wrapper = shallow(); assert.deepEqual(wrapper.find('Marker').at(0).prop('bufferRange').serialize(), [[1, 0], [1, 0]]); assert.deepEqual(wrapper.find('Marker').at(1).prop('bufferRange').serialize(), [[12, 0], [12, 0]]); @@ -56,7 +56,7 @@ describe('PrCommentsView', function() { }) .build(); - const wrapper = shallow(); + const wrapper = shallow(); const comments = wrapper.find('PullRequestCommentView'); assert.lengthOf(comments, 1); From 7dd8376dbfe9a3324a27826d497f2be4f4dce6ba Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 27 Dec 2018 11:03:18 -0800 Subject: [PATCH 1625/4053] WIP sort comments in correct order. Red herring. seems like they're coming back sorted, but rendering in the wrong order # 50-character subject line # # 72-character wrapped longer description. This should answer: # # * Why was this change necessary? # * How does it address the problem? # * Are there any side effects? # # Include a link to the ticket, if any. # # Don't forget the following emoji: # # * :art: `:art:` when improving the format/structure of the code # * :racehorse: `:racehorse:` when improving performance # * :non-potable_water: `:non-potable_water:` when plugging memory leaks # * :memo: `:memo:` when writing docs # * :penguin: `:penguin:` when fixing something on Linux # * :apple: `:apple:` when fixing something on Mac OS # * :checkered_flag: `:checkered_flag:` when fixing something on Windows # * :bug: `:bug:` when fixing a bug # * :fire: `:fire:` when removing code or files # * :green_heart: `:green_heart:` when fixing the CI build # * :white_check_mark: `:white_check_mark:` when adding tests # * :lock: `:lock:` when dealing with security # * :arrow_up: `:arrow_up:` when upgrading dependencies # * :arrow_down: `:arrow_down:` when downgrading dependencies # * :shirt: `:shirt:` when removing linter warnings --- lib/views/pr-comments-view.js | 48 ++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index 34cd615bfd..e91417339b 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -14,14 +14,31 @@ export default class PullRequestCommentsView extends React.Component { return null; } - return this.props.reviews.nodes.map(review => { - return review.comments.nodes.map(comment => { + const commentsByPosition = new Map(); + this.props.reviews.nodes.forEach(review => { + review.comments.nodes.forEach(comment => { + const nativePath = toNativePathSep(comment.path); + const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, comment.position); + const commentsAtRow = commentsByPosition.get(row); + if (commentsAtRow) { + commentsAtRow.add(comment); + } else { + commentsByPosition.set(row, new Set([comment])); + } + }); + }); + + return [...commentsByPosition].map(([row, comments]) => { + const getUnixTime = epoch => new Date(epoch).getTime() / 1000; + console.log([...comments]); + const sortedComments = [...comments].sort((a, b) => getUnixTime(a.createdAt) - getUnixTime(b.createdAt)); + + console.log(sortedComments); + return sortedComments.map(comment => { if (!comment.position) { return null; } - const nativePath = toNativePathSep(comment.path); - const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, comment.position); const point = new Point(row, 0); const range = new Range(point, point); @@ -34,6 +51,29 @@ export default class PullRequestCommentsView extends React.Component { ); }); }); + + // debugger; + // + // return this.props.reviews.nodes.map(review => { + // return review.comments.nodes.map(comment => { + // if (!comment.position) { + // return null; + // } + // + // const nativePath = toNativePathSep(comment.path); + // const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, comment.position); + // const point = new Point(row, 0); + // const range = new Range(point, point); + // + // return ( + // + // + // + // + // + // ); + // }); + // }); } } From 7b7d6cdac4da27c1ada6878dc67f14d405011b7c Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 27 Dec 2018 13:46:59 -0800 Subject: [PATCH 1626/4053] Separate out comments by thread, and render them in correct order # 50-character subject line # # 72-character wrapped longer description. This should answer: # # * Why was this change necessary? # * How does it address the problem? # * Are there any side effects? # # Include a link to the ticket, if any. # # Don't forget the following emoji: # # * :art: `:art:` when improving the format/structure of the code # * :racehorse: `:racehorse:` when improving performance # * :non-potable_water: `:non-potable_water:` when plugging memory leaks # * :memo: `:memo:` when writing docs # * :penguin: `:penguin:` when fixing something on Linux # * :apple: `:apple:` when fixing something on Mac OS # * :checkered_flag: `:checkered_flag:` when fixing something on Windows # * :bug: `:bug:` when fixing a bug # * :fire: `:fire:` when removing code or files # * :green_heart: `:green_heart:` when fixing the CI build # * :white_check_mark: `:white_check_mark:` when adding tests # * :lock: `:lock:` when dealing with security # * :arrow_up: `:arrow_up:` when upgrading dependencies # * :arrow_down: `:arrow_down:` when downgrading dependencies # * :shirt: `:shirt:` when removing linter warnings --- lib/views/pr-comments-view.js | 77 ++++++++++++----------------------- 1 file changed, 27 insertions(+), 50 deletions(-) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index e91417339b..1bb8b479c7 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -14,66 +14,43 @@ export default class PullRequestCommentsView extends React.Component { return null; } - const commentsByPosition = new Map(); + const commentsByRootCommentId = new Map(); + this.props.reviews.nodes.forEach(review => { review.comments.nodes.forEach(comment => { - const nativePath = toNativePathSep(comment.path); - const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, comment.position); - const commentsAtRow = commentsByPosition.get(row); - if (commentsAtRow) { - commentsAtRow.add(comment); + if (!comment.replyTo) { + commentsByRootCommentId.set(comment.id, [comment]); } else { - commentsByPosition.set(row, new Set([comment])); + commentsByRootCommentId.get(comment.replyTo.id).push(comment); } }); }); - return [...commentsByPosition].map(([row, comments]) => { - const getUnixTime = epoch => new Date(epoch).getTime() / 1000; - console.log([...comments]); - const sortedComments = [...comments].sort((a, b) => getUnixTime(a.createdAt) - getUnixTime(b.createdAt)); - - console.log(sortedComments); - return sortedComments.map(comment => { - if (!comment.position) { - return null; - } - const point = new Point(row, 0); - const range = new Range(point, point); + return [...commentsByRootCommentId].reverse().map(([rootCommentId, comments]) => { + const rootComment = comments[0]; + if (!rootComment.position) { + return null; + } - return ( - - - - - - ); - }); + const nativePath = toNativePathSep(rootComment.path); + const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, rootComment.position); + const point = new Point(row, 0); + const range = new Range(point, point); + return ( + + + {comments.map(comment => + , + )} + + + ); }); - - // debugger; - // - // return this.props.reviews.nodes.map(review => { - // return review.comments.nodes.map(comment => { - // if (!comment.position) { - // return null; - // } - // - // const nativePath = toNativePathSep(comment.path); - // const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, comment.position); - // const point = new Point(row, 0); - // const range = new Range(point, point); - // - // return ( - // - // - // - // - // - // ); - // }); - // }); } } From 97f22e628a952c440bebd80694779ae507a1d39a Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 27 Dec 2018 14:01:48 -0800 Subject: [PATCH 1627/4053] add tests for `PullRequestCommentView` --- lib/views/pr-comments-view.js | 7 ++-- test/views/pr-comments-view.test.js | 56 ++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index 1bb8b479c7..a1beeb9d72 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -54,14 +54,15 @@ export default class PullRequestCommentsView extends React.Component { } } -class PullRequestCommentView extends React.Component { +export class PullRequestCommentView extends React.Component { render() { const author = this.props.comment.author; + const login = author ? author.login : 'someone'; return (
    - {author.login} - {author ? author.login : 'someone'} commented{' '} + {login} + {login} commented{' '} diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index 5617c02120..ccb7b78673 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -3,7 +3,7 @@ import {shallow} from 'enzyme'; import {multiFilePatchBuilder} from '../builder/patch'; import {pullRequestBuilder} from '../builder/pr'; -import PullRequestCommentsView from '../../lib/views/pr-comments-view'; +import PullRequestCommentsView, {PullRequestCommentView} from '../../lib/views/pr-comments-view'; describe('PullRequestCommentsView', function() { it('adjusts the position for comments after hunk headers', function() { @@ -63,3 +63,57 @@ describe('PullRequestCommentsView', function() { assert.strictEqual(comments.at(0).prop('comment').body, 'one'); }); }); + +describe('PullRequestCommentView', function() { + const avatarUrl = 'https://avatars3.githubusercontent.com/u/3781742?s=40&v=4'; + const login = 'annthurium'; + const commentUrl = 'https://github.com/kuychaco/test-repo/pull/4#discussion_r244214873'; + const createdAt = '2018-12-27T17:51:17Z'; + const bodyHTML = '
    yo yo
    '; + const switchToIssueish = () => {}; + + function buildApp(overrideProps = {}, opts = {}) { + const props = { + comment: { + bodyHTML, + url: commentUrl, + createdAt, + author: { + avatarUrl, + login, + }, + ...overrideProps, + }, + switchToIssueish, + ...opts, + }; + + return ( + + ); + } + it('renders the PullRequestCommentReview information', function() { + const wrapper = shallow(buildApp()); + const avatar = wrapper.find('.github-PrComment-avatar'); + + assert.strictEqual(avatar.getElement('src').props.src, avatarUrl); + assert.strictEqual(avatar.getElement('alt').props.alt, login); + + assert.isTrue(wrapper.text().includes(`${login} commented`)); + + const a = wrapper.find('.github-PrComment-timeAgo'); + assert.strictEqual(a.getElement('href').props.href, commentUrl); + + const timeAgo = wrapper.find('Timeago'); + assert.strictEqual(timeAgo.prop('time'), createdAt); + + const githubDotcomMarkdown = wrapper.find('GithubDotcomMarkdown'); + assert.strictEqual(githubDotcomMarkdown.prop('html'), bodyHTML); + assert.strictEqual(githubDotcomMarkdown.prop('switchToIssueish'), switchToIssueish); + }); + + it('contains the text `someone commented` for null authors', function() { + const wrapper = shallow(buildApp({author: null})); + assert.isTrue(wrapper.text().includes('someone commented')); + }); +}); From ed7d0a5c5b42435ea51de16ff680551effe22dfb Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 27 Dec 2018 15:36:24 -0800 Subject: [PATCH 1628/4053] WIP create PrReviewsContainer to lay foundation for pagination Move query from PrDetailView to this container Co-Authored-By: Tilde Ann Thurium --- .../issueishDetailContainerQuery.graphql.js | 467 +++++++------- .../prReviewsContainerQuery.graphql.js | 503 +++++++++++++++ .../prReviewsContainer_pullRequest.graphql.js | 354 +++++++++++ lib/containers/issueish-detail-container.js | 6 + lib/containers/pr-reviews-container.js | 92 +++ .../prDetailViewRefetchQuery.graphql.js | 587 +++++++++--------- .../prDetailView_pullRequest.graphql.js | 447 +++---------- lib/views/multi-file-patch-view.js | 4 +- lib/views/pr-comments-view.js | 2 +- lib/views/pr-detail-view.js | 78 +-- 10 files changed, 1548 insertions(+), 992 deletions(-) create mode 100644 lib/containers/__generated__/prReviewsContainerQuery.graphql.js create mode 100644 lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js create mode 100644 lib/containers/pr-reviews-container.js diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 55b80a18d2..ea0f609242 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 063056b23fdd8f4f06939894757d5382 + * @relayHash 615fe759e1fcacb3ed5561310f21f8ed */ /* eslint-disable */ @@ -20,6 +20,8 @@ export type issueishDetailContainerQueryVariables = {| commitCursor?: ?string, commentCount: number, commentCursor?: ?string, + reviewCount?: ?number, + reviewCursor?: ?string, |}; export type issueishDetailContainerQueryResponse = {| +repository: ?{| @@ -44,12 +46,12 @@ query issueishDetailContainerQuery( $commitCursor: String ) { repository(owner: $repoOwner, name: $repoName) { - ...issueishDetailController_repository_2LgaQ1 + ...issueishDetailController_repository_y3nHF id } } -fragment issueishDetailController_repository_2LgaQ1 on Repository { +fragment issueishDetailController_repository_y3nHF on Repository { ...issueDetailView_repository ...prDetailView_repository name @@ -158,87 +160,7 @@ fragment prDetailView_pullRequest_1Etigl on PullRequest { } isCrossRepository changedFiles - comments { - totalCount - } - reviews(first: 100) { - pageInfo { - hasNextPage - endCursor - } - nodes { - id - body - commitId: commit { - oid - id - } - state - submittedAt - login: author { - __typename - login - ... on Node { - id - } - } - author { - __typename - avatarUrl - ... on Node { - id - } - } - comments(first: 100) { - pageInfo { - hasNextPage - endCursor - } - nodes { - id - pullRequestId: pullRequest { - number - id - } - databaseId - login: author { - __typename - login - ... on Node { - id - } - } - author { - __typename - avatarUrl - login - ... on Node { - id - } - } - body - bodyHTML - path - commitSha: commit { - oid - id - } - diffHunk - position - originalPosition - originalCommitId: originalCommit { - oid - id - } - replyTo { - id - } - createdAt - url - } - } - } - } + ...prReviewsContainer_pullRequest_3CUNoW ...prCommitsView_pullRequest_38TpXw countedCommits: commits { totalCount @@ -278,6 +200,69 @@ fragment prDetailView_pullRequest_1Etigl on PullRequest { } } +fragment prReviewsContainer_pullRequest_3CUNoW on PullRequest { + url + reviews { + pageInfo { + hasNextPage + endCursor + } + edges { + cursor + node { + id + body + commitId: commit { + oid + id + } + state + submittedAt + login: author { + __typename + login + ... on Node { + id + } + } + author { + __typename + avatarUrl + ... on Node { + id + } + } + comments(first: 100) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + author { + __typename + avatarUrl + login + ... on Node { + id + } + } + bodyHTML + path + position + replyTo { + id + } + createdAt + url + } + } + __typename + } + } + } +} + fragment prCommitsView_pullRequest_38TpXw on PullRequest { url commits(first: $commitCount, after: $commitCursor) { @@ -651,6 +636,18 @@ var v0 = [ "name": "commentCursor", "type": "String", "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCount", + "type": "Int", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCursor", + "type": "String", + "defaultValue": null } ], v1 = [ @@ -1106,15 +1103,7 @@ v34 = [ "type": "Int" } ], -v35 = [ - { - "kind": "Literal", - "name": "first", - "value": 100, - "type": "Int" - } -], -v36 = { +v35 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -1127,14 +1116,7 @@ v36 = { v18 ] }, -v37 = { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null -}, -v38 = [ +v36 = [ { "kind": "ScalarField", "alias": null, @@ -1144,31 +1126,29 @@ v38 = [ }, v2 ], -v39 = { - "kind": "LinkedField", - "alias": "login", - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": v6 -}, -v40 = { +v37 = [ + { + "kind": "Literal", + "name": "first", + "value": 100, + "type": "Int" + } +], +v38 = { "kind": "ScalarField", "alias": null, "name": "path", "args": null, "storageKey": null }, -v41 = { +v39 = { "kind": "ScalarField", "alias": null, "name": "position", "args": null, "storageKey": null }, -v42 = { +v40 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -1176,9 +1156,9 @@ v42 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v38 + "selections": v36 }, -v43 = { +v41 = { "kind": "LinkedField", "alias": null, "name": "actor", @@ -1193,7 +1173,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_2LgaQ1\n id\n }\n}\n\nfragment issueishDetailController_repository_2LgaQ1 on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_1Etigl\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_1Etigl\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_3CUNoW\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_3CUNoW on PullRequest {\n url\n reviews {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1245,6 +1225,18 @@ return { "variableName": "issueishNumber", "type": null }, + { + "kind": "Variable", + "name": "reviewCount", + "variableName": "reviewCount", + "type": null + }, + { + "kind": "Variable", + "name": "reviewCursor", + "variableName": "reviewCursor", + "type": null + }, { "kind": "Variable", "name": "timelineCount", @@ -1509,173 +1501,144 @@ return { "args": null, "storageKey": null }, + v14, { "kind": "LinkedField", "alias": null, - "name": "comments", + "name": "reviews", "storageKey": null, "args": null, - "concreteType": "IssueCommentConnection", - "plural": false, - "selections": v32 - }, - { - "kind": "LinkedField", - "alias": null, - "name": "reviews", - "storageKey": "reviews(first:100)", - "args": v35, "concreteType": "PullRequestReviewConnection", "plural": false, "selections": [ - v36, + v35, { "kind": "LinkedField", "alias": null, - "name": "nodes", + "name": "edges", "storageKey": null, "args": null, - "concreteType": "PullRequestReview", + "concreteType": "PullRequestReviewEdge", "plural": true, "selections": [ - v2, - v37, - { - "kind": "LinkedField", - "alias": "commitId", - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v38 - }, - v11, - { - "kind": "ScalarField", - "alias": null, - "name": "submittedAt", - "args": null, - "storageKey": null - }, - v39, + v21, { "kind": "LinkedField", "alias": null, - "name": "author", + "name": "node", "storageKey": null, "args": null, - "concreteType": null, + "concreteType": "PullRequestReview", "plural": false, "selections": [ - v4, - v13, - v2 - ] - }, - { - "kind": "LinkedField", - "alias": null, - "name": "comments", - "storageKey": "comments(first:100)", - "args": v35, - "concreteType": "PullRequestReviewCommentConnection", - "plural": false, - "selections": [ - v36, + v2, + { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "commitId", + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v36 + }, + v11, + { + "kind": "ScalarField", + "alias": null, + "name": "submittedAt", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "login", + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v6 + }, { "kind": "LinkedField", "alias": null, - "name": "nodes", + "name": "author", "storageKey": null, "args": null, - "concreteType": "PullRequestReviewComment", - "plural": true, + "concreteType": null, + "plural": false, "selections": [ - { - "kind": "LinkedField", - "alias": "commitSha", - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v38 - }, - v2, - { - "kind": "ScalarField", - "alias": null, - "name": "databaseId", - "args": null, - "storageKey": null - }, - v39, - v26, - v37, - v12, - v40, - { - "kind": "LinkedField", - "alias": "pullRequestId", - "name": "pullRequest", - "storageKey": null, - "args": null, - "concreteType": "PullRequest", - "plural": false, - "selections": [ - v10, - v2 - ] - }, - { - "kind": "ScalarField", - "alias": null, - "name": "diffHunk", - "args": null, - "storageKey": null - }, - v41, - { - "kind": "ScalarField", - "alias": null, - "name": "originalPosition", - "args": null, - "storageKey": null - }, - { - "kind": "LinkedField", - "alias": "originalCommitId", - "name": "originalCommit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v38 - }, + v4, + v13, + v2 + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": "comments(first:100)", + "args": v37, + "concreteType": "PullRequestReviewCommentConnection", + "plural": false, + "selections": [ + v35, { "kind": "LinkedField", "alias": null, - "name": "replyTo", + "name": "nodes", "storageKey": null, "args": null, "concreteType": "PullRequestReviewComment", - "plural": false, + "plural": true, "selections": [ - v2 + v2, + v26, + v12, + v38, + v39, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v2 + ] + }, + v27, + v14 ] - }, - v27, - v14 + } ] - } + }, + v4 ] } ] } ] }, - v14, + { + "kind": "LinkedHandle", + "alias": null, + "name": "reviews", + "args": null, + "handle": "connection", + "key": "prReviewsContainer_reviews", + "filters": null + }, v10, { "kind": "LinkedField", @@ -1856,13 +1819,13 @@ return { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v42, + v40, { "kind": "LinkedField", "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v35, + "args": v37, "concreteType": "CommitCommentConnection", "plural": false, "selections": [ @@ -1895,11 +1858,11 @@ return { "plural": false, "selections": v23 }, - v42, + v40, v12, v27, - v40, - v41 + v38, + v39 ] } ] @@ -1912,7 +1875,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v43, + v41, { "kind": "LinkedField", "alias": null, @@ -1921,7 +1884,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v38 + "selections": v36 }, { "kind": "LinkedField", @@ -1931,7 +1894,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v38 + "selections": v36 }, v27 ] @@ -1940,8 +1903,8 @@ return { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v43, - v42, + v41, + v40, { "kind": "ScalarField", "alias": null, @@ -1981,5 +1944,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'b749679230ad5e75455f1923ba78b425'; +(node/*: any*/).hash = 'e4903c4025a67651b3086652adb8aa06'; module.exports = node; diff --git a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js new file mode 100644 index 0000000000..8f1deb1224 --- /dev/null +++ b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js @@ -0,0 +1,503 @@ +/** + * @flow + * @relayHash dc9158ed713eb5e8c4c725dd4bfc9e00 + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest } from 'relay-runtime'; +type prReviewsContainer_pullRequest$ref = any; +export type prReviewsContainerQueryVariables = {| + reviewCount?: ?number, + reviewCursor?: ?string, + url: any, +|}; +export type prReviewsContainerQueryResponse = {| + +resource: ?{| + +$fragmentRefs: prReviewsContainer_pullRequest$ref + |} +|}; +export type prReviewsContainerQuery = {| + variables: prReviewsContainerQueryVariables, + response: prReviewsContainerQueryResponse, +|}; +*/ + + +/* +query prReviewsContainerQuery( + $reviewCount: Int + $reviewCursor: String + $url: URI! +) { + resource(url: $url) { + __typename + ... on PullRequest { + ...prReviewsContainer_pullRequest_2zzc96 + } + ... on Node { + id + } + } +} + +fragment prReviewsContainer_pullRequest_2zzc96 on PullRequest { + url + reviews(first: $reviewCount, after: $reviewCursor) { + pageInfo { + hasNextPage + endCursor + } + edges { + cursor + node { + id + body + commitId: commit { + oid + id + } + state + submittedAt + login: author { + __typename + login + ... on Node { + id + } + } + author { + __typename + avatarUrl + ... on Node { + id + } + } + comments(first: 100) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + author { + __typename + avatarUrl + login + ... on Node { + id + } + } + bodyHTML + path + position + replyTo { + id + } + createdAt + url + } + } + __typename + } + } + } +} +*/ + +const node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "kind": "LocalArgument", + "name": "reviewCount", + "type": "Int", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCursor", + "type": "String", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "url", + "type": "URI!", + "defaultValue": null + } +], +v1 = [ + { + "kind": "Variable", + "name": "url", + "variableName": "url", + "type": "URI!" + } +], +v2 = { + "kind": "ScalarField", + "alias": null, + "name": "__typename", + "args": null, + "storageKey": null +}, +v3 = { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null +}, +v4 = { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null +}, +v5 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "reviewCursor", + "type": "String" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "reviewCount", + "type": "Int" + } +], +v6 = { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null + } + ] +}, +v7 = { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null +}, +v8 = { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null +}; +return { + "kind": "Request", + "operationKind": "query", + "name": "prReviewsContainerQuery", + "id": null, + "text": "query prReviewsContainerQuery(\n $reviewCount: Int\n $reviewCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_2zzc96\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n", + "metadata": {}, + "fragment": { + "kind": "Fragment", + "name": "prReviewsContainerQuery", + "type": "Query", + "metadata": null, + "argumentDefinitions": v0, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "resource", + "storageKey": null, + "args": v1, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "type": "PullRequest", + "selections": [ + { + "kind": "FragmentSpread", + "name": "prReviewsContainer_pullRequest", + "args": [ + { + "kind": "Variable", + "name": "reviewCount", + "variableName": "reviewCount", + "type": null + }, + { + "kind": "Variable", + "name": "reviewCursor", + "variableName": "reviewCursor", + "type": null + } + ] + } + ] + } + ] + } + ] + }, + "operation": { + "kind": "Operation", + "name": "prReviewsContainerQuery", + "argumentDefinitions": v0, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "resource", + "storageKey": null, + "args": v1, + "concreteType": null, + "plural": false, + "selections": [ + v2, + v3, + { + "kind": "InlineFragment", + "type": "PullRequest", + "selections": [ + v4, + { + "kind": "LinkedField", + "alias": null, + "name": "reviews", + "storageKey": null, + "args": v5, + "concreteType": "PullRequestReviewConnection", + "plural": false, + "selections": [ + v6, + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewEdge", + "plural": true, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "cursor", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReview", + "plural": false, + "selections": [ + v3, + { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "commitId", + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "oid", + "args": null, + "storageKey": null + }, + v3 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "submittedAt", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "login", + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v2, + v7, + v3 + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v2, + v8, + v3 + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": "comments(first:100)", + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100, + "type": "Int" + } + ], + "concreteType": "PullRequestReviewCommentConnection", + "plural": false, + "selections": [ + v6, + { + "kind": "LinkedField", + "alias": null, + "name": "nodes", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": true, + "selections": [ + v3, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v2, + v8, + v7, + v3 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "path", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "position", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v3 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null + }, + v4 + ] + } + ] + }, + v2 + ] + } + ] + } + ] + }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "reviews", + "args": v5, + "handle": "connection", + "key": "prReviewsContainer_reviews", + "filters": null + } + ] + } + ] + } + ] + } +}; +})(); +// prettier-ignore +(node/*: any*/).hash = '29e5b225e2d3999e4cd475edc354015d'; +module.exports = node; diff --git a/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js new file mode 100644 index 0000000000..192298ed34 --- /dev/null +++ b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js @@ -0,0 +1,354 @@ +/** + * @flow + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteFragment } from 'relay-runtime'; +export type PullRequestReviewState = "APPROVED" | "CHANGES_REQUESTED" | "COMMENTED" | "DISMISSED" | "PENDING" | "%future added value"; +import type { FragmentReference } from "relay-runtime"; +declare export opaque type prReviewsContainer_pullRequest$ref: FragmentReference; +export type prReviewsContainer_pullRequest = {| + +url: any, + +reviews: ?{| + +pageInfo: {| + +hasNextPage: boolean, + +endCursor: ?string, + |}, + +edges: ?$ReadOnlyArray, + |}, + |}, + |}>, + |}, + +$refType: prReviewsContainer_pullRequest$ref, +|}; +*/ + + +const node/*: ConcreteFragment*/ = (function(){ +var v0 = { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null +}, +v1 = { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null + } + ] +}, +v2 = { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null +}, +v3 = { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null +}, +v4 = { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null +}; +return { + "kind": "Fragment", + "name": "prReviewsContainer_pullRequest", + "type": "PullRequest", + "metadata": { + "connection": [ + { + "count": "reviewCount", + "cursor": "reviewCursor", + "direction": "forward", + "path": [ + "reviews" + ] + } + ] + }, + "argumentDefinitions": [ + { + "kind": "LocalArgument", + "name": "reviewCount", + "type": "Int", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCursor", + "type": "String", + "defaultValue": null + } + ], + "selections": [ + v0, + { + "kind": "LinkedField", + "alias": "reviews", + "name": "__prReviewsContainer_reviews_connection", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewConnection", + "plural": false, + "selections": [ + v1, + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewEdge", + "plural": true, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "cursor", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReview", + "plural": false, + "selections": [ + v2, + { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "commitId", + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "oid", + "args": null, + "storageKey": null + } + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "submittedAt", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "login", + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v3 + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v4 + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": "comments(first:100)", + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100, + "type": "Int" + } + ], + "concreteType": "PullRequestReviewCommentConnection", + "plural": false, + "selections": [ + v1, + { + "kind": "LinkedField", + "alias": null, + "name": "nodes", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": true, + "selections": [ + v2, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v4, + v3 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "path", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "position", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v2 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null + }, + v0 + ] + } + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "__typename", + "args": null, + "storageKey": null + } + ] + } + ] + } + ] + } + ] +}; +})(); +// prettier-ignore +(node/*: any*/).hash = 'd22654576b18d8a49cff9628b69d7b17'; +module.exports = node; diff --git a/lib/containers/issueish-detail-container.js b/lib/containers/issueish-detail-container.js index 2f8a2e40f1..ecb33175e9 100644 --- a/lib/containers/issueish-detail-container.js +++ b/lib/containers/issueish-detail-container.js @@ -124,6 +124,8 @@ export default class IssueishDetailContainer extends React.Component { $commitCursor: String, $commentCount: Int!, $commentCursor: String, + $reviewCount: Int, + $reviewCursor: String, ) { repository(owner: $repoOwner, name: $repoName) { ...issueishDetailController_repository @arguments( @@ -134,6 +136,8 @@ export default class IssueishDetailContainer extends React.Component { commitCursor: $commitCursor, commentCount: $commentCount, commentCursor: $commentCursor, + reviewCount: $reviewCount, + reviewCursor: $reviewCursor, ) } } @@ -148,6 +152,8 @@ export default class IssueishDetailContainer extends React.Component { commitCursor: null, commentCount: 100, commentCursor: null, + reviewCount: 3, + reviewCursor: null, }; return ( diff --git a/lib/containers/pr-reviews-container.js b/lib/containers/pr-reviews-container.js new file mode 100644 index 0000000000..d4f6992ee6 --- /dev/null +++ b/lib/containers/pr-reviews-container.js @@ -0,0 +1,92 @@ +import {graphql, createPaginationContainer} from 'react-relay'; + +import PullRequestReviewsView from '../views/pr-comments-view'; + +export default createPaginationContainer(PullRequestReviewsView, { + pullRequest: graphql` + fragment prReviewsContainer_pullRequest on PullRequest + @argumentDefinitions( + reviewCount: {type: "Int"}, + reviewCursor: {type: "String"} + ) { + ... on PullRequest { + url + reviews(first: $reviewCount, after: $reviewCursor) @connection(key: "prReviewsContainer_reviews") { + pageInfo { + hasNextPage + endCursor + } + + edges { + cursor + node { + id + body + commitId: commit { + oid + } + state + submittedAt + login: author { + login + } + author { + avatarUrl + } + comments(first: 100) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + author { + avatarUrl + login + } + bodyHTML + path + position + replyTo { + id + } + createdAt + url + } + } + } + } + } + } + } + `, +}, { + direction: 'forward', + getConnectionFromProps(props) { + console.log(props.pullRequest); + return props.pullRequest.reviews; + }, + getFragmentVariables(prevVars, totalCount) { + return { + ...prevVars, + reviewCount: totalCount, + }; + }, + getVariables(props, {count, cursor}, fragmentVariables) { + console.log(props.pullRequest); + return { + url: props.pullRequest.url, + reviewCount: count, + reviewCursor: cursor, + }; + }, + query: graphql` + query prReviewsContainerQuery($reviewCount: Int, $reviewCursor: String, $url: URI!) { + resource(url: $url) { + ... on PullRequest { + ...prReviewsContainer_pullRequest @arguments(reviewCount: $reviewCount, reviewCursor: $reviewCursor) + } + } + } + `, +}); diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index 34aff4255d..c82a1a5e27 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 83a0f29490df40e31d9f5aee5cdbae2f + * @relayHash b8344287c886583940c118565aa06547 */ /* eslint-disable */ @@ -20,6 +20,8 @@ export type prDetailViewRefetchQueryVariables = {| commitCursor?: ?string, commentCount: number, commentCursor?: ?string, + reviewCount?: ?number, + reviewCursor?: ?string, |}; export type prDetailViewRefetchQueryResponse = {| +repository: ?{| @@ -44,6 +46,8 @@ query prDetailViewRefetchQuery( $timelineCursor: String $commitCount: Int! $commitCursor: String + $reviewCount: Int + $reviewCursor: String ) { repository: node(id: $repoId) { __typename @@ -52,7 +56,7 @@ query prDetailViewRefetchQuery( } pullRequest: node(id: $issueishId) { __typename - ...prDetailView_pullRequest_1Etigl + ...prDetailView_pullRequest_2qM2KL id } } @@ -67,94 +71,14 @@ fragment prDetailView_repository_3D8CP9 on Repository { } } -fragment prDetailView_pullRequest_1Etigl on PullRequest { +fragment prDetailView_pullRequest_2qM2KL on PullRequest { __typename ... on Node { id } isCrossRepository changedFiles - comments { - totalCount - } - reviews(first: 100) { - pageInfo { - hasNextPage - endCursor - } - nodes { - id - body - commitId: commit { - oid - id - } - state - submittedAt - login: author { - __typename - login - ... on Node { - id - } - } - author { - __typename - avatarUrl - ... on Node { - id - } - } - comments(first: 100) { - pageInfo { - hasNextPage - endCursor - } - nodes { - id - pullRequestId: pullRequest { - number - id - } - databaseId - login: author { - __typename - login - ... on Node { - id - } - } - author { - __typename - avatarUrl - login - ... on Node { - id - } - } - body - bodyHTML - path - commitSha: commit { - oid - id - } - diffHunk - position - originalPosition - originalCommitId: originalCommit { - oid - id - } - replyTo { - id - } - createdAt - url - } - } - } - } + ...prReviewsContainer_pullRequest_2zzc96 ...prCommitsView_pullRequest_38TpXw countedCommits: commits { totalCount @@ -194,6 +118,69 @@ fragment prDetailView_pullRequest_1Etigl on PullRequest { } } +fragment prReviewsContainer_pullRequest_2zzc96 on PullRequest { + url + reviews(first: $reviewCount, after: $reviewCursor) { + pageInfo { + hasNextPage + endCursor + } + edges { + cursor + node { + id + body + commitId: commit { + oid + id + } + state + submittedAt + login: author { + __typename + login + ... on Node { + id + } + } + author { + __typename + avatarUrl + ... on Node { + id + } + } + comments(first: 100) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + author { + __typename + avatarUrl + login + ... on Node { + id + } + } + bodyHTML + path + position + replyTo { + id + } + createdAt + url + } + } + __typename + } + } + } +} + fragment prCommitsView_pullRequest_38TpXw on PullRequest { url commits(first: $commitCount, after: $commitCursor) { @@ -539,6 +526,18 @@ var v0 = [ "name": "commentCursor", "type": "String", "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCount", + "type": "Int", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCursor", + "type": "String", + "defaultValue": null } ], v1 = [ @@ -615,7 +614,7 @@ v10 = { v11 = { "kind": "ScalarField", "alias": null, - "name": "state", + "name": "number", "args": null, "storageKey": null }, @@ -626,20 +625,24 @@ v12 = { "args": null, "storageKey": null }, -v13 = [ - { - "kind": "ScalarField", - "alias": null, - "name": "totalCount", - "args": null, - "storageKey": null - } -], +v13 = { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null +}, v14 = [ { - "kind": "Literal", + "kind": "Variable", + "name": "after", + "variableName": "reviewCursor", + "type": "String" + }, + { + "kind": "Variable", "name": "first", - "value": 100, + "variableName": "reviewCount", "type": "Int" } ], @@ -673,7 +676,7 @@ v17 = { v18 = { "kind": "ScalarField", "alias": null, - "name": "body", + "name": "cursor", "args": null, "storageKey": null }, @@ -688,14 +691,11 @@ v19 = [ v6 ], v20 = { - "kind": "LinkedField", - "alias": "login", - "name": "author", - "storageKey": null, + "kind": "ScalarField", + "alias": null, + "name": "state", "args": null, - "concreteType": null, - "plural": false, - "selections": v9 + "storageKey": null }, v21 = { "kind": "ScalarField", @@ -705,12 +705,20 @@ v21 = { "storageKey": null }, v22 = [ + { + "kind": "Literal", + "name": "first", + "value": 100, + "type": "Int" + } +], +v23 = [ v5, v21, v8, v6 ], -v23 = { +v24 = { "kind": "LinkedField", "alias": null, "name": "author", @@ -718,26 +726,19 @@ v23 = { "args": null, "concreteType": null, "plural": false, - "selections": v22 -}, -v24 = { - "kind": "ScalarField", - "alias": null, - "name": "bodyHTML", - "args": null, - "storageKey": null + "selections": v23 }, v25 = { "kind": "ScalarField", "alias": null, - "name": "path", + "name": "bodyHTML", "args": null, "storageKey": null }, v26 = { "kind": "ScalarField", "alias": null, - "name": "number", + "name": "path", "args": null, "storageKey": null }, @@ -755,14 +756,7 @@ v28 = { "args": null, "storageKey": null }, -v29 = { - "kind": "ScalarField", - "alias": null, - "name": "url", - "args": null, - "storageKey": null -}, -v30 = [ +v29 = [ { "kind": "Variable", "name": "after", @@ -776,7 +770,7 @@ v30 = [ "type": "Int" } ], -v31 = { +v30 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -789,31 +783,33 @@ v31 = { v15 ] }, -v32 = { - "kind": "ScalarField", - "alias": null, - "name": "cursor", - "args": null, - "storageKey": null -}, -v33 = { +v31 = { "kind": "ScalarField", "alias": "sha", "name": "oid", "args": null, "storageKey": null }, -v34 = { +v32 = [ + { + "kind": "ScalarField", + "alias": null, + "name": "totalCount", + "args": null, + "storageKey": null + } +], +v33 = { "kind": "ScalarField", "alias": null, "name": "title", "args": null, "storageKey": null }, -v35 = [ - v29 +v34 = [ + v13 ], -v36 = [ +v35 = [ { "kind": "Variable", "name": "after", @@ -827,13 +823,13 @@ v36 = [ "type": "Int" } ], -v37 = [ +v36 = [ v5, v8, v21, v6 ], -v38 = { +v37 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -843,7 +839,7 @@ v38 = { "plural": false, "selections": v19 }, -v39 = { +v38 = { "kind": "LinkedField", "alias": null, "name": "actor", @@ -851,9 +847,9 @@ v39 = { "args": null, "concreteType": null, "plural": false, - "selections": v22 + "selections": v23 }, -v40 = { +v39 = { "kind": "LinkedField", "alias": null, "name": "user", @@ -871,7 +867,7 @@ return { "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_1Etigl\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n comments {\n totalCount\n }\n reviews(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n pullRequestId: pullRequest {\n number\n id\n }\n databaseId\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n commitSha: commit {\n oid\n id\n }\n diffHunk\n position\n originalPosition\n originalCommitId: originalCommit {\n oid\n id\n }\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n }\n }\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int\n $reviewCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_2zzc96\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -936,6 +932,18 @@ return { "variableName": "commitCursor", "type": null }, + { + "kind": "Variable", + "name": "reviewCount", + "variableName": "reviewCount", + "type": null + }, + { + "kind": "Variable", + "name": "reviewCursor", + "variableName": "reviewCursor", + "type": null + }, v2, v3 ] @@ -995,21 +1003,12 @@ return { "args": null, "storageKey": null }, - { - "kind": "LinkedField", - "alias": null, - "name": "comments", - "storageKey": null, - "args": null, - "concreteType": "IssueCommentConnection", - "plural": false, - "selections": v13 - }, + v13, { "kind": "LinkedField", "alias": null, "name": "reviews", - "storageKey": "reviews(first:100)", + "storageKey": null, "args": v14, "concreteType": "PullRequestReviewConnection", "plural": false, @@ -1018,160 +1017,140 @@ return { { "kind": "LinkedField", "alias": null, - "name": "nodes", + "name": "edges", "storageKey": null, "args": null, - "concreteType": "PullRequestReview", + "concreteType": "PullRequestReviewEdge", "plural": true, "selections": [ - v6, v18, - { - "kind": "LinkedField", - "alias": "commitId", - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v19 - }, - v11, - { - "kind": "ScalarField", - "alias": null, - "name": "submittedAt", - "args": null, - "storageKey": null - }, - v20, { "kind": "LinkedField", "alias": null, - "name": "author", + "name": "node", "storageKey": null, "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v21, - v6 - ] - }, - { - "kind": "LinkedField", - "alias": null, - "name": "comments", - "storageKey": "comments(first:100)", - "args": v14, - "concreteType": "PullRequestReviewCommentConnection", + "concreteType": "PullRequestReview", "plural": false, "selections": [ - v17, + v6, + { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "commitId", + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": v19 + }, + v20, + { + "kind": "ScalarField", + "alias": null, + "name": "submittedAt", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "login", + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": v9 + }, { "kind": "LinkedField", "alias": null, - "name": "nodes", + "name": "author", "storageKey": null, "args": null, - "concreteType": "PullRequestReviewComment", - "plural": true, + "concreteType": null, + "plural": false, "selections": [ - { - "kind": "LinkedField", - "alias": "commitSha", - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v19 - }, - v6, - { - "kind": "ScalarField", - "alias": null, - "name": "databaseId", - "args": null, - "storageKey": null - }, - v20, - v23, - v18, - v24, - v25, - { - "kind": "LinkedField", - "alias": "pullRequestId", - "name": "pullRequest", - "storageKey": null, - "args": null, - "concreteType": "PullRequest", - "plural": false, - "selections": [ - v26, - v6 - ] - }, - { - "kind": "ScalarField", - "alias": null, - "name": "diffHunk", - "args": null, - "storageKey": null - }, - v27, - { - "kind": "ScalarField", - "alias": null, - "name": "originalPosition", - "args": null, - "storageKey": null - }, - { - "kind": "LinkedField", - "alias": "originalCommitId", - "name": "originalCommit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v19 - }, + v5, + v21, + v6 + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": "comments(first:100)", + "args": v22, + "concreteType": "PullRequestReviewCommentConnection", + "plural": false, + "selections": [ + v17, { "kind": "LinkedField", "alias": null, - "name": "replyTo", + "name": "nodes", "storageKey": null, "args": null, "concreteType": "PullRequestReviewComment", - "plural": false, + "plural": true, "selections": [ - v6 + v6, + v24, + v25, + v26, + v27, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v6 + ] + }, + v28, + v13 ] - }, - v28, - v29 + } ] - } + }, + v5 ] } ] } ] }, - v29, + { + "kind": "LinkedHandle", + "alias": null, + "name": "reviews", + "args": v14, + "handle": "connection", + "key": "prReviewsContainer_reviews", + "filters": null + }, { "kind": "LinkedField", "alias": null, "name": "commits", "storageKey": null, - "args": v30, + "args": v29, "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v31, + v30, { "kind": "LinkedField", "alias": null, @@ -1181,7 +1160,7 @@ return { "concreteType": "PullRequestCommitEdge", "plural": true, "selections": [ - v32, + v18, { "kind": "LinkedField", "alias": null, @@ -1242,8 +1221,8 @@ return { "args": null, "storageKey": null }, - v33, - v29 + v31, + v13 ] }, v6, @@ -1258,7 +1237,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v30, + "args": v29, "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -1271,7 +1250,7 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v13 + "selections": v32 }, { "kind": "LinkedField", @@ -1325,7 +1304,7 @@ return { "concreteType": "Status", "plural": false, "selections": [ - v11, + v20, { "kind": "LinkedField", "alias": null, @@ -1336,7 +1315,7 @@ return { "plural": true, "selections": [ v6, - v11, + v20, { "kind": "ScalarField", "alias": null, @@ -1373,9 +1352,9 @@ return { } ] }, - v26, - v34, - v24, + v20, + v33, + v25, { "kind": "ScalarField", "alias": null, @@ -1406,12 +1385,12 @@ return { { "kind": "InlineFragment", "type": "Bot", - "selections": v35 + "selections": v34 }, { "kind": "InlineFragment", "type": "User", - "selections": v35 + "selections": v34 } ] }, @@ -1443,11 +1422,11 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v36, + "args": v35, "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v31, + v30, { "kind": "LinkedField", "alias": null, @@ -1457,7 +1436,7 @@ return { "concreteType": "PullRequestTimelineItemEdge", "plural": true, "selections": [ - v32, + v18, { "kind": "LinkedField", "alias": null, @@ -1489,7 +1468,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v37 + "selections": v36 }, { "kind": "LinkedField", @@ -1527,9 +1506,9 @@ return { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v26, - v34, - v29, + v11, + v33, + v13, { "kind": "ScalarField", "alias": "prState", @@ -1543,9 +1522,9 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v26, - v34, - v29, + v11, + v33, + v13, { "kind": "ScalarField", "alias": "issueState", @@ -1563,13 +1542,13 @@ return { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v38, + v37, { "kind": "LinkedField", "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v14, + "args": v22, "concreteType": "CommitCommentConnection", "plural": false, "selections": [ @@ -1600,12 +1579,12 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v37 + "selections": v36 }, - v38, - v24, - v28, + v37, v25, + v28, + v26, v27 ] } @@ -1619,7 +1598,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v39, + v38, { "kind": "LinkedField", "alias": null, @@ -1647,8 +1626,8 @@ return { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v39, v38, + v37, { "kind": "ScalarField", "alias": null, @@ -1663,10 +1642,10 @@ return { "kind": "InlineFragment", "type": "IssueComment", "selections": [ - v23, v24, + v25, v28, - v29 + v13 ] }, { @@ -1683,7 +1662,7 @@ return { "plural": false, "selections": [ v7, - v40, + v39, v21 ] }, @@ -1698,7 +1677,7 @@ return { "selections": [ v7, v21, - v40 + v39 ] }, { @@ -1708,7 +1687,7 @@ return { "args": null, "storageKey": null }, - v33, + v31, { "kind": "ScalarField", "alias": null, @@ -1742,7 +1721,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v36, + "args": v35, "handle": "connection", "key": "prTimelineContainer_timeline", "filters": null @@ -1771,7 +1750,7 @@ return { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v13 + "selections": v32 } ] } @@ -1784,5 +1763,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'f928e12221da7ed9e70170512e98cbb2'; +(node/*: any*/).hash = '443d5e2812d4ab3aca1a33b1e95e1d14'; module.exports = node; diff --git a/lib/views/__generated__/prDetailView_pullRequest.graphql.js b/lib/views/__generated__/prDetailView_pullRequest.graphql.js index e98a5c4a5f..331dbe1566 100644 --- a/lib/views/__generated__/prDetailView_pullRequest.graphql.js +++ b/lib/views/__generated__/prDetailView_pullRequest.graphql.js @@ -9,9 +9,9 @@ /*:: import type { ConcreteFragment } from 'relay-runtime'; type prCommitsView_pullRequest$ref = any; +type prReviewsContainer_pullRequest$ref = any; type prStatusesView_pullRequest$ref = any; type prTimelineController_pullRequest$ref = any; -export type PullRequestReviewState = "APPROVED" | "CHANGES_REQUESTED" | "COMMENTED" | "DISMISSED" | "PENDING" | "%future added value"; export type PullRequestState = "CLOSED" | "MERGED" | "OPEN" | "%future added value"; export type ReactionContent = "CONFUSED" | "HEART" | "HOORAY" | "LAUGH" | "THUMBS_DOWN" | "THUMBS_UP" | "%future added value"; import type { FragmentReference } from "relay-runtime"; @@ -20,67 +20,6 @@ export type prDetailView_pullRequest = {| +id?: string, +isCrossRepository: boolean, +changedFiles: number, - +comments: {| - +totalCount: number - |}, - +reviews: ?{| - +pageInfo: {| - +hasNextPage: boolean, - +endCursor: ?string, - |}, - +nodes: ?$ReadOnlyArray, - |}, - |}>, - |}, +countedCommits: {| +totalCount: number |}, @@ -103,21 +42,14 @@ export type prDetailView_pullRequest = {| |}, |}>, +__typename: "PullRequest", - +$fragmentRefs: prCommitsView_pullRequest$ref & prStatusesView_pullRequest$ref & prTimelineController_pullRequest$ref, + +$fragmentRefs: prReviewsContainer_pullRequest$ref & prCommitsView_pullRequest$ref & prStatusesView_pullRequest$ref & prTimelineController_pullRequest$ref, +$refType: prDetailView_pullRequest$ref, |}; */ const node/*: ConcreteFragment*/ = (function(){ -var v0 = { - "kind": "ScalarField", - "alias": null, - "name": "state", - "args": null, - "storageKey": null -}, -v1 = [ +var v0 = [ { "kind": "ScalarField", "alias": null, @@ -126,111 +58,15 @@ v1 = [ "storageKey": null } ], -v2 = [ - { - "kind": "Literal", - "name": "first", - "value": 100, - "type": "Int" - } -], -v3 = { - "kind": "LinkedField", - "alias": null, - "name": "pageInfo", - "storageKey": null, - "args": null, - "concreteType": "PageInfo", - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "hasNextPage", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "endCursor", - "args": null, - "storageKey": null - } - ] -}, -v4 = { - "kind": "ScalarField", - "alias": null, - "name": "id", - "args": null, - "storageKey": null -}, -v5 = { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null -}, -v6 = [ - { - "kind": "ScalarField", - "alias": null, - "name": "oid", - "args": null, - "storageKey": null - } -], -v7 = { - "kind": "ScalarField", - "alias": null, - "name": "login", - "args": null, - "storageKey": null -}, -v8 = { - "kind": "LinkedField", - "alias": "login", - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v7 - ] -}, -v9 = { - "kind": "ScalarField", - "alias": null, - "name": "avatarUrl", - "args": null, - "storageKey": null -}, -v10 = { - "kind": "ScalarField", - "alias": null, - "name": "bodyHTML", - "args": null, - "storageKey": null -}, -v11 = { - "kind": "ScalarField", - "alias": null, - "name": "number", - "args": null, - "storageKey": null -}, -v12 = { +v1 = { "kind": "ScalarField", "alias": null, "name": "url", "args": null, "storageKey": null }, -v13 = [ - v12 +v2 = [ + v1 ]; return { "kind": "Fragment", @@ -273,221 +109,64 @@ return { "name": "commentCursor", "type": "String", "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCount", + "type": "Int", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCursor", + "type": "String", + "defaultValue": null } ], "selections": [ - v0, { "kind": "ScalarField", "alias": null, - "name": "__typename", + "name": "number", "args": null, "storageKey": null }, { "kind": "ScalarField", "alias": null, - "name": "isCrossRepository", + "name": "__typename", "args": null, "storageKey": null }, { "kind": "ScalarField", "alias": null, - "name": "changedFiles", + "name": "isCrossRepository", "args": null, "storageKey": null }, { - "kind": "LinkedField", + "kind": "ScalarField", "alias": null, - "name": "comments", - "storageKey": null, + "name": "changedFiles", "args": null, - "concreteType": "IssueCommentConnection", - "plural": false, - "selections": v1 + "storageKey": null }, { - "kind": "LinkedField", - "alias": null, - "name": "reviews", - "storageKey": "reviews(first:100)", - "args": v2, - "concreteType": "PullRequestReviewConnection", - "plural": false, - "selections": [ - v3, + "kind": "FragmentSpread", + "name": "prReviewsContainer_pullRequest", + "args": [ { - "kind": "LinkedField", - "alias": null, - "name": "nodes", - "storageKey": null, - "args": null, - "concreteType": "PullRequestReview", - "plural": true, - "selections": [ - v4, - v5, - { - "kind": "LinkedField", - "alias": "commitId", - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v6 - }, - v0, - { - "kind": "ScalarField", - "alias": null, - "name": "submittedAt", - "args": null, - "storageKey": null - }, - v8, - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v9 - ] - }, - { - "kind": "LinkedField", - "alias": null, - "name": "comments", - "storageKey": "comments(first:100)", - "args": v2, - "concreteType": "PullRequestReviewCommentConnection", - "plural": false, - "selections": [ - v3, - { - "kind": "LinkedField", - "alias": null, - "name": "nodes", - "storageKey": null, - "args": null, - "concreteType": "PullRequestReviewComment", - "plural": true, - "selections": [ - { - "kind": "LinkedField", - "alias": "commitSha", - "name": "commit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v6 - }, - v4, - { - "kind": "ScalarField", - "alias": null, - "name": "databaseId", - "args": null, - "storageKey": null - }, - v8, - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v9, - v7 - ] - }, - v5, - v10, - { - "kind": "ScalarField", - "alias": null, - "name": "path", - "args": null, - "storageKey": null - }, - { - "kind": "LinkedField", - "alias": "pullRequestId", - "name": "pullRequest", - "storageKey": null, - "args": null, - "concreteType": "PullRequest", - "plural": false, - "selections": [ - v11 - ] - }, - { - "kind": "ScalarField", - "alias": null, - "name": "diffHunk", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "position", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "originalPosition", - "args": null, - "storageKey": null - }, - { - "kind": "LinkedField", - "alias": "originalCommitId", - "name": "originalCommit", - "storageKey": null, - "args": null, - "concreteType": "Commit", - "plural": false, - "selections": v6 - }, - { - "kind": "LinkedField", - "alias": null, - "name": "replyTo", - "storageKey": null, - "args": null, - "concreteType": "PullRequestReviewComment", - "plural": false, - "selections": [ - v4 - ] - }, - { - "kind": "ScalarField", - "alias": null, - "name": "createdAt", - "args": null, - "storageKey": null - }, - v12 - ] - } - ] - } - ] + "kind": "Variable", + "name": "reviewCount", + "variableName": "reviewCount", + "type": null + }, + { + "kind": "Variable", + "name": "reviewCursor", + "variableName": "reviewCursor", + "type": null } ] }, @@ -517,15 +196,27 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v1 + "selections": v0 }, { "kind": "FragmentSpread", "name": "prStatusesView_pullRequest", "args": null }, - v4, - v11, + { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null + }, { "kind": "ScalarField", "alias": null, @@ -533,7 +224,13 @@ return { "args": null, "storageKey": null }, - v10, + { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null + }, { "kind": "ScalarField", "alias": null, @@ -557,17 +254,29 @@ return { "concreteType": null, "plural": false, "selections": [ - v7, - v9, + { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null + }, { "kind": "InlineFragment", "type": "Bot", - "selections": v13 + "selections": v2 }, { "kind": "InlineFragment", "type": "User", - "selections": v13 + "selections": v2 } ] }, @@ -589,7 +298,7 @@ return { } ] }, - v12, + v1, { "kind": "LinkedField", "alias": null, @@ -614,7 +323,7 @@ return { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v1 + "selections": v0 } ] } @@ -622,5 +331,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'ab20ccdc3ccc10843176a48cd07d03ae'; +(node/*: any*/).hash = 'e4e973dd4a21d8051d6908ff6844245a'; module.exports = node; diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 7768d9d5e1..c82b530b19 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -15,7 +15,7 @@ import Commands, {Command} from '../atom/commands'; import FilePatchHeaderView from './file-patch-header-view'; import FilePatchMetaView from './file-patch-meta-view'; import HunkHeaderView from './hunk-header-view'; -import PullRequestCommentsView from './pr-comments-view'; +import PullRequestsReviewsContainer from '../containers/pr-reviews-container'; import RefHolder from '../models/ref-holder'; import ChangedFileItem from '../items/changed-file-item'; import CommitDetailItem from '../items/commit-detail-item'; @@ -281,7 +281,7 @@ export default class MultiFilePatchView extends React.Component { /> )} - + {this.props.multiFilePatch.getFilePatches().map(this.renderFilePatchDecorations)} {this.renderLineDecorations( diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index a1beeb9d72..c58233785d 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -16,7 +16,7 @@ export default class PullRequestCommentsView extends React.Component { const commentsByRootCommentId = new Map(); - this.props.reviews.nodes.forEach(review => { + this.props.reviews.edges.forEach(review => { review.comments.nodes.forEach(comment => { if (!comment.replyTo) { commentsByRootCommentId.set(comment.id, [comment]); diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 44560797bf..e87e40600a 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -351,6 +351,8 @@ export class BarePullRequestDetailView extends React.Component { timelineCursor: null, commitCount: 100, commitCursor: null, + reviewCount: 3, + reviewCursor: null, }, null, () => { this.setState({refreshing: false}); }, {force: true}); @@ -376,7 +378,9 @@ export default createRefetchContainer(BarePullRequestDetailView, { commitCount: {type: "Int!"}, commitCursor: {type: "String"}, commentCount: {type: "Int!"}, - commentCursor: {type: "String"} + commentCursor: {type: "String"}, + reviewCount: {type: "Int"}, + reviewCursor: {type: "String"} ) { __typename @@ -387,67 +391,9 @@ export default createRefetchContainer(BarePullRequestDetailView, { ... on PullRequest { isCrossRepository changedFiles - comments { - totalCount - } - reviews(first: 100) { - pageInfo { - hasNextPage - endCursor - } - nodes { - id - body - commitId: commit { - oid - } - state - submittedAt - login: author { - login - } - author { - avatarUrl - } - comments(first: 100) { - pageInfo { - hasNextPage - endCursor - } - nodes { - id - pullRequestId: pullRequest { - number - } - databaseId - login: author { - login - } - author { - avatarUrl - login - } - body - bodyHTML - path - commitSha: commit { - oid - } - diffHunk - position - originalPosition - originalCommitId: originalCommit { - oid - } - replyTo { - id - } - createdAt - url - } - } - } - } + + ...prReviewsContainer_pullRequest @arguments(reviewCount: $reviewCount, reviewCursor: $reviewCursor) + ...prCommitsView_pullRequest @arguments(commitCount: $commitCount, commitCursor: $commitCursor) countedCommits: commits { totalCount @@ -482,7 +428,9 @@ export default createRefetchContainer(BarePullRequestDetailView, { $commitCount: Int!, $commitCursor: String, $commentCount: Int!, - $commentCursor: String + $commentCursor: String, + $reviewCount: Int, + $reviewCursor: String ) { repository:node(id: $repoId) { ...prDetailView_repository @arguments( @@ -498,7 +446,9 @@ export default createRefetchContainer(BarePullRequestDetailView, { commitCount: $commitCount, commitCursor: $commitCursor, commentCount: $commentCount, - commentCursor: $commentCursor + commentCursor: $commentCursor, + reviewCount: $reviewCount, + reviewCursor: $reviewCursor ) } } From 498d5ab261b4ce13e1fa6af9903c0bee96dc92db Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 27 Dec 2018 16:57:51 -0800 Subject: [PATCH 1629/4053] Got reviews rendering again Only redner PullRequestReviewsContainer if itemType is IssueishDetailItem Co-Authored-By: Tilde Ann Thurium --- .../issueishDetailContainerQuery.graphql.js | 80 ++++++++++-------- .../prReviewsContainerQuery.graphql.js | 4 +- .../prReviewsContainer_pullRequest.graphql.js | 4 +- lib/containers/pr-reviews-container.js | 81 +++++++++---------- ...eishDetailController_repository.graphql.js | 26 +++++- lib/controllers/issueish-detail-controller.js | 4 + .../prDetailViewRefetchQuery.graphql.js | 4 +- lib/views/multi-file-patch-view.js | 11 ++- lib/views/pr-comments-view.js | 20 ++++- lib/views/pr-detail-view.js | 3 +- 10 files changed, 153 insertions(+), 84 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index ea0f609242..19a08b76f5 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 615fe759e1fcacb3ed5561310f21f8ed + * @relayHash e6b8ab56b47c1e91cd699080eeb040fa */ /* eslint-disable */ @@ -44,6 +44,8 @@ query issueishDetailContainerQuery( $timelineCursor: String $commitCount: Int! $commitCursor: String + $reviewCount: Int + $reviewCursor: String ) { repository(owner: $repoOwner, name: $repoName) { ...issueishDetailController_repository_y3nHF @@ -88,7 +90,7 @@ fragment issueishDetailController_repository_y3nHF on Repository { sshUrl id } - ...prDetailView_pullRequest_1Etigl + ...prDetailView_pullRequest_2qM2KL } ... on Node { id @@ -153,14 +155,14 @@ fragment issueDetailView_issue_4cAEh0 on Issue { } } -fragment prDetailView_pullRequest_1Etigl on PullRequest { +fragment prDetailView_pullRequest_2qM2KL on PullRequest { __typename ... on Node { id } isCrossRepository changedFiles - ...prReviewsContainer_pullRequest_3CUNoW + ...prReviewsContainer_pullRequest_2zzc96 ...prCommitsView_pullRequest_38TpXw countedCommits: commits { totalCount @@ -200,9 +202,9 @@ fragment prDetailView_pullRequest_1Etigl on PullRequest { } } -fragment prReviewsContainer_pullRequest_3CUNoW on PullRequest { +fragment prReviewsContainer_pullRequest_2zzc96 on PullRequest { url - reviews { + reviews(first: $reviewCount, after: $reviewCursor) { pageInfo { hasNextPage endCursor @@ -1103,7 +1105,21 @@ v34 = [ "type": "Int" } ], -v35 = { +v35 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "reviewCursor", + "type": "String" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "reviewCount", + "type": "Int" + } +], +v36 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -1116,7 +1132,7 @@ v35 = { v18 ] }, -v36 = [ +v37 = [ { "kind": "ScalarField", "alias": null, @@ -1126,7 +1142,7 @@ v36 = [ }, v2 ], -v37 = [ +v38 = [ { "kind": "Literal", "name": "first", @@ -1134,21 +1150,21 @@ v37 = [ "type": "Int" } ], -v38 = { +v39 = { "kind": "ScalarField", "alias": null, "name": "path", "args": null, "storageKey": null }, -v39 = { +v40 = { "kind": "ScalarField", "alias": null, "name": "position", "args": null, "storageKey": null }, -v40 = { +v41 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -1156,9 +1172,9 @@ v40 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v36 + "selections": v37 }, -v41 = { +v42 = { "kind": "LinkedField", "alias": null, "name": "actor", @@ -1173,7 +1189,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_1Etigl\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_1Etigl on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_3CUNoW\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_3CUNoW on PullRequest {\n url\n reviews {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int\n $reviewCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_2zzc96\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1507,11 +1523,11 @@ return { "alias": null, "name": "reviews", "storageKey": null, - "args": null, + "args": v35, "concreteType": "PullRequestReviewConnection", "plural": false, "selections": [ - v35, + v36, { "kind": "LinkedField", "alias": null, @@ -1547,7 +1563,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v36 + "selections": v37 }, v11, { @@ -1586,11 +1602,11 @@ return { "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v37, + "args": v38, "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ - v35, + v36, { "kind": "LinkedField", "alias": null, @@ -1603,8 +1619,8 @@ return { v2, v26, v12, - v38, v39, + v40, { "kind": "LinkedField", "alias": null, @@ -1634,9 +1650,9 @@ return { "kind": "LinkedHandle", "alias": null, "name": "reviews", - "args": null, + "args": v35, "handle": "connection", - "key": "prReviewsContainer_reviews", + "key": "PrReviewsContainer_reviews", "filters": null }, v10, @@ -1819,13 +1835,13 @@ return { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v40, + v41, { "kind": "LinkedField", "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v37, + "args": v38, "concreteType": "CommitCommentConnection", "plural": false, "selections": [ @@ -1858,11 +1874,11 @@ return { "plural": false, "selections": v23 }, - v40, + v41, v12, v27, - v38, - v39 + v39, + v40 ] } ] @@ -1875,7 +1891,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v41, + v42, { "kind": "LinkedField", "alias": null, @@ -1884,7 +1900,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v36 + "selections": v37 }, { "kind": "LinkedField", @@ -1894,7 +1910,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v36 + "selections": v37 }, v27 ] @@ -1903,8 +1919,8 @@ return { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ + v42, v41, - v40, { "kind": "ScalarField", "alias": null, diff --git a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js index 8f1deb1224..853f2b03ed 100644 --- a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash dc9158ed713eb5e8c4c725dd4bfc9e00 + * @relayHash 78ed01f06235641c5292e2ee9fab3f03 */ /* eslint-disable */ @@ -487,7 +487,7 @@ return { "name": "reviews", "args": v5, "handle": "connection", - "key": "prReviewsContainer_reviews", + "key": "PrReviewsContainer_reviews", "filters": null } ] diff --git a/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js index 192298ed34..502c34a9b2 100644 --- a/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js +++ b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js @@ -152,7 +152,7 @@ return { { "kind": "LinkedField", "alias": "reviews", - "name": "__prReviewsContainer_reviews_connection", + "name": "__PrReviewsContainer_reviews_connection", "storageKey": null, "args": null, "concreteType": "PullRequestReviewConnection", @@ -350,5 +350,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'd22654576b18d8a49cff9628b69d7b17'; +(node/*: any*/).hash = 'd9ac6d62742b887f30bdeac72bbafefb'; module.exports = node; diff --git a/lib/containers/pr-reviews-container.js b/lib/containers/pr-reviews-container.js index d4f6992ee6..7897d2544e 100644 --- a/lib/containers/pr-reviews-container.js +++ b/lib/containers/pr-reviews-container.js @@ -9,50 +9,51 @@ export default createPaginationContainer(PullRequestReviewsView, { reviewCount: {type: "Int"}, reviewCursor: {type: "String"} ) { - ... on PullRequest { - url - reviews(first: $reviewCount, after: $reviewCursor) @connection(key: "prReviewsContainer_reviews") { - pageInfo { - hasNextPage - endCursor - } + url + reviews( + first: $reviewCount, + after: $reviewCursor + ) @connection(key: "PrReviewsContainer_reviews") { + pageInfo { + hasNextPage + endCursor + } - edges { - cursor - node { - id - body - commitId: commit { - oid - } - state - submittedAt - login: author { - login - } - author { - avatarUrl + edges { + cursor + node { + id + body + commitId: commit { + oid + } + state + submittedAt + login: author { + login + } + author { + avatarUrl + } + comments(first: 100) { + pageInfo { + hasNextPage + endCursor } - comments(first: 100) { - pageInfo { - hasNextPage - endCursor + nodes { + id + author { + avatarUrl + login } - nodes { + bodyHTML + path + position + replyTo { id - author { - avatarUrl - login - } - bodyHTML - path - position - replyTo { - id - } - createdAt - url } + createdAt + url } } } @@ -63,7 +64,6 @@ export default createPaginationContainer(PullRequestReviewsView, { }, { direction: 'forward', getConnectionFromProps(props) { - console.log(props.pullRequest); return props.pullRequest.reviews; }, getFragmentVariables(prevVars, totalCount) { @@ -73,7 +73,6 @@ export default createPaginationContainer(PullRequestReviewsView, { }; }, getVariables(props, {count, cursor}, fragmentVariables) { - console.log(props.pullRequest); return { url: props.pullRequest.url, reviewCount: count, diff --git a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js index 5f17d1be10..3c9b629202 100644 --- a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js +++ b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js @@ -180,6 +180,18 @@ return { "name": "commentCursor", "type": "String", "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCursor", + "type": "String", + "defaultValue": null } ], "selections": [ @@ -293,6 +305,18 @@ return { }, v6, v7, + { + "kind": "Variable", + "name": "reviewCount", + "variableName": "reviewCount", + "type": null + }, + { + "kind": "Variable", + "name": "reviewCursor", + "variableName": "reviewCursor", + "type": null + }, v8, v9 ] @@ -305,5 +329,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '0a288c1ab2398af40de27baf5db2aacb'; +(node/*: any*/).hash = 'f24ee4210befb63664396cf05d2afa79'; module.exports = node; diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index ae8bc47bda..75ef71c32d 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -299,6 +299,8 @@ export default createFragmentContainer(BareIssueishDetailController, { commitCursor: {type: "String"}, commentCount: {type: "Int!"}, commentCursor: {type: "String"}, + reviewCount: {type: "Int!"}, + reviewCursor: {type: "String"}, ) { ...issueDetailView_repository ...prDetailView_repository @@ -340,6 +342,8 @@ export default createFragmentContainer(BareIssueishDetailController, { commitCursor: $commitCursor, commentCount: $commentCount, commentCursor: $commentCursor, + reviewCount: $reviewCount, + reviewCursor: $reviewCursor, ) } } diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index c82a1a5e27..538e2cf4d6 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash b8344287c886583940c118565aa06547 + * @relayHash e7fd1c7c963c648913576e5f8a5f07d4 */ /* eslint-disable */ @@ -1138,7 +1138,7 @@ return { "name": "reviews", "args": v14, "handle": "connection", - "key": "prReviewsContainer_reviews", + "key": "PrReviewsContainer_reviews", "filters": null }, { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index c82b530b19..7296aee04d 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -281,7 +281,8 @@ export default class MultiFilePatchView extends React.Component { /> )} - + {this.renderPullRequestReviews()} + {this.props.multiFilePatch.getFilePatches().map(this.renderFilePatchDecorations)} {this.renderLineDecorations( @@ -310,6 +311,14 @@ export default class MultiFilePatchView extends React.Component { ); } + renderPullRequestReviews() { + if (this.props.itemType === IssueishDetailItem) { + return ; + } else { + return null; + } + } + renderFilePatchDecorations = filePatch => { return ( diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index c58233785d..7ebf7294fc 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Point, Range} from 'atom'; +import {RelayConnectionPropType} from '../prop-types'; import {toNativePathSep} from '../helpers'; import Marker from '../atom/marker'; @@ -9,14 +10,29 @@ import GithubDotcomMarkdown from './github-dotcom-markdown'; import Timeago from './timeago'; export default class PullRequestCommentsView extends React.Component { + static propTypes = { + relay: PropTypes.shape({ + hasMore: PropTypes.func.isRequired, + loadMore: PropTypes.func.isRequired, + isLoading: PropTypes.func.isRequired, + }).isRequired, + pullRequest: PropTypes.shape({ + reviews: RelayConnectionPropType( + PropTypes.object, + ), + }), + multiFilePatch: PropTypes.object.isRequired, + } + render() { - if (!this.props.reviews) { + if (!this.props.pullRequest || !this.props.pullRequest.reviews) { return null; } const commentsByRootCommentId = new Map(); - this.props.reviews.edges.forEach(review => { + this.props.pullRequest.reviews.edges.forEach(({node}) => { + const review = node; review.comments.nodes.forEach(comment => { if (!comment.replyTo) { commentsByRootCommentId.set(comment.id, [comment]); diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index e87e40600a..1abd62b92d 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -209,8 +209,9 @@ export class BarePullRequestDetailView extends React.Component { destroy={this.props.destroy} shouldRefetch={this.state.refreshing} - reviews={this.props.pullRequest.reviews} switchToIssueish={this.props.switchToIssueish} + + pullRequest={this.props.pullRequest} /> From 5ac1f5566778a0adb1215a86d935989099504617 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 27 Dec 2018 17:51:41 -0800 Subject: [PATCH 1630/4053] Fetch all reviews --- .../issueishDetailContainerQuery.graphql.js | 59 ++++--- .../prReviewsContainerQuery.graphql.js | 29 +-- .../prReviewsContainer_pullRequest.graphql.js | 29 +-- lib/containers/issueish-detail-container.js | 2 +- lib/containers/pr-reviews-container.js | 1 + .../prDetailViewRefetchQuery.graphql.js | 167 +++++++++--------- lib/views/pr-comments-view.js | 49 ++++- 7 files changed, 192 insertions(+), 144 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 19a08b76f5..2005783ec7 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash e6b8ab56b47c1e91cd699080eeb040fa + * @relayHash eacb91752813910cc6e618d90f821447 */ /* eslint-disable */ @@ -249,6 +249,7 @@ fragment prReviewsContainer_pullRequest_2zzc96 on PullRequest { id } } + body bodyHTML path position @@ -1132,7 +1133,14 @@ v36 = { v18 ] }, -v37 = [ +v37 = { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null +}, +v38 = [ { "kind": "ScalarField", "alias": null, @@ -1142,7 +1150,7 @@ v37 = [ }, v2 ], -v38 = [ +v39 = [ { "kind": "Literal", "name": "first", @@ -1150,21 +1158,21 @@ v38 = [ "type": "Int" } ], -v39 = { +v40 = { "kind": "ScalarField", "alias": null, "name": "path", "args": null, "storageKey": null }, -v40 = { +v41 = { "kind": "ScalarField", "alias": null, "name": "position", "args": null, "storageKey": null }, -v41 = { +v42 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -1172,9 +1180,9 @@ v41 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": v38 }, -v42 = { +v43 = { "kind": "LinkedField", "alias": null, "name": "actor", @@ -1189,7 +1197,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int\n $reviewCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_2zzc96\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int\n $reviewCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_2zzc96\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1548,13 +1556,7 @@ return { "plural": false, "selections": [ v2, - { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null - }, + v37, { "kind": "LinkedField", "alias": "commitId", @@ -1563,7 +1565,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": v38 }, v11, { @@ -1602,7 +1604,7 @@ return { "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v38, + "args": v39, "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ @@ -1618,9 +1620,10 @@ return { "selections": [ v2, v26, + v37, v12, - v39, v40, + v41, { "kind": "LinkedField", "alias": null, @@ -1835,13 +1838,13 @@ return { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v41, + v42, { "kind": "LinkedField", "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v38, + "args": v39, "concreteType": "CommitCommentConnection", "plural": false, "selections": [ @@ -1874,11 +1877,11 @@ return { "plural": false, "selections": v23 }, - v41, + v42, v12, v27, - v39, - v40 + v40, + v41 ] } ] @@ -1891,7 +1894,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v42, + v43, { "kind": "LinkedField", "alias": null, @@ -1900,7 +1903,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": v38 }, { "kind": "LinkedField", @@ -1910,7 +1913,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": v38 }, v27 ] @@ -1919,8 +1922,8 @@ return { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ + v43, v42, - v41, { "kind": "ScalarField", "alias": null, diff --git a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js index 853f2b03ed..e6ee421cf3 100644 --- a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 78ed01f06235641c5292e2ee9fab3f03 + * @relayHash c79a1d774a35cceb455839a0124e28fc */ /* eslint-disable */ @@ -91,6 +91,7 @@ fragment prReviewsContainer_pullRequest_2zzc96 on PullRequest { id } } + body bodyHTML path position @@ -200,11 +201,18 @@ v6 = { v7 = { "kind": "ScalarField", "alias": null, - "name": "login", + "name": "body", "args": null, "storageKey": null }, v8 = { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null +}, +v9 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", @@ -216,7 +224,7 @@ return { "operationKind": "query", "name": "prReviewsContainerQuery", "id": null, - "text": "query prReviewsContainerQuery(\n $reviewCount: Int\n $reviewCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_2zzc96\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n", + "text": "query prReviewsContainerQuery(\n $reviewCount: Int\n $reviewCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_2zzc96\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -319,13 +327,7 @@ return { "plural": false, "selections": [ v3, - { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null - }, + v7, { "kind": "LinkedField", "alias": "commitId", @@ -369,7 +371,7 @@ return { "plural": false, "selections": [ v2, - v7, + v8, v3 ] }, @@ -383,7 +385,7 @@ return { "plural": false, "selections": [ v2, - v8, + v9, v3 ] }, @@ -424,11 +426,12 @@ return { "plural": false, "selections": [ v2, + v9, v8, - v7, v3 ] }, + v7, { "kind": "ScalarField", "alias": null, diff --git a/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js index 502c34a9b2..cd27865199 100644 --- a/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js +++ b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js @@ -45,6 +45,7 @@ export type prReviewsContainer_pullRequest = {| +avatarUrl: any, +login: string, |}, + +body: string, +bodyHTML: any, +path: string, +position: ?number, @@ -106,11 +107,18 @@ v2 = { v3 = { "kind": "ScalarField", "alias": null, - "name": "login", + "name": "body", "args": null, "storageKey": null }, v4 = { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null +}, +v5 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", @@ -185,13 +193,7 @@ return { "plural": false, "selections": [ v2, - { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null - }, + v3, { "kind": "LinkedField", "alias": "commitId", @@ -233,7 +235,7 @@ return { "concreteType": null, "plural": false, "selections": [ - v3 + v4 ] }, { @@ -245,7 +247,7 @@ return { "concreteType": null, "plural": false, "selections": [ - v4 + v5 ] }, { @@ -284,10 +286,11 @@ return { "concreteType": null, "plural": false, "selections": [ - v4, - v3 + v5, + v4 ] }, + v3, { "kind": "ScalarField", "alias": null, @@ -350,5 +353,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'd9ac6d62742b887f30bdeac72bbafefb'; +(node/*: any*/).hash = 'd234d1f47b1a977b18174045b21cf0f0'; module.exports = node; diff --git a/lib/containers/issueish-detail-container.js b/lib/containers/issueish-detail-container.js index ecb33175e9..33e2404fbe 100644 --- a/lib/containers/issueish-detail-container.js +++ b/lib/containers/issueish-detail-container.js @@ -152,7 +152,7 @@ export default class IssueishDetailContainer extends React.Component { commitCursor: null, commentCount: 100, commentCursor: null, - reviewCount: 3, + reviewCount: 1, reviewCursor: null, }; diff --git a/lib/containers/pr-reviews-container.js b/lib/containers/pr-reviews-container.js index 7897d2544e..f90e50e1c2 100644 --- a/lib/containers/pr-reviews-container.js +++ b/lib/containers/pr-reviews-container.js @@ -46,6 +46,7 @@ export default createPaginationContainer(PullRequestReviewsView, { avatarUrl login } + body bodyHTML path position diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index 538e2cf4d6..fe380438d1 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash e7fd1c7c963c648913576e5f8a5f07d4 + * @relayHash 8b5005b08f8393c1b2d22577e9530360 */ /* eslint-disable */ @@ -165,6 +165,7 @@ fragment prReviewsContainer_pullRequest_2zzc96 on PullRequest { id } } + body bodyHTML path position @@ -680,7 +681,14 @@ v18 = { "args": null, "storageKey": null }, -v19 = [ +v19 = { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null +}, +v20 = [ { "kind": "ScalarField", "alias": null, @@ -690,21 +698,21 @@ v19 = [ }, v6 ], -v20 = { +v21 = { "kind": "ScalarField", "alias": null, "name": "state", "args": null, "storageKey": null }, -v21 = { +v22 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", "args": null, "storageKey": null }, -v22 = [ +v23 = [ { "kind": "Literal", "name": "first", @@ -712,13 +720,13 @@ v22 = [ "type": "Int" } ], -v23 = [ +v24 = [ v5, - v21, + v22, v8, v6 ], -v24 = { +v25 = { "kind": "LinkedField", "alias": null, "name": "author", @@ -726,37 +734,37 @@ v24 = { "args": null, "concreteType": null, "plural": false, - "selections": v23 + "selections": v24 }, -v25 = { +v26 = { "kind": "ScalarField", "alias": null, "name": "bodyHTML", "args": null, "storageKey": null }, -v26 = { +v27 = { "kind": "ScalarField", "alias": null, "name": "path", "args": null, "storageKey": null }, -v27 = { +v28 = { "kind": "ScalarField", "alias": null, "name": "position", "args": null, "storageKey": null }, -v28 = { +v29 = { "kind": "ScalarField", "alias": null, "name": "createdAt", "args": null, "storageKey": null }, -v29 = [ +v30 = [ { "kind": "Variable", "name": "after", @@ -770,7 +778,7 @@ v29 = [ "type": "Int" } ], -v30 = { +v31 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -783,14 +791,14 @@ v30 = { v15 ] }, -v31 = { +v32 = { "kind": "ScalarField", "alias": "sha", "name": "oid", "args": null, "storageKey": null }, -v32 = [ +v33 = [ { "kind": "ScalarField", "alias": null, @@ -799,17 +807,17 @@ v32 = [ "storageKey": null } ], -v33 = { +v34 = { "kind": "ScalarField", "alias": null, "name": "title", "args": null, "storageKey": null }, -v34 = [ +v35 = [ v13 ], -v35 = [ +v36 = [ { "kind": "Variable", "name": "after", @@ -823,13 +831,13 @@ v35 = [ "type": "Int" } ], -v36 = [ +v37 = [ v5, v8, - v21, + v22, v6 ], -v37 = { +v38 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -837,9 +845,9 @@ v37 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v19 + "selections": v20 }, -v38 = { +v39 = { "kind": "LinkedField", "alias": null, "name": "actor", @@ -847,9 +855,9 @@ v38 = { "args": null, "concreteType": null, "plural": false, - "selections": v23 + "selections": v24 }, -v39 = { +v40 = { "kind": "LinkedField", "alias": null, "name": "user", @@ -867,7 +875,7 @@ return { "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int\n $reviewCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_2zzc96\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int\n $reviewCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_2zzc96\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1034,13 +1042,7 @@ return { "plural": false, "selections": [ v6, - { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null - }, + v19, { "kind": "LinkedField", "alias": "commitId", @@ -1049,9 +1051,9 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v19 + "selections": v20 }, - v20, + v21, { "kind": "ScalarField", "alias": null, @@ -1079,7 +1081,7 @@ return { "plural": false, "selections": [ v5, - v21, + v22, v6 ] }, @@ -1088,7 +1090,7 @@ return { "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v22, + "args": v23, "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ @@ -1103,10 +1105,11 @@ return { "plural": true, "selections": [ v6, - v24, v25, + v19, v26, v27, + v28, { "kind": "LinkedField", "alias": null, @@ -1119,7 +1122,7 @@ return { v6 ] }, - v28, + v29, v13 ] } @@ -1146,11 +1149,11 @@ return { "alias": null, "name": "commits", "storageKey": null, - "args": v29, + "args": v30, "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v30, + v31, { "kind": "LinkedField", "alias": null, @@ -1189,7 +1192,7 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v21, + v22, v7, { "kind": "ScalarField", @@ -1221,7 +1224,7 @@ return { "args": null, "storageKey": null }, - v31, + v32, v13 ] }, @@ -1237,7 +1240,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v29, + "args": v30, "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -1250,7 +1253,7 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v32 + "selections": v33 }, { "kind": "LinkedField", @@ -1304,7 +1307,7 @@ return { "concreteType": "Status", "plural": false, "selections": [ - v20, + v21, { "kind": "LinkedField", "alias": null, @@ -1315,7 +1318,7 @@ return { "plural": true, "selections": [ v6, - v20, + v21, { "kind": "ScalarField", "alias": null, @@ -1352,9 +1355,9 @@ return { } ] }, - v20, - v33, - v25, + v21, + v34, + v26, { "kind": "ScalarField", "alias": null, @@ -1380,17 +1383,17 @@ return { "selections": [ v5, v8, - v21, + v22, v6, { "kind": "InlineFragment", "type": "Bot", - "selections": v34 + "selections": v35 }, { "kind": "InlineFragment", "type": "User", - "selections": v34 + "selections": v35 } ] }, @@ -1422,11 +1425,11 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v35, + "args": v36, "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v30, + v31, { "kind": "LinkedField", "alias": null, @@ -1468,7 +1471,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v36 + "selections": v37 }, { "kind": "LinkedField", @@ -1507,7 +1510,7 @@ return { "type": "PullRequest", "selections": [ v11, - v33, + v34, v13, { "kind": "ScalarField", @@ -1523,7 +1526,7 @@ return { "type": "Issue", "selections": [ v11, - v33, + v34, v13, { "kind": "ScalarField", @@ -1542,13 +1545,13 @@ return { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v37, + v38, { "kind": "LinkedField", "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v22, + "args": v23, "concreteType": "CommitCommentConnection", "plural": false, "selections": [ @@ -1579,13 +1582,13 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v36 + "selections": v37 }, - v37, - v25, - v28, + v38, v26, - v27 + v29, + v27, + v28 ] } ] @@ -1598,7 +1601,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v38, + v39, { "kind": "LinkedField", "alias": null, @@ -1607,7 +1610,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v19 + "selections": v20 }, { "kind": "LinkedField", @@ -1617,17 +1620,17 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v19 + "selections": v20 }, - v28 + v29 ] }, { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ + v39, v38, - v37, { "kind": "ScalarField", "alias": null, @@ -1635,16 +1638,16 @@ return { "args": null, "storageKey": null }, - v28 + v29 ] }, { "kind": "InlineFragment", "type": "IssueComment", "selections": [ - v24, v25, - v28, + v26, + v29, v13 ] }, @@ -1662,8 +1665,8 @@ return { "plural": false, "selections": [ v7, - v39, - v21 + v40, + v22 ] }, { @@ -1676,8 +1679,8 @@ return { "plural": false, "selections": [ v7, - v21, - v39 + v22, + v40 ] }, { @@ -1687,7 +1690,7 @@ return { "args": null, "storageKey": null }, - v31, + v32, { "kind": "ScalarField", "alias": null, @@ -1721,7 +1724,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v35, + "args": v36, "handle": "connection", "key": "prTimelineContainer_timeline", "filters": null @@ -1750,7 +1753,7 @@ return { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v32 + "selections": v33 } ] } diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index 7ebf7294fc..bd2369f41f 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -24,6 +24,37 @@ export default class PullRequestCommentsView extends React.Component { multiFilePatch: PropTypes.object.isRequired, } + componentDidMount() { + console.log('DID MOUNT'); + this._attemptToLoadMoreReviews(); + } + + _loadMoreReviews = () => { + this.props.relay.loadMore( + 1, // Fetch the next 10 feed items + error => { + this._attemptToLoadMoreReviews(); + if (error) { + console.log(error); + } + }, + ); + } + + _attemptToLoadMoreReviews = () => { + if (!this.props.relay.hasMore()) { + return; + } + + if (this.props.relay.isLoading()) { + setTimeout(() => { + this._loadMoreReviews(); + }, 300); + } else { + this._loadMoreReviews(); + } + } + render() { if (!this.props.pullRequest || !this.props.pullRequest.reviews) { return null; @@ -42,6 +73,7 @@ export default class PullRequestCommentsView extends React.Component { }); }); + console.log('SIZE', commentsByRootCommentId.size, [...commentsByRootCommentId]); return [...commentsByRootCommentId].reverse().map(([rootCommentId, comments]) => { const rootComment = comments[0]; @@ -56,13 +88,16 @@ export default class PullRequestCommentsView extends React.Component { return ( - {comments.map(comment => - , - )} + {comments.map(comment => { + console.log(comment.body); + return ( + + ); + })} ); From 38efd85cf03f8605eedec56de8952db197885fe1 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 27 Dec 2018 17:52:08 -0800 Subject: [PATCH 1631/4053] Hack to get comments to show up in correct order. TODO: fixme --- lib/views/pr-comments-view.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index bd2369f41f..b0b5eebbb3 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -9,6 +9,8 @@ import Decoration from '../atom/decoration'; import GithubDotcomMarkdown from './github-dotcom-markdown'; import Timeago from './timeago'; +let count = 0; + export default class PullRequestCommentsView extends React.Component { static propTypes = { relay: PropTypes.shape({ @@ -86,7 +88,7 @@ export default class PullRequestCommentsView extends React.Component { const point = new Point(row, 0); const range = new Range(point, point); return ( - + {comments.map(comment => { console.log(comment.body); From e4abb3c121f1ca7ae4887110b1482d2fe631c45b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 27 Dec 2018 18:10:13 -0800 Subject: [PATCH 1632/4053] Fetch 100 review items at a time --- lib/containers/issueish-detail-container.js | 2 +- lib/views/pr-comments-view.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/containers/issueish-detail-container.js b/lib/containers/issueish-detail-container.js index 33e2404fbe..ee83411112 100644 --- a/lib/containers/issueish-detail-container.js +++ b/lib/containers/issueish-detail-container.js @@ -152,7 +152,7 @@ export default class IssueishDetailContainer extends React.Component { commitCursor: null, commentCount: 100, commentCursor: null, - reviewCount: 1, + reviewCount: 100, reviewCursor: null, }; diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index b0b5eebbb3..d269797d1f 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -33,7 +33,7 @@ export default class PullRequestCommentsView extends React.Component { _loadMoreReviews = () => { this.props.relay.loadMore( - 1, // Fetch the next 10 feed items + 100, error => { this._attemptToLoadMoreReviews(); if (error) { From 60c413a339127619f09afbaed959c1f2216db59b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 27 Dec 2018 18:14:44 -0800 Subject: [PATCH 1633/4053] Handle case then `replyTo` comment is outdated --- lib/views/pr-comments-view.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index d269797d1f..c1d7922f2e 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -70,7 +70,15 @@ export default class PullRequestCommentsView extends React.Component { if (!comment.replyTo) { commentsByRootCommentId.set(comment.id, [comment]); } else { - commentsByRootCommentId.get(comment.replyTo.id).push(comment); + // When comment being replied to is outdated... + // Ran into this error when viewing files for https://github.com/numpy/numpy/pull/9998 + // for comment MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE1MzA1NTUzMw, + // who's replyTo comment is an outdated comment + if (!commentsByRootCommentId.get(comment.replyTo.id)) { + commentsByRootCommentId.set(comment.replyTo.id, [comment]); + } else { + commentsByRootCommentId.get(comment.replyTo.id).push(comment); + } } }); }); From 929115f14a1249e7fb91b48c67662dd8ae6e064e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 27 Dec 2018 18:17:44 -0800 Subject: [PATCH 1634/4053] Add TODO to inline to fix hack to get comments to show up in order --- lib/views/pr-comments-view.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index c1d7922f2e..8624c1f159 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -95,6 +95,8 @@ export default class PullRequestCommentsView extends React.Component { const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, rootComment.position); const point = new Point(row, 0); const range = new Range(point, point); + // TODO: find way to re-use nodes by using same key. this count++ hack is in place to get the comments to show up + // in the correct order after new pages of data are fetched. Test it by reducing the reviewCount to a small number return ( From 130f862105d3388f0220e6b8b679399aa27d1349 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 27 Dec 2018 18:33:12 -0800 Subject: [PATCH 1635/4053] =?UTF-8?q?Make=20it=20clear=20in=20the=20commen?= =?UTF-8?q?t=20that=20I'm=20not=20100%=20sure=20=C2=AF\=5F(=E3=83=84)=5F/?= =?UTF-8?q?=C2=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/views/pr-comments-view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index 8624c1f159..c0755edce2 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -70,7 +70,8 @@ export default class PullRequestCommentsView extends React.Component { if (!comment.replyTo) { commentsByRootCommentId.set(comment.id, [comment]); } else { - // When comment being replied to is outdated... + // When comment being replied to is outdated...?? Not 100% sure... + // Why would we even get an outdated comment or a response to one here? // Ran into this error when viewing files for https://github.com/numpy/numpy/pull/9998 // for comment MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE1MzA1NTUzMw, // who's replyTo comment is an outdated comment From cbca7804f0a32180fce8ec1247d70a612a54d6f7 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 28 Dec 2018 11:29:56 -0800 Subject: [PATCH 1636/4053] WIP handle comments pagination Co-Authored-By: Tilde Ann Thurium --- .../pr-review-comments-container.js | 71 +++++++++++++++++++ lib/containers/pr-reviews-container.js | 30 ++------ lib/controllers/pr-reviews-controller.js | 60 ++++++++++++++++ 3 files changed, 137 insertions(+), 24 deletions(-) create mode 100644 lib/containers/pr-review-comments-container.js create mode 100644 lib/controllers/pr-reviews-controller.js diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js new file mode 100644 index 0000000000..ae23b3dca4 --- /dev/null +++ b/lib/containers/pr-review-comments-container.js @@ -0,0 +1,71 @@ +import {graphql, createPaginationContainer} from 'react-relay'; + +import PullRequestReviewCommentsView from '../views/pr-comments-view'; + +export default createPaginationContainer(PullRequestReviewCommentsView, { + review: graphql` + fragment prReviewCommentsContainer_review on PullRequestReview + @argumentDefinitions( + commentCount: {type: "Int!"}, + commentCursor: {type: "String"} + ) { + url + comments( + first: $commentCount, + after: $commentCursor + ) @connection(key: "PrReviewCommentsContainer_comments") { + pageInfo { + hasNextPage + endCursor + } + + edges { + cursor + node { + id + author { + avatarUrl + login + } + body + bodyHTML + path + position + replyTo { + id + } + createdAt + url + } + } + } + } + `, +}, { + direction: 'forward', + getConnectionFromProps(props) { + return props.review.comments; + }, + getFragmentVariables(prevVars, totalCount) { + return { + ...prevVars, + commentCount: totalCount, + }; + }, + getVariables(props, {count, cursor}, fragmentVariables) { + return { + url: props.review.url, + commentCount: count, + commentCursor: cursor, + }; + }, + query: graphql` + query prReviewCommentsContainerQuery($commentCount: Int, $commentCursor: String, $url: URI!) { + resource(url: $url) { + ... on PullRequestReview { + ...prReviewCommentsContainer_review @arguments(commentCount: $commentCount, commentCursor: $commentCursor) + } + } + } + `, +}); diff --git a/lib/containers/pr-reviews-container.js b/lib/containers/pr-reviews-container.js index f90e50e1c2..d57cd3a553 100644 --- a/lib/containers/pr-reviews-container.js +++ b/lib/containers/pr-reviews-container.js @@ -1,8 +1,8 @@ import {graphql, createPaginationContainer} from 'react-relay'; -import PullRequestReviewsView from '../views/pr-comments-view'; +import PullRequestReviewsController from '../controller/pr-reviews-controller'; -export default createPaginationContainer(PullRequestReviewsView, { +export default createPaginationContainer(PullRequestReviewsController, { pullRequest: graphql` fragment prReviewsContainer_pullRequest on PullRequest @argumentDefinitions( @@ -35,28 +35,10 @@ export default createPaginationContainer(PullRequestReviewsView, { author { avatarUrl } - comments(first: 100) { - pageInfo { - hasNextPage - endCursor - } - nodes { - id - author { - avatarUrl - login - } - body - bodyHTML - path - position - replyTo { - id - } - createdAt - url - } - } + ...prReviewCommentsContainer_review @arguments( + commentCount: $commentCount, + commentCursor: $commentCursor + ) } } } diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js new file mode 100644 index 0000000000..a5b20a3882 --- /dev/null +++ b/lib/controllers/pr-reviews-controller.js @@ -0,0 +1,60 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {RelayConnectionPropType} from '../prop-types'; + +import PullRequestReviewCommentsContainer from '../containers/pr-review-comments-container'; + + +export default class PullRequestReviewsController extends React.Component { + static propTypes = { + relay: PropTypes.shape({ + hasMore: PropTypes.func.isRequired, + loadMore: PropTypes.func.isRequired, + isLoading: PropTypes.func.isRequired, + }).isRequired, + pullRequest: PropTypes.shape({ + reviews: RelayConnectionPropType( + PropTypes.object, + ), + }), + multiFilePatch: PropTypes.object.isRequired, + } + + componentDidMount() { + this._attemptToLoadMoreReviews(); + } + + _loadMoreReviews = () => { + this.props.relay.loadMore( + 100, + error => { + this._attemptToLoadMoreReviews(); + if (error) { + console.log(error); + } + }, + ); + } + + _attemptToLoadMoreReviews = () => { + if (!this.props.relay.hasMore()) { + return; + } + + if (this.props.relay.isLoading()) { + setTimeout(() => { + this._loadMoreReviews(); + }, 300); + } else { + this._loadMoreReviews(); + } + } + + render() { + if (!this.props.pullRequest || !this.props.pullRequest.reviews) { + return null; + } + + return ; + } +} From 2c239ac42722be9fb89a95eaa0c40a314d59d6ed Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 28 Dec 2018 13:18:44 -0800 Subject: [PATCH 1637/4053] wip - render paginated comments Co-Authored-By: Katrina Uychaco --- .../issueishDetailContainerQuery.graphql.js | 136 ++++--- .../prReviewCommentsContainerQuery.graphql.js | 375 ++++++++++++++++++ ...rReviewCommentsContainer_review.graphql.js | 242 +++++++++++ .../prReviewsContainerQuery.graphql.js | 233 ++++++----- .../prReviewsContainer_pullRequest.graphql.js | 256 ++++-------- .../pr-review-comments-container.js | 13 +- lib/containers/pr-reviews-container.js | 2 +- lib/controllers/pr-reviews-controller.js | 9 +- .../prDetailViewRefetchQuery.graphql.js | 136 ++++--- lib/views/pr-comments-view.js | 9 +- 10 files changed, 1044 insertions(+), 367 deletions(-) create mode 100644 lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js create mode 100644 lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 2005783ec7..85536bb239 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash eacb91752813910cc6e618d90f821447 + * @relayHash 2272e39d4e33d96fe66954cf4a808caa */ /* eslint-disable */ @@ -44,6 +44,8 @@ query issueishDetailContainerQuery( $timelineCursor: String $commitCount: Int! $commitCursor: String + $commentCount: Int! + $commentCursor: String $reviewCount: Int $reviewCursor: String ) { @@ -234,32 +236,7 @@ fragment prReviewsContainer_pullRequest_2zzc96 on PullRequest { id } } - comments(first: 100) { - pageInfo { - hasNextPage - endCursor - } - nodes { - id - author { - __typename - avatarUrl - login - ... on Node { - id - } - } - body - bodyHTML - path - position - replyTo { - id - } - createdAt - url - } - } + ...prReviewCommentsContainer_review_1VbUmL __typename } } @@ -561,6 +538,40 @@ fragment prCommitView_item on Commit { url } +fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { + id + comments(first: $commentCount, after: $commentCursor) { + pageInfo { + hasNextPage + endCursor + } + edges { + cursor + node { + id + author { + __typename + avatarUrl + login + ... on Node { + id + } + } + body + bodyHTML + path + position + replyTo { + id + } + createdAt + url + __typename + } + } + } +} + fragment issueTimelineController_issue_3D8CP9 on Issue { url timeline(first: $timelineCount, after: $timelineCursor) { @@ -1152,9 +1163,15 @@ v38 = [ ], v39 = [ { - "kind": "Literal", + "kind": "Variable", + "name": "after", + "variableName": "commentCursor", + "type": "String" + }, + { + "kind": "Variable", "name": "first", - "value": 100, + "variableName": "commentCount", "type": "Int" } ], @@ -1197,7 +1214,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int\n $reviewCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_2zzc96\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $commentCount: Int!\n $commentCursor: String\n $reviewCount: Int\n $reviewCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_2zzc96\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1603,7 +1620,7 @@ return { "kind": "LinkedField", "alias": null, "name": "comments", - "storageKey": "comments(first:100)", + "storageKey": null, "args": v39, "concreteType": "PullRequestReviewCommentConnection", "plural": false, @@ -1612,36 +1629,58 @@ return { { "kind": "LinkedField", "alias": null, - "name": "nodes", + "name": "edges", "storageKey": null, "args": null, - "concreteType": "PullRequestReviewComment", + "concreteType": "PullRequestReviewCommentEdge", "plural": true, "selections": [ - v2, - v26, - v37, - v12, - v40, - v41, + v21, { "kind": "LinkedField", "alias": null, - "name": "replyTo", + "name": "node", "storageKey": null, "args": null, "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v2 + v2, + v26, + v37, + v12, + v40, + v41, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v2 + ] + }, + v27, + v14, + v4 ] - }, - v27, - v14 + } ] } ] }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "comments", + "args": v39, + "handle": "connection", + "key": "PrReviewCommentsContainer_comments", + "filters": null + }, v4 ] } @@ -1844,7 +1883,14 @@ return { "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v39, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100, + "type": "Int" + } + ], "concreteType": "CommitCommentConnection", "plural": false, "selections": [ diff --git a/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js new file mode 100644 index 0000000000..dc46be1e33 --- /dev/null +++ b/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js @@ -0,0 +1,375 @@ +/** + * @flow + * @relayHash 5658256f086fc990e6f8c58966b65c77 + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest } from 'relay-runtime'; +type prReviewCommentsContainer_review$ref = any; +export type prReviewCommentsContainerQueryVariables = {| + commentCount: number, + commentCursor?: ?string, + id: string, +|}; +export type prReviewCommentsContainerQueryResponse = {| + +node: ?{| + +$fragmentRefs: prReviewCommentsContainer_review$ref + |} +|}; +export type prReviewCommentsContainerQuery = {| + variables: prReviewCommentsContainerQueryVariables, + response: prReviewCommentsContainerQueryResponse, +|}; +*/ + + +/* +query prReviewCommentsContainerQuery( + $commentCount: Int! + $commentCursor: String + $id: ID! +) { + node(id: $id) { + __typename + ... on PullRequestReview { + ...prReviewCommentsContainer_review_1VbUmL + } + id + } +} + +fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { + id + comments(first: $commentCount, after: $commentCursor) { + pageInfo { + hasNextPage + endCursor + } + edges { + cursor + node { + id + author { + __typename + avatarUrl + login + ... on Node { + id + } + } + body + bodyHTML + path + position + replyTo { + id + } + createdAt + url + __typename + } + } + } +} +*/ + +const node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "kind": "LocalArgument", + "name": "commentCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCursor", + "type": "String", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "id", + "type": "ID!", + "defaultValue": null + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id", + "type": "ID!" + } +], +v2 = { + "kind": "ScalarField", + "alias": null, + "name": "__typename", + "args": null, + "storageKey": null +}, +v3 = { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null +}, +v4 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "commentCursor", + "type": "String" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "commentCount", + "type": "Int" + } +]; +return { + "kind": "Request", + "operationKind": "query", + "name": "prReviewCommentsContainerQuery", + "id": null, + "text": "query prReviewCommentsContainerQuery(\n $commentCount: Int!\n $commentCursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on PullRequestReview {\n ...prReviewCommentsContainer_review_1VbUmL\n }\n id\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "metadata": {}, + "fragment": { + "kind": "Fragment", + "name": "prReviewCommentsContainerQuery", + "type": "Query", + "metadata": null, + "argumentDefinitions": v0, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": v1, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "type": "PullRequestReview", + "selections": [ + { + "kind": "FragmentSpread", + "name": "prReviewCommentsContainer_review", + "args": [ + { + "kind": "Variable", + "name": "commentCount", + "variableName": "commentCount", + "type": null + }, + { + "kind": "Variable", + "name": "commentCursor", + "variableName": "commentCursor", + "type": null + } + ] + } + ] + } + ] + } + ] + }, + "operation": { + "kind": "Operation", + "name": "prReviewCommentsContainerQuery", + "argumentDefinitions": v0, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": v1, + "concreteType": null, + "plural": false, + "selections": [ + v2, + v3, + { + "kind": "InlineFragment", + "type": "PullRequestReview", + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": null, + "args": v4, + "concreteType": "PullRequestReviewCommentConnection", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null + } + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewCommentEdge", + "plural": true, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "cursor", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v3, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v2, + { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null + }, + v3 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "path", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "position", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v3 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null + }, + v2 + ] + } + ] + } + ] + }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "comments", + "args": v4, + "handle": "connection", + "key": "PrReviewCommentsContainer_comments", + "filters": null + } + ] + } + ] + } + ] + } +}; +})(); +// prettier-ignore +(node/*: any*/).hash = 'd48507a6296f84357e94000010c34713'; +module.exports = node; diff --git a/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js b/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js new file mode 100644 index 0000000000..5a674efbcc --- /dev/null +++ b/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js @@ -0,0 +1,242 @@ +/** + * @flow + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteFragment } from 'relay-runtime'; +import type { FragmentReference } from "relay-runtime"; +declare export opaque type prReviewCommentsContainer_review$ref: FragmentReference; +export type prReviewCommentsContainer_review = {| + +id: string, + +comments: {| + +pageInfo: {| + +hasNextPage: boolean, + +endCursor: ?string, + |}, + +edges: ?$ReadOnlyArray, + |}, + +$refType: prReviewCommentsContainer_review$ref, +|}; +*/ + + +const node/*: ConcreteFragment*/ = (function(){ +var v0 = { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null +}; +return { + "kind": "Fragment", + "name": "prReviewCommentsContainer_review", + "type": "PullRequestReview", + "metadata": { + "connection": [ + { + "count": "commentCount", + "cursor": "commentCursor", + "direction": "forward", + "path": [ + "comments" + ] + } + ] + }, + "argumentDefinitions": [ + { + "kind": "LocalArgument", + "name": "commentCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCursor", + "type": "String", + "defaultValue": null + } + ], + "selections": [ + v0, + { + "kind": "LinkedField", + "alias": "comments", + "name": "__PrReviewCommentsContainer_comments_connection", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewCommentConnection", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null + } + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewCommentEdge", + "plural": true, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "cursor", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v0, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null + } + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "path", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "position", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v0 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "__typename", + "args": null, + "storageKey": null + } + ] + } + ] + } + ] + } + ] +}; +})(); +// prettier-ignore +(node/*: any*/).hash = 'd4b785b8fc4e5b4bedc5aa92491f37e7'; +module.exports = node; diff --git a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js index e6ee421cf3..aac991e4b6 100644 --- a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash c79a1d774a35cceb455839a0124e28fc + * @relayHash f0d1b6fd1bbccab16bb6901bb25f4120 */ /* eslint-disable */ @@ -76,32 +76,41 @@ fragment prReviewsContainer_pullRequest_2zzc96 on PullRequest { id } } - comments(first: 100) { - pageInfo { - hasNextPage - endCursor - } - nodes { + ...prReviewCommentsContainer_review_1VbUmL + __typename + } + } + } +} + +fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { + id + comments(first: $commentCount, after: $commentCursor) { + pageInfo { + hasNextPage + endCursor + } + edges { + cursor + node { + id + author { + __typename + avatarUrl + login + ... on Node { id - author { - __typename - avatarUrl - login - ... on Node { - id - } - } - body - bodyHTML - path - position - replyTo { - id - } - createdAt - url } } + body + bodyHTML + path + position + replyTo { + id + } + createdAt + url __typename } } @@ -201,30 +210,51 @@ v6 = { v7 = { "kind": "ScalarField", "alias": null, - "name": "body", + "name": "cursor", "args": null, "storageKey": null }, v8 = { "kind": "ScalarField", "alias": null, - "name": "login", + "name": "body", "args": null, "storageKey": null }, v9 = { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null +}, +v10 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", "args": null, "storageKey": null -}; +}, +v11 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "commentCursor", + "type": "String" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "commentCount", + "type": "Int" + } +]; return { "kind": "Request", "operationKind": "query", "name": "prReviewsContainerQuery", "id": null, - "text": "query prReviewsContainerQuery(\n $reviewCount: Int\n $reviewCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_2zzc96\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n", + "text": "query prReviewsContainerQuery(\n $reviewCount: Int\n $reviewCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_2zzc96\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -310,13 +340,7 @@ return { "concreteType": "PullRequestReviewEdge", "plural": true, "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "cursor", - "args": null, - "storageKey": null - }, + v7, { "kind": "LinkedField", "alias": null, @@ -327,7 +351,7 @@ return { "plural": false, "selections": [ v3, - v7, + v8, { "kind": "LinkedField", "alias": "commitId", @@ -371,7 +395,7 @@ return { "plural": false, "selections": [ v2, - v8, + v9, v3 ] }, @@ -385,7 +409,7 @@ return { "plural": false, "selections": [ v2, - v9, + v10, v3 ] }, @@ -393,15 +417,8 @@ return { "kind": "LinkedField", "alias": null, "name": "comments", - "storageKey": "comments(first:100)", - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 100, - "type": "Int" - } - ], + "storageKey": null, + "args": v11, "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ @@ -409,74 +426,96 @@ return { { "kind": "LinkedField", "alias": null, - "name": "nodes", + "name": "edges", "storageKey": null, "args": null, - "concreteType": "PullRequestReviewComment", + "concreteType": "PullRequestReviewCommentEdge", "plural": true, "selections": [ - v3, - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v2, - v9, - v8, - v3 - ] - }, v7, - { - "kind": "ScalarField", - "alias": null, - "name": "bodyHTML", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "path", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "position", - "args": null, - "storageKey": null - }, { "kind": "LinkedField", "alias": null, - "name": "replyTo", + "name": "node", "storageKey": null, "args": null, "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v3 + v3, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + v2, + v10, + v9, + v3 + ] + }, + v8, + { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "path", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "position", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v3 + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null + }, + v4, + v2 ] - }, - { - "kind": "ScalarField", - "alias": null, - "name": "createdAt", - "args": null, - "storageKey": null - }, - v4 + } ] } ] }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "comments", + "args": v11, + "handle": "connection", + "key": "PrReviewCommentsContainer_comments", + "filters": null + }, v2 ] } diff --git a/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js index cd27865199..5145f6f6ec 100644 --- a/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js +++ b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js @@ -8,6 +8,7 @@ /*:: import type { ConcreteFragment } from 'relay-runtime'; +type prReviewCommentsContainer_review$ref = any; export type PullRequestReviewState = "APPROVED" | "CHANGES_REQUESTED" | "COMMENTED" | "DISMISSED" | "PENDING" | "%future added value"; import type { FragmentReference } from "relay-runtime"; declare export opaque type prReviewsContainer_pullRequest$ref: FragmentReference; @@ -34,28 +35,7 @@ export type prReviewsContainer_pullRequest = {| +author: ?{| +avatarUrl: any |}, - +comments: {| - +pageInfo: {| - +hasNextPage: boolean, - +endCursor: ?string, - |}, - +nodes: ?$ReadOnlyArray, - |}, + +$fragmentRefs: prReviewCommentsContainer_review$ref, |}, |}>, |}, @@ -64,68 +44,7 @@ export type prReviewsContainer_pullRequest = {| */ -const node/*: ConcreteFragment*/ = (function(){ -var v0 = { - "kind": "ScalarField", - "alias": null, - "name": "url", - "args": null, - "storageKey": null -}, -v1 = { - "kind": "LinkedField", - "alias": null, - "name": "pageInfo", - "storageKey": null, - "args": null, - "concreteType": "PageInfo", - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "hasNextPage", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "endCursor", - "args": null, - "storageKey": null - } - ] -}, -v2 = { - "kind": "ScalarField", - "alias": null, - "name": "id", - "args": null, - "storageKey": null -}, -v3 = { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null -}, -v4 = { - "kind": "ScalarField", - "alias": null, - "name": "login", - "args": null, - "storageKey": null -}, -v5 = { - "kind": "ScalarField", - "alias": null, - "name": "avatarUrl", - "args": null, - "storageKey": null -}; -return { +const node/*: ConcreteFragment*/ = { "kind": "Fragment", "name": "prReviewsContainer_pullRequest", "type": "PullRequest", @@ -153,10 +72,26 @@ return { "name": "reviewCursor", "type": "String", "defaultValue": null + }, + { + "kind": "RootArgument", + "name": "commentCount", + "type": null + }, + { + "kind": "RootArgument", + "name": "commentCursor", + "type": null } ], "selections": [ - v0, + { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null + }, { "kind": "LinkedField", "alias": "reviews", @@ -166,7 +101,31 @@ return { "concreteType": "PullRequestReviewConnection", "plural": false, "selections": [ - v1, + { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null + } + ] + }, { "kind": "LinkedField", "alias": null, @@ -192,8 +151,20 @@ return { "concreteType": "PullRequestReview", "plural": false, "selections": [ - v2, - v3, + { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null + }, { "kind": "LinkedField", "alias": "commitId", @@ -235,7 +206,13 @@ return { "concreteType": null, "plural": false, "selections": [ - v4 + { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null + } ] }, { @@ -247,92 +224,30 @@ return { "concreteType": null, "plural": false, "selections": [ - v5 + { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null + } ] }, { - "kind": "LinkedField", - "alias": null, - "name": "comments", - "storageKey": "comments(first:100)", + "kind": "FragmentSpread", + "name": "prReviewCommentsContainer_review", "args": [ { - "kind": "Literal", - "name": "first", - "value": 100, - "type": "Int" - } - ], - "concreteType": "PullRequestReviewCommentConnection", - "plural": false, - "selections": [ - v1, + "kind": "Variable", + "name": "commentCount", + "variableName": "commentCount", + "type": null + }, { - "kind": "LinkedField", - "alias": null, - "name": "nodes", - "storageKey": null, - "args": null, - "concreteType": "PullRequestReviewComment", - "plural": true, - "selections": [ - v2, - { - "kind": "LinkedField", - "alias": null, - "name": "author", - "storageKey": null, - "args": null, - "concreteType": null, - "plural": false, - "selections": [ - v5, - v4 - ] - }, - v3, - { - "kind": "ScalarField", - "alias": null, - "name": "bodyHTML", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "path", - "args": null, - "storageKey": null - }, - { - "kind": "ScalarField", - "alias": null, - "name": "position", - "args": null, - "storageKey": null - }, - { - "kind": "LinkedField", - "alias": null, - "name": "replyTo", - "storageKey": null, - "args": null, - "concreteType": "PullRequestReviewComment", - "plural": false, - "selections": [ - v2 - ] - }, - { - "kind": "ScalarField", - "alias": null, - "name": "createdAt", - "args": null, - "storageKey": null - }, - v0 - ] + "kind": "Variable", + "name": "commentCursor", + "variableName": "commentCursor", + "type": null } ] }, @@ -351,7 +266,6 @@ return { } ] }; -})(); // prettier-ignore -(node/*: any*/).hash = 'd234d1f47b1a977b18174045b21cf0f0'; +(node/*: any*/).hash = '4d74fc25f4b854782495fc05521f961c'; module.exports = node; diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index ae23b3dca4..3caeb9f408 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -9,7 +9,7 @@ export default createPaginationContainer(PullRequestReviewCommentsView, { commentCount: {type: "Int!"}, commentCursor: {type: "String"} ) { - url + id comments( first: $commentCount, after: $commentCursor @@ -54,16 +54,19 @@ export default createPaginationContainer(PullRequestReviewCommentsView, { }, getVariables(props, {count, cursor}, fragmentVariables) { return { - url: props.review.url, + id: props.review.id, commentCount: count, commentCursor: cursor, }; }, query: graphql` - query prReviewCommentsContainerQuery($commentCount: Int, $commentCursor: String, $url: URI!) { - resource(url: $url) { + query prReviewCommentsContainerQuery($commentCount: Int!, $commentCursor: String, $id: ID!) { + node(id: $id) { ... on PullRequestReview { - ...prReviewCommentsContainer_review @arguments(commentCount: $commentCount, commentCursor: $commentCursor) + ...prReviewCommentsContainer_review @arguments( + commentCount: $commentCount, + commentCursor: $commentCursor + ) } } } diff --git a/lib/containers/pr-reviews-container.js b/lib/containers/pr-reviews-container.js index d57cd3a553..8de768a770 100644 --- a/lib/containers/pr-reviews-container.js +++ b/lib/containers/pr-reviews-container.js @@ -1,6 +1,6 @@ import {graphql, createPaginationContainer} from 'react-relay'; -import PullRequestReviewsController from '../controller/pr-reviews-controller'; +import PullRequestReviewsController from '../controllers/pr-reviews-controller'; export default createPaginationContainer(PullRequestReviewsController, { pullRequest: graphql` diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index a5b20a3882..e744234186 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -55,6 +55,13 @@ export default class PullRequestReviewsController extends React.Component { return null; } - return ; + return this.props.pullRequest.reviews.forEach.map(({node}) => { + const review = node; + const reviewProp = this.props.reviews.find(r => r.id === review.id) + reviewProp.comments = review.comments; + return ( + + ); + }); } } diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index fe380438d1..15337e8fdb 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 8b5005b08f8393c1b2d22577e9530360 + * @relayHash e23a0a2054c2cff16dfe71a64cf4fe9f */ /* eslint-disable */ @@ -46,6 +46,8 @@ query prDetailViewRefetchQuery( $timelineCursor: String $commitCount: Int! $commitCursor: String + $commentCount: Int! + $commentCursor: String $reviewCount: Int $reviewCursor: String ) { @@ -150,32 +152,7 @@ fragment prReviewsContainer_pullRequest_2zzc96 on PullRequest { id } } - comments(first: 100) { - pageInfo { - hasNextPage - endCursor - } - nodes { - id - author { - __typename - avatarUrl - login - ... on Node { - id - } - } - body - bodyHTML - path - position - replyTo { - id - } - createdAt - url - } - } + ...prReviewCommentsContainer_review_1VbUmL __typename } } @@ -476,6 +453,40 @@ fragment prCommitView_item on Commit { sha: oid url } + +fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { + id + comments(first: $commentCount, after: $commentCursor) { + pageInfo { + hasNextPage + endCursor + } + edges { + cursor + node { + id + author { + __typename + avatarUrl + login + ... on Node { + id + } + } + body + bodyHTML + path + position + replyTo { + id + } + createdAt + url + __typename + } + } + } +} */ const node/*: ConcreteRequest*/ = (function(){ @@ -714,9 +725,15 @@ v22 = { }, v23 = [ { - "kind": "Literal", + "kind": "Variable", + "name": "after", + "variableName": "commentCursor", + "type": "String" + }, + { + "kind": "Variable", "name": "first", - "value": 100, + "variableName": "commentCount", "type": "Int" } ], @@ -875,7 +892,7 @@ return { "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int\n $reviewCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_2zzc96\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n comments(first: 100) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n }\n }\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $commentCount: Int!\n $commentCursor: String\n $reviewCount: Int\n $reviewCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_2zzc96\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1089,7 +1106,7 @@ return { "kind": "LinkedField", "alias": null, "name": "comments", - "storageKey": "comments(first:100)", + "storageKey": null, "args": v23, "concreteType": "PullRequestReviewCommentConnection", "plural": false, @@ -1098,36 +1115,58 @@ return { { "kind": "LinkedField", "alias": null, - "name": "nodes", + "name": "edges", "storageKey": null, "args": null, - "concreteType": "PullRequestReviewComment", + "concreteType": "PullRequestReviewCommentEdge", "plural": true, "selections": [ - v6, - v25, - v19, - v26, - v27, - v28, + v18, { "kind": "LinkedField", "alias": null, - "name": "replyTo", + "name": "node", "storageKey": null, "args": null, "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v6 + v6, + v25, + v19, + v26, + v27, + v28, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + v6 + ] + }, + v29, + v13, + v5 ] - }, - v29, - v13 + } ] } ] }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "comments", + "args": v23, + "handle": "connection", + "key": "PrReviewCommentsContainer_comments", + "filters": null + }, v5 ] } @@ -1551,7 +1590,14 @@ return { "alias": null, "name": "comments", "storageKey": "comments(first:100)", - "args": v23, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100, + "type": "Int" + } + ], "concreteType": "CommitCommentConnection", "plural": false, "selections": [ diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-comments-view.js index c0755edce2..a63f517a2c 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-comments-view.js @@ -27,7 +27,6 @@ export default class PullRequestCommentsView extends React.Component { } componentDidMount() { - console.log('DID MOUNT'); this._attemptToLoadMoreReviews(); } @@ -58,6 +57,7 @@ export default class PullRequestCommentsView extends React.Component { } render() { + console.log('!!!! props', this.props) if (!this.props.pullRequest || !this.props.pullRequest.reviews) { return null; } @@ -66,7 +66,12 @@ export default class PullRequestCommentsView extends React.Component { this.props.pullRequest.reviews.edges.forEach(({node}) => { const review = node; - review.comments.nodes.forEach(comment => { + if (!review.comments) { + return null; + } + review.comments.edges.forEach(({node}) => { + const comment = node; + console.log('comment!!!', comment); if (!comment.replyTo) { commentsByRootCommentId.set(comment.id, [comment]); } else { From 657c7a68212a7db8a09b46f32c6d56b7972f9952 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 28 Dec 2018 15:32:41 -0800 Subject: [PATCH 1638/4053] Get comments rendering again! Woohoo Co-Authored-By: Tilde Ann Thurium --- .../pr-review-comments-container.js | 58 +++++++++++++- lib/controllers/pr-reviews-controller.js | 67 ++++++++++++++-- ...nts-view.js => pr-review-comments-view.js} | 76 ++----------------- 3 files changed, 121 insertions(+), 80 deletions(-) rename lib/views/{pr-comments-view.js => pr-review-comments-view.js} (56%) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 3caeb9f408..723d9b352e 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -1,8 +1,62 @@ import {graphql, createPaginationContainer} from 'react-relay'; +import React from 'react'; -import PullRequestReviewCommentsView from '../views/pr-comments-view'; +export class ReviewCommentsController extends React.Component { + // static getDerivedStateFromProps(nextProps, prevState) { + // console.log('getDerivedStateFromProps'); + // if (nextProps !== prevState.props) { + // nextProps.aggregateComments(nextProps.review.id, nextProps.review.comments); + // } + // return { + // props: nextProps, + // }; + // } -export default createPaginationContainer(PullRequestReviewCommentsView, { + constructor(props) { + super(props); + console.log('pr-review-container-constructor'); + } + + componentDidMount() { + this.props.aggregateComments(this.props.review.id, this.props.review.comments); + console.log('comments!!!', this.props.review.comments); + this._attemptToLoadMoreComments(); + } + + _loadMoreComments = () => { + this.props.relay.loadMore( + 100, + error => { + this.props.aggregateComments(this.props.review.id, this.props.review.comments); + console.log('comments!!!', this.props.review.comments); + this._attemptToLoadMoreComments(); + if (error) { + console.log(error); + } + }, + ); + } + + _attemptToLoadMoreComments = () => { + if (!this.props.relay.hasMore()) { + return; + } + + if (this.props.relay.isLoading()) { + setTimeout(() => { + this._loadMoreComments(); + }, 300); + } else { + this._loadMoreComments(); + } + } + + render() { + return null; + } +} + +export default createPaginationContainer(ReviewCommentsController, { review: graphql` fragment prReviewCommentsContainer_review on PullRequestReview @argumentDefinitions( diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index e744234186..79b3c32fb7 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import {RelayConnectionPropType} from '../prop-types'; import PullRequestReviewCommentsContainer from '../containers/pr-review-comments-container'; - +import PullRequestReviewCommentsView from '../views/pr-review-comments-view'; export default class PullRequestReviewsController extends React.Component { static propTypes = { @@ -20,6 +20,11 @@ export default class PullRequestReviewsController extends React.Component { multiFilePatch: PropTypes.object.isRequired, } + constructor(props) { + super(props); + this.state = {}; + } + componentDidMount() { this._attemptToLoadMoreReviews(); } @@ -50,18 +55,64 @@ export default class PullRequestReviewsController extends React.Component { } } + aggregateComments = (reviewId, comments) => { + const state = this.state; + comments.edges.forEach(({node}) => { + const comment = node; + if (!comment.replyTo) { + state[comment.id] = [comment]; + // this.setState({[comment.id]: [comment]}); + } else { + // When comment being replied to is outdated...?? Not 100% sure... + // Why would we even get an outdated comment or a response to one here? + // Ran into this error when viewing files for https://github.com/numpy/numpy/pull/9998 + // for comment MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE1MzA1NTUzMw, + // who's replyTo comment is an outdated comment + if (!state[comment.replyTo.id]) { + state[comment.id] = [comment]; + // this.setState({[comment.id]: [comment]}); + } else { + state[comment.replyTo.id].push(comment); + } + } + }); + this.setState(state); + } + + renderReviewCommentContainers() { + // Aggregate comments from all reviews + return this.props.pullRequest.reviews.edges.map(({node}) => { + const review = node; + console.log('!!!! review in PrReviewsController', review); + return ( + + ); + }); + } + render() { + console.log('PrReviewsController', this.props.pullRequest); + if (!this.props.pullRequest || !this.props.pullRequest.reviews) { return null; } - return this.props.pullRequest.reviews.forEach.map(({node}) => { - const review = node; - const reviewProp = this.props.reviews.find(r => r.id === review.id) - reviewProp.comments = review.comments; - return ( - - ); + const commentThreads = Object.keys(this.state).map(rootCommentId => { + return { + rootCommentId, + comments: this.state[rootCommentId], + }; }); + + return ( +
    + {this.renderReviewCommentContainers()} + +
    + ); } } diff --git a/lib/views/pr-comments-view.js b/lib/views/pr-review-comments-view.js similarity index 56% rename from lib/views/pr-comments-view.js rename to lib/views/pr-review-comments-view.js index a63f517a2c..a7434538bb 100644 --- a/lib/views/pr-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -18,80 +18,16 @@ export default class PullRequestCommentsView extends React.Component { loadMore: PropTypes.func.isRequired, isLoading: PropTypes.func.isRequired, }).isRequired, - pullRequest: PropTypes.shape({ - reviews: RelayConnectionPropType( - PropTypes.object, - ), - }), + commentThreads: PropTypes.arrayOf(PropTypes.shape({ + rootCommentId: PropTypes.number.isRequired, + comments: PropTypes.arrayOf(PropTypes.object).isRequired, + })), multiFilePatch: PropTypes.object.isRequired, } - componentDidMount() { - this._attemptToLoadMoreReviews(); - } - - _loadMoreReviews = () => { - this.props.relay.loadMore( - 100, - error => { - this._attemptToLoadMoreReviews(); - if (error) { - console.log(error); - } - }, - ); - } - - _attemptToLoadMoreReviews = () => { - if (!this.props.relay.hasMore()) { - return; - } - - if (this.props.relay.isLoading()) { - setTimeout(() => { - this._loadMoreReviews(); - }, 300); - } else { - this._loadMoreReviews(); - } - } - render() { - console.log('!!!! props', this.props) - if (!this.props.pullRequest || !this.props.pullRequest.reviews) { - return null; - } - - const commentsByRootCommentId = new Map(); - - this.props.pullRequest.reviews.edges.forEach(({node}) => { - const review = node; - if (!review.comments) { - return null; - } - review.comments.edges.forEach(({node}) => { - const comment = node; - console.log('comment!!!', comment); - if (!comment.replyTo) { - commentsByRootCommentId.set(comment.id, [comment]); - } else { - // When comment being replied to is outdated...?? Not 100% sure... - // Why would we even get an outdated comment or a response to one here? - // Ran into this error when viewing files for https://github.com/numpy/numpy/pull/9998 - // for comment MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE1MzA1NTUzMw, - // who's replyTo comment is an outdated comment - if (!commentsByRootCommentId.get(comment.replyTo.id)) { - commentsByRootCommentId.set(comment.replyTo.id, [comment]); - } else { - commentsByRootCommentId.get(comment.replyTo.id).push(comment); - } - } - }); - }); - - console.log('SIZE', commentsByRootCommentId.size, [...commentsByRootCommentId]); - - return [...commentsByRootCommentId].reverse().map(([rootCommentId, comments]) => { + console.log('!!!! comment threads', this.props.commentThreads); + return [...this.props.commentThreads].reverse().map(({rootCommentId, comments}) => { const rootComment = comments[0]; if (!rootComment.position) { return null; From 2de958fc19433527607d7826529a360ac5c02ead Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 28 Dec 2018 15:58:53 -0800 Subject: [PATCH 1639/4053] Thread commentCount and commentCursor through component hierarchy Co-Authored-By: Tilde Ann Thurium --- .../issueishDetailContainerQuery.graphql.js | 28 ++++++------ .../prReviewsContainerQuery.graphql.js | 44 +++++++++++++++---- .../prReviewsContainer_pullRequest.graphql.js | 14 +++--- lib/containers/issueish-detail-container.js | 14 +++--- .../pr-review-comments-container.js | 18 +------- lib/containers/pr-reviews-container.js | 25 +++++++++-- ...eishDetailController_repository.graphql.js | 10 ++--- lib/controllers/issueish-detail-controller.js | 8 ++-- lib/controllers/pr-reviews-controller.js | 4 +- .../prDetailViewRefetchQuery.graphql.js | 28 ++++++------ .../prDetailView_pullRequest.graphql.js | 24 +++++++--- lib/views/pr-detail-view.js | 23 ++++++---- lib/views/pr-review-comments-view.js | 1 - 13 files changed, 144 insertions(+), 97 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 85536bb239..5158fd1965 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 2272e39d4e33d96fe66954cf4a808caa + * @relayHash 89eb63ec03a0c0f750b90f024809ee03 */ /* eslint-disable */ @@ -18,10 +18,10 @@ export type issueishDetailContainerQueryVariables = {| timelineCursor?: ?string, commitCount: number, commitCursor?: ?string, + reviewCount: number, + reviewCursor?: ?string, commentCount: number, commentCursor?: ?string, - reviewCount?: ?number, - reviewCursor?: ?string, |}; export type issueishDetailContainerQueryResponse = {| +repository: ?{| @@ -44,10 +44,10 @@ query issueishDetailContainerQuery( $timelineCursor: String $commitCount: Int! $commitCursor: String + $reviewCount: Int! + $reviewCursor: String $commentCount: Int! $commentCursor: String - $reviewCount: Int - $reviewCursor: String ) { repository(owner: $repoOwner, name: $repoName) { ...issueishDetailController_repository_y3nHF @@ -164,7 +164,7 @@ fragment prDetailView_pullRequest_2qM2KL on PullRequest { } isCrossRepository changedFiles - ...prReviewsContainer_pullRequest_2zzc96 + ...prReviewsContainer_pullRequest_y4qc0 ...prCommitsView_pullRequest_38TpXw countedCommits: commits { totalCount @@ -204,7 +204,7 @@ fragment prDetailView_pullRequest_2qM2KL on PullRequest { } } -fragment prReviewsContainer_pullRequest_2zzc96 on PullRequest { +fragment prReviewsContainer_pullRequest_y4qc0 on PullRequest { url reviews(first: $reviewCount, after: $reviewCursor) { pageInfo { @@ -641,25 +641,25 @@ var v0 = [ }, { "kind": "LocalArgument", - "name": "commentCount", + "name": "reviewCount", "type": "Int!", "defaultValue": null }, { "kind": "LocalArgument", - "name": "commentCursor", + "name": "reviewCursor", "type": "String", "defaultValue": null }, { "kind": "LocalArgument", - "name": "reviewCount", - "type": "Int", + "name": "commentCount", + "type": "Int!", "defaultValue": null }, { "kind": "LocalArgument", - "name": "reviewCursor", + "name": "commentCursor", "type": "String", "defaultValue": null } @@ -1214,7 +1214,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $commentCount: Int!\n $commentCursor: String\n $reviewCount: Int\n $reviewCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_2zzc96\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -2009,5 +2009,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'e4903c4025a67651b3086652adb8aa06'; +(node/*: any*/).hash = '6a16db513a3cd8bf14fafb3c8b269643'; module.exports = node; diff --git a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js index aac991e4b6..edf519cbfc 100644 --- a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash f0d1b6fd1bbccab16bb6901bb25f4120 + * @relayHash 4118118a2d92572dc108cf48c14c067f */ /* eslint-disable */ @@ -11,8 +11,10 @@ import type { ConcreteRequest } from 'relay-runtime'; type prReviewsContainer_pullRequest$ref = any; export type prReviewsContainerQueryVariables = {| - reviewCount?: ?number, + reviewCount: number, reviewCursor?: ?string, + commentCount: number, + commentCursor?: ?string, url: any, |}; export type prReviewsContainerQueryResponse = {| @@ -29,14 +31,16 @@ export type prReviewsContainerQuery = {| /* query prReviewsContainerQuery( - $reviewCount: Int + $reviewCount: Int! $reviewCursor: String + $commentCount: Int! + $commentCursor: String $url: URI! ) { resource(url: $url) { __typename ... on PullRequest { - ...prReviewsContainer_pullRequest_2zzc96 + ...prReviewsContainer_pullRequest_y4qc0 } ... on Node { id @@ -44,7 +48,7 @@ query prReviewsContainerQuery( } } -fragment prReviewsContainer_pullRequest_2zzc96 on PullRequest { +fragment prReviewsContainer_pullRequest_y4qc0 on PullRequest { url reviews(first: $reviewCount, after: $reviewCursor) { pageInfo { @@ -123,7 +127,7 @@ var v0 = [ { "kind": "LocalArgument", "name": "reviewCount", - "type": "Int", + "type": "Int!", "defaultValue": null }, { @@ -132,6 +136,18 @@ var v0 = [ "type": "String", "defaultValue": null }, + { + "kind": "LocalArgument", + "name": "commentCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCursor", + "type": "String", + "defaultValue": null + }, { "kind": "LocalArgument", "name": "url", @@ -254,7 +270,7 @@ return { "operationKind": "query", "name": "prReviewsContainerQuery", "id": null, - "text": "query prReviewsContainerQuery(\n $reviewCount: Int\n $reviewCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_2zzc96\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_2zzc96 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "text": "query prReviewsContainerQuery(\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_y4qc0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -280,6 +296,18 @@ return { "kind": "FragmentSpread", "name": "prReviewsContainer_pullRequest", "args": [ + { + "kind": "Variable", + "name": "commentCount", + "variableName": "commentCount", + "type": null + }, + { + "kind": "Variable", + "name": "commentCursor", + "variableName": "commentCursor", + "type": null + }, { "kind": "Variable", "name": "reviewCount", @@ -541,5 +569,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '29e5b225e2d3999e4cd475edc354015d'; +(node/*: any*/).hash = 'a84a1ddfd0a7a0667a57d94d5db110cf'; module.exports = node; diff --git a/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js index 5145f6f6ec..04d2100cf7 100644 --- a/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js +++ b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js @@ -64,7 +64,7 @@ const node/*: ConcreteFragment*/ = { { "kind": "LocalArgument", "name": "reviewCount", - "type": "Int", + "type": "Int!", "defaultValue": null }, { @@ -74,14 +74,16 @@ const node/*: ConcreteFragment*/ = { "defaultValue": null }, { - "kind": "RootArgument", + "kind": "LocalArgument", "name": "commentCount", - "type": null + "type": "Int!", + "defaultValue": null }, { - "kind": "RootArgument", + "kind": "LocalArgument", "name": "commentCursor", - "type": null + "type": "String", + "defaultValue": null } ], "selections": [ @@ -267,5 +269,5 @@ const node/*: ConcreteFragment*/ = { ] }; // prettier-ignore -(node/*: any*/).hash = '4d74fc25f4b854782495fc05521f961c'; +(node/*: any*/).hash = 'e5d1cfb5428af5817e22a53694345419'; module.exports = node; diff --git a/lib/containers/issueish-detail-container.js b/lib/containers/issueish-detail-container.js index ee83411112..fc326181ce 100644 --- a/lib/containers/issueish-detail-container.js +++ b/lib/containers/issueish-detail-container.js @@ -122,10 +122,10 @@ export default class IssueishDetailContainer extends React.Component { $timelineCursor: String $commitCount: Int! $commitCursor: String, + $reviewCount: Int!, + $reviewCursor: String, $commentCount: Int!, $commentCursor: String, - $reviewCount: Int, - $reviewCursor: String, ) { repository(owner: $repoOwner, name: $repoName) { ...issueishDetailController_repository @arguments( @@ -134,10 +134,10 @@ export default class IssueishDetailContainer extends React.Component { timelineCursor: $timelineCursor, commitCount: $commitCount, commitCursor: $commitCursor, - commentCount: $commentCount, - commentCursor: $commentCursor, reviewCount: $reviewCount, reviewCursor: $reviewCursor, + commentCount: $commentCount, + commentCursor: $commentCursor, ) } } @@ -150,10 +150,10 @@ export default class IssueishDetailContainer extends React.Component { timelineCursor: null, commitCount: 100, commitCursor: null, - commentCount: 100, - commentCursor: null, - reviewCount: 100, + reviewCount: 2, reviewCursor: null, + commentCount: 2, + commentCursor: null, }; return ( diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 723d9b352e..175ace49a0 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -2,24 +2,9 @@ import {graphql, createPaginationContainer} from 'react-relay'; import React from 'react'; export class ReviewCommentsController extends React.Component { - // static getDerivedStateFromProps(nextProps, prevState) { - // console.log('getDerivedStateFromProps'); - // if (nextProps !== prevState.props) { - // nextProps.aggregateComments(nextProps.review.id, nextProps.review.comments); - // } - // return { - // props: nextProps, - // }; - // } - - constructor(props) { - super(props); - console.log('pr-review-container-constructor'); - } componentDidMount() { this.props.aggregateComments(this.props.review.id, this.props.review.comments); - console.log('comments!!!', this.props.review.comments); this._attemptToLoadMoreComments(); } @@ -27,8 +12,7 @@ export class ReviewCommentsController extends React.Component { this.props.relay.loadMore( 100, error => { - this.props.aggregateComments(this.props.review.id, this.props.review.comments); - console.log('comments!!!', this.props.review.comments); + console.log('loaded more comments!', this.props.review.comments.edges); this._attemptToLoadMoreComments(); if (error) { console.log(error); diff --git a/lib/containers/pr-reviews-container.js b/lib/containers/pr-reviews-container.js index 8de768a770..a07dad5251 100644 --- a/lib/containers/pr-reviews-container.js +++ b/lib/containers/pr-reviews-container.js @@ -6,8 +6,10 @@ export default createPaginationContainer(PullRequestReviewsController, { pullRequest: graphql` fragment prReviewsContainer_pullRequest on PullRequest @argumentDefinitions( - reviewCount: {type: "Int"}, - reviewCursor: {type: "String"} + reviewCount: {type: "Int!"}, + reviewCursor: {type: "String"}, + commentCount: {type: "Int!"}, + commentCursor: {type: "String"} ) { url reviews( @@ -56,17 +58,32 @@ export default createPaginationContainer(PullRequestReviewsController, { }; }, getVariables(props, {count, cursor}, fragmentVariables) { + // console.log('fragmentVariables -------------->', fragmentVariables); + // console.log(arguments); return { url: props.pullRequest.url, reviewCount: count, reviewCursor: cursor, + commentCount: fragmentVariables.commentCount, + commentCursor: fragmentVariables.commentCursor, }; }, query: graphql` - query prReviewsContainerQuery($reviewCount: Int, $reviewCursor: String, $url: URI!) { + query prReviewsContainerQuery( + $reviewCount: Int!, + $reviewCursor: String, + $commentCount: Int!, + $commentCursor: String, + $url: URI! + ) { resource(url: $url) { ... on PullRequest { - ...prReviewsContainer_pullRequest @arguments(reviewCount: $reviewCount, reviewCursor: $reviewCursor) + ...prReviewsContainer_pullRequest @arguments( + reviewCount: $reviewCount, + reviewCursor: $reviewCursor, + commentCount: $commentCount, + commentCursor: $commentCursor + ) } } } diff --git a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js index 3c9b629202..12a4a5d6e8 100644 --- a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js +++ b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js @@ -171,25 +171,25 @@ return { }, { "kind": "LocalArgument", - "name": "commentCount", + "name": "reviewCount", "type": "Int!", "defaultValue": null }, { "kind": "LocalArgument", - "name": "commentCursor", + "name": "reviewCursor", "type": "String", "defaultValue": null }, { "kind": "LocalArgument", - "name": "reviewCount", + "name": "commentCount", "type": "Int!", "defaultValue": null }, { "kind": "LocalArgument", - "name": "reviewCursor", + "name": "commentCursor", "type": "String", "defaultValue": null } @@ -329,5 +329,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'f24ee4210befb63664396cf05d2afa79'; +(node/*: any*/).hash = 'e348cf91ee76425342abf4aa3c60b143'; module.exports = node; diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 75ef71c32d..697a1f0e96 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -297,10 +297,10 @@ export default createFragmentContainer(BareIssueishDetailController, { timelineCursor: {type: "String"}, commitCount: {type: "Int!"}, commitCursor: {type: "String"}, - commentCount: {type: "Int!"}, - commentCursor: {type: "String"}, reviewCount: {type: "Int!"}, reviewCursor: {type: "String"}, + commentCount: {type: "Int!"}, + commentCursor: {type: "String"}, ) { ...issueDetailView_repository ...prDetailView_repository @@ -340,10 +340,10 @@ export default createFragmentContainer(BareIssueishDetailController, { timelineCursor: $timelineCursor, commitCount: $commitCount, commitCursor: $commitCursor, - commentCount: $commentCount, - commentCursor: $commentCursor, reviewCount: $reviewCount, reviewCursor: $reviewCursor, + commentCount: $commentCount, + commentCursor: $commentCursor, ) } } diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 79b3c32fb7..3e3ee359de 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -34,6 +34,7 @@ export default class PullRequestReviewsController extends React.Component { 100, error => { this._attemptToLoadMoreReviews(); + console.log('loaded more reviews!', this.props.pullRequest.reviews.edges); if (error) { console.log(error); } @@ -83,7 +84,6 @@ export default class PullRequestReviewsController extends React.Component { // Aggregate comments from all reviews return this.props.pullRequest.reviews.edges.map(({node}) => { const review = node; - console.log('!!!! review in PrReviewsController', review); return ( { this.setState({refreshing: false}); }, {force: true}); @@ -378,10 +380,10 @@ export default createRefetchContainer(BarePullRequestDetailView, { timelineCursor: {type: "String"}, commitCount: {type: "Int!"}, commitCursor: {type: "String"}, + reviewCount: {type: "Int!"}, + reviewCursor: {type: "String"}, commentCount: {type: "Int!"}, commentCursor: {type: "String"}, - reviewCount: {type: "Int"}, - reviewCursor: {type: "String"} ) { __typename @@ -393,7 +395,12 @@ export default createRefetchContainer(BarePullRequestDetailView, { isCrossRepository changedFiles - ...prReviewsContainer_pullRequest @arguments(reviewCount: $reviewCount, reviewCursor: $reviewCursor) + ...prReviewsContainer_pullRequest @arguments( + reviewCount: $reviewCount, + reviewCursor: $reviewCursor, + commentCount: $commentCount, + commentCursor: $commentCursor, + ) ...prCommitsView_pullRequest @arguments(commitCount: $commitCount, commitCursor: $commitCursor) countedCommits: commits { @@ -428,10 +435,10 @@ export default createRefetchContainer(BarePullRequestDetailView, { $timelineCursor: String, $commitCount: Int!, $commitCursor: String, + $reviewCount: Int!, + $reviewCursor: String, $commentCount: Int!, $commentCursor: String, - $reviewCount: Int, - $reviewCursor: String ) { repository:node(id: $repoId) { ...prDetailView_repository @arguments( @@ -446,10 +453,10 @@ export default createRefetchContainer(BarePullRequestDetailView, { timelineCursor: $timelineCursor, commitCount: $commitCount, commitCursor: $commitCursor, + reviewCount: $reviewCount, + reviewCursor: $reviewCursor, commentCount: $commentCount, commentCursor: $commentCursor, - reviewCount: $reviewCount, - reviewCursor: $reviewCursor ) } } diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index a7434538bb..abf70c729e 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -43,7 +43,6 @@ export default class PullRequestCommentsView extends React.Component { {comments.map(comment => { - console.log(comment.body); return ( Date: Fri, 28 Dec 2018 15:59:30 -0800 Subject: [PATCH 1640/4053] Fix proptype error Co-Authored-By: Tilde Ann Thurium --- lib/views/pr-review-comments-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index abf70c729e..1247585ee0 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -19,7 +19,7 @@ export default class PullRequestCommentsView extends React.Component { isLoading: PropTypes.func.isRequired, }).isRequired, commentThreads: PropTypes.arrayOf(PropTypes.shape({ - rootCommentId: PropTypes.number.isRequired, + rootCommentId: PropTypes.string.isRequired, comments: PropTypes.arrayOf(PropTypes.object).isRequired, })), multiFilePatch: PropTypes.object.isRequired, From 4fc8e7da2274a4cf7c4cb1684672bf8d25bf551e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 28 Dec 2018 16:23:19 -0800 Subject: [PATCH 1641/4053] Add PAGE_SIZE const to helper file Co-Authored-By: Tilde Ann Thurium --- lib/containers/issueish-detail-container.js | 10 +++++----- lib/containers/pr-review-comments-container.js | 4 +++- lib/controllers/pr-reviews-controller.js | 3 ++- lib/helpers.js | 1 + lib/views/pr-commits-view.js | 4 +--- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/containers/issueish-detail-container.js b/lib/containers/issueish-detail-container.js index fc326181ce..7cd560d3fb 100644 --- a/lib/containers/issueish-detail-container.js +++ b/lib/containers/issueish-detail-container.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import yubikiri from 'yubikiri'; import {QueryRenderer, graphql} from 'react-relay'; -import {autobind} from '../helpers'; +import {autobind, PAGE_SIZE} from '../helpers'; import RelayNetworkLayerManager from '../relay-network-layer-manager'; import {GithubLoginModelPropType, ItemTypePropType, EndpointPropType} from '../prop-types'; import {UNAUTHENTICATED, INSUFFICIENT} from '../shared/keytar-strategy'; @@ -146,13 +146,13 @@ export default class IssueishDetailContainer extends React.Component { repoOwner: this.props.owner, repoName: this.props.repo, issueishNumber: this.props.issueishNumber, - timelineCount: 100, + timelineCount: PAGE_SIZE, timelineCursor: null, - commitCount: 100, + commitCount: PAGE_SIZE, commitCursor: null, - reviewCount: 2, + reviewCount: PAGE_SIZE, reviewCursor: null, - commentCount: 2, + commentCount: PAGE_SIZE, commentCursor: null, }; diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 175ace49a0..c55576b955 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -1,6 +1,8 @@ import {graphql, createPaginationContainer} from 'react-relay'; import React from 'react'; +import {PAGE_SIZE} from '../helpers'; + export class ReviewCommentsController extends React.Component { componentDidMount() { @@ -10,7 +12,7 @@ export class ReviewCommentsController extends React.Component { _loadMoreComments = () => { this.props.relay.loadMore( - 100, + PAGE_SIZE, error => { console.log('loaded more comments!', this.props.review.comments.edges); this._attemptToLoadMoreComments(); diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 3e3ee359de..86b33bb965 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -4,6 +4,7 @@ import {RelayConnectionPropType} from '../prop-types'; import PullRequestReviewCommentsContainer from '../containers/pr-review-comments-container'; import PullRequestReviewCommentsView from '../views/pr-review-comments-view'; +import {PAGE_SIZE} from '../helpers'; export default class PullRequestReviewsController extends React.Component { static propTypes = { @@ -31,7 +32,7 @@ export default class PullRequestReviewsController extends React.Component { _loadMoreReviews = () => { this.props.relay.loadMore( - 100, + PAGE_SIZE, error => { this._attemptToLoadMoreReviews(); console.log('loaded more reviews!', this.props.pullRequest.reviews.edges); diff --git a/lib/helpers.js b/lib/helpers.js index 74a77f9460..c45375b232 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -8,6 +8,7 @@ import RefHolder from './models/ref-holder'; export const LINE_ENDING_REGEX = /\r?\n/; export const CO_AUTHOR_REGEX = /^co-authored-by. (.+?) <(.+?)>$/i; +export const PAGE_SIZE = 50; export function autobind(self, ...methods) { for (const method of methods) { diff --git a/lib/views/pr-commits-view.js b/lib/views/pr-commits-view.js index 5398c58ad1..0ae3e6a76d 100644 --- a/lib/views/pr-commits-view.js +++ b/lib/views/pr-commits-view.js @@ -4,9 +4,7 @@ import {graphql, createPaginationContainer} from 'react-relay'; import {RelayConnectionPropType} from '../prop-types'; import PrCommitView from './pr-commit-view'; -import {autobind} from '../helpers'; - -const PAGE_SIZE = 50; +import {autobind, PAGE_SIZE} from '../helpers'; export class PrCommitsView extends React.Component { static propTypes = { From d215b328c0e0420b7015a0cea4dea65b99ffeded Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 28 Dec 2018 16:23:40 -0800 Subject: [PATCH 1642/4053] Clean up logs Co-Authored-By: Tilde Ann Thurium --- lib/containers/pr-review-comments-container.js | 1 - lib/containers/pr-reviews-container.js | 2 -- lib/controllers/pr-reviews-controller.js | 1 - lib/views/pr-review-comments-view.js | 1 - 4 files changed, 5 deletions(-) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index c55576b955..031ae77653 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -14,7 +14,6 @@ export class ReviewCommentsController extends React.Component { this.props.relay.loadMore( PAGE_SIZE, error => { - console.log('loaded more comments!', this.props.review.comments.edges); this._attemptToLoadMoreComments(); if (error) { console.log(error); diff --git a/lib/containers/pr-reviews-container.js b/lib/containers/pr-reviews-container.js index a07dad5251..b0bc33a95b 100644 --- a/lib/containers/pr-reviews-container.js +++ b/lib/containers/pr-reviews-container.js @@ -58,8 +58,6 @@ export default createPaginationContainer(PullRequestReviewsController, { }; }, getVariables(props, {count, cursor}, fragmentVariables) { - // console.log('fragmentVariables -------------->', fragmentVariables); - // console.log(arguments); return { url: props.pullRequest.url, reviewCount: count, diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 86b33bb965..1e1d8c2f11 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -35,7 +35,6 @@ export default class PullRequestReviewsController extends React.Component { PAGE_SIZE, error => { this._attemptToLoadMoreReviews(); - console.log('loaded more reviews!', this.props.pullRequest.reviews.edges); if (error) { console.log(error); } diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index 1247585ee0..286615ab76 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -26,7 +26,6 @@ export default class PullRequestCommentsView extends React.Component { } render() { - console.log('!!!! comment threads', this.props.commentThreads); return [...this.props.commentThreads].reverse().map(({rootCommentId, comments}) => { const rootComment = comments[0]; if (!rootComment.position) { From 839583e36712d6c672f98365a94eb4bcbecb5ece Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 28 Dec 2018 16:26:11 -0800 Subject: [PATCH 1643/4053] Add PAGINATION_WAIT_TIME_MS const to helper file Co-Authored-By: Tilde Ann Thurium --- lib/containers/pr-review-comments-container.js | 4 ++-- lib/controllers/pr-reviews-controller.js | 4 ++-- lib/helpers.js | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 031ae77653..c58d0ac7ee 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -1,7 +1,7 @@ import {graphql, createPaginationContainer} from 'react-relay'; import React from 'react'; -import {PAGE_SIZE} from '../helpers'; +import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../helpers'; export class ReviewCommentsController extends React.Component { @@ -30,7 +30,7 @@ export class ReviewCommentsController extends React.Component { if (this.props.relay.isLoading()) { setTimeout(() => { this._loadMoreComments(); - }, 300); + }, PAGINATION_WAIT_TIME_MS); } else { this._loadMoreComments(); } diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 1e1d8c2f11..b67df0c3f8 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -4,7 +4,7 @@ import {RelayConnectionPropType} from '../prop-types'; import PullRequestReviewCommentsContainer from '../containers/pr-review-comments-container'; import PullRequestReviewCommentsView from '../views/pr-review-comments-view'; -import {PAGE_SIZE} from '../helpers'; +import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../helpers'; export default class PullRequestReviewsController extends React.Component { static propTypes = { @@ -50,7 +50,7 @@ export default class PullRequestReviewsController extends React.Component { if (this.props.relay.isLoading()) { setTimeout(() => { this._loadMoreReviews(); - }, 300); + }, PAGINATION_WAIT_TIME_MS); } else { this._loadMoreReviews(); } diff --git a/lib/helpers.js b/lib/helpers.js index c45375b232..0b76f1bc71 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -9,6 +9,7 @@ import RefHolder from './models/ref-holder'; export const LINE_ENDING_REGEX = /\r?\n/; export const CO_AUTHOR_REGEX = /^co-authored-by. (.+?) <(.+?)>$/i; export const PAGE_SIZE = 50; +export const PAGINATION_WAIT_TIME_MS = 100; export function autobind(self, ...methods) { for (const method of methods) { From dd39ae5b561f91ef98457057ca144be3a851f338 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 28 Dec 2018 16:47:58 -0800 Subject: [PATCH 1644/4053] Add explanatory comments Co-Authored-By: Tilde Ann Thurium --- lib/controllers/pr-reviews-controller.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index b67df0c3f8..38479ad8b3 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import {RelayConnectionPropType} from '../prop-types'; @@ -57,12 +57,14 @@ export default class PullRequestReviewsController extends React.Component { } aggregateComments = (reviewId, comments) => { + // react batches calls to setState and does not update state synchronously + // therefore we need an intermediate state so we can do checks against keys + // we have just added. const state = this.state; comments.edges.forEach(({node}) => { const comment = node; if (!comment.replyTo) { state[comment.id] = [comment]; - // this.setState({[comment.id]: [comment]}); } else { // When comment being replied to is outdated...?? Not 100% sure... // Why would we even get an outdated comment or a response to one here? @@ -71,7 +73,6 @@ export default class PullRequestReviewsController extends React.Component { // who's replyTo comment is an outdated comment if (!state[comment.replyTo.id]) { state[comment.id] = [comment]; - // this.setState({[comment.id]: [comment]}); } else { state[comment.replyTo.id].push(comment); } @@ -80,7 +81,7 @@ export default class PullRequestReviewsController extends React.Component { this.setState(state); } - renderReviewCommentContainers() { + renderCommentFetchingContainers() { // Aggregate comments from all reviews return this.props.pullRequest.reviews.edges.map(({node}) => { const review = node; @@ -106,11 +107,20 @@ export default class PullRequestReviewsController extends React.Component { }; }); + /** Slightly hacky thing to deal with comment threading... + * + * Threads can have comments belonging to multiple reviews. + * We need a nested pagination container to fetch comment pages. + * Upon fetching new comments, the `aggregateComments` method is called with the comments to add. + * Ultimately we want to organize comments based on the root comment they are replies to. + * So `renderCommentFetchingContainers` simply fetches data and doesn't render any DOM elements. + * `PullRequestReviewCommentsView` renders the comment thread data aggregated. + * */ return ( -
    - {this.renderReviewCommentContainers()} + + {this.renderCommentFetchingContainers()} -
    +
    ); } } From 75da755b4a5fd426f0125865b77dd0877276094f Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 28 Dec 2018 16:49:46 -0800 Subject: [PATCH 1645/4053] :fire: unused `body` field on review comment Co-Authored-By: Tilde Ann Thurium --- .../issueishDetailContainerQuery.graphql.js | 59 +++---- .../prReviewCommentsContainerQuery.graphql.js | 12 +- ...rReviewCommentsContainer_review.graphql.js | 10 +- .../prReviewsContainerQuery.graphql.js | 35 ++-- .../pr-review-comments-container.js | 2 - .../prDetailViewRefetchQuery.graphql.js | 167 +++++++++--------- 6 files changed, 129 insertions(+), 156 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 5158fd1965..6f0b3544a4 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 89eb63ec03a0c0f750b90f024809ee03 + * @relayHash 8593181024464fe44267e78f2c66b059 */ /* eslint-disable */ @@ -557,7 +557,6 @@ fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { id } } - body bodyHTML path position @@ -1144,14 +1143,7 @@ v36 = { v18 ] }, -v37 = { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null -}, -v38 = [ +v37 = [ { "kind": "ScalarField", "alias": null, @@ -1161,7 +1153,7 @@ v38 = [ }, v2 ], -v39 = [ +v38 = [ { "kind": "Variable", "name": "after", @@ -1175,21 +1167,21 @@ v39 = [ "type": "Int" } ], -v40 = { +v39 = { "kind": "ScalarField", "alias": null, "name": "path", "args": null, "storageKey": null }, -v41 = { +v40 = { "kind": "ScalarField", "alias": null, "name": "position", "args": null, "storageKey": null }, -v42 = { +v41 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -1197,9 +1189,9 @@ v42 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v38 + "selections": v37 }, -v43 = { +v42 = { "kind": "LinkedField", "alias": null, "name": "actor", @@ -1214,7 +1206,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1573,7 +1565,13 @@ return { "plural": false, "selections": [ v2, - v37, + { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null + }, { "kind": "LinkedField", "alias": "commitId", @@ -1582,7 +1580,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v38 + "selections": v37 }, v11, { @@ -1621,7 +1619,7 @@ return { "alias": null, "name": "comments", "storageKey": null, - "args": v39, + "args": v38, "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ @@ -1647,10 +1645,9 @@ return { "selections": [ v2, v26, - v37, v12, + v39, v40, - v41, { "kind": "LinkedField", "alias": null, @@ -1676,7 +1673,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "comments", - "args": v39, + "args": v38, "handle": "connection", "key": "PrReviewCommentsContainer_comments", "filters": null @@ -1877,7 +1874,7 @@ return { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v42, + v41, { "kind": "LinkedField", "alias": null, @@ -1923,11 +1920,11 @@ return { "plural": false, "selections": v23 }, - v42, + v41, v12, v27, - v40, - v41 + v39, + v40 ] } ] @@ -1940,7 +1937,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v43, + v42, { "kind": "LinkedField", "alias": null, @@ -1949,7 +1946,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v38 + "selections": v37 }, { "kind": "LinkedField", @@ -1959,7 +1956,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v38 + "selections": v37 }, v27 ] @@ -1968,8 +1965,8 @@ return { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v43, v42, + v41, { "kind": "ScalarField", "alias": null, diff --git a/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js index dc46be1e33..6fc33f5de0 100644 --- a/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 5658256f086fc990e6f8c58966b65c77 + * @relayHash aa28e03673267c061792b18ed9e13038 */ /* eslint-disable */ @@ -61,7 +61,6 @@ fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { id } } - body bodyHTML path position @@ -139,7 +138,7 @@ return { "operationKind": "query", "name": "prReviewCommentsContainerQuery", "id": null, - "text": "query prReviewCommentsContainerQuery(\n $commentCount: Int!\n $commentCursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on PullRequestReview {\n ...prReviewCommentsContainer_review_1VbUmL\n }\n id\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "text": "query prReviewCommentsContainerQuery(\n $commentCount: Int!\n $commentCursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on PullRequestReview {\n ...prReviewCommentsContainer_review_1VbUmL\n }\n id\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -292,13 +291,6 @@ return { v3 ] }, - { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null - }, { "kind": "ScalarField", "alias": null, diff --git a/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js b/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js index 5a674efbcc..7f1b8d16e1 100644 --- a/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js +++ b/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js @@ -25,7 +25,6 @@ export type prReviewCommentsContainer_review = {| +avatarUrl: any, +login: string, |}, - +body: string, +bodyHTML: any, +path: string, +position: ?number, @@ -167,13 +166,6 @@ return { } ] }, - { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null - }, { "kind": "ScalarField", "alias": null, @@ -238,5 +230,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'd4b785b8fc4e5b4bedc5aa92491f37e7'; +(node/*: any*/).hash = 'd2db363653fb73e8f4d019026668babc'; module.exports = node; diff --git a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js index edf519cbfc..63254fbff3 100644 --- a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 4118118a2d92572dc108cf48c14c067f + * @relayHash a2fda52ce9272b72cf214c6bcd70719a */ /* eslint-disable */ @@ -106,7 +106,6 @@ fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { id } } - body bodyHTML path position @@ -231,27 +230,20 @@ v7 = { "storageKey": null }, v8 = { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null -}, -v9 = { "kind": "ScalarField", "alias": null, "name": "login", "args": null, "storageKey": null }, -v10 = { +v9 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", "args": null, "storageKey": null }, -v11 = [ +v10 = [ { "kind": "Variable", "name": "after", @@ -270,7 +262,7 @@ return { "operationKind": "query", "name": "prReviewsContainerQuery", "id": null, - "text": "query prReviewsContainerQuery(\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_y4qc0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "text": "query prReviewsContainerQuery(\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_y4qc0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -379,7 +371,13 @@ return { "plural": false, "selections": [ v3, - v8, + { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null + }, { "kind": "LinkedField", "alias": "commitId", @@ -423,7 +421,7 @@ return { "plural": false, "selections": [ v2, - v9, + v8, v3 ] }, @@ -437,7 +435,7 @@ return { "plural": false, "selections": [ v2, - v10, + v9, v3 ] }, @@ -446,7 +444,7 @@ return { "alias": null, "name": "comments", "storageKey": null, - "args": v11, + "args": v10, "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ @@ -481,12 +479,11 @@ return { "plural": false, "selections": [ v2, - v10, v9, + v8, v3 ] }, - v8, { "kind": "ScalarField", "alias": null, @@ -539,7 +536,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "comments", - "args": v11, + "args": v10, "handle": "connection", "key": "PrReviewCommentsContainer_comments", "filters": null diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index c58d0ac7ee..a06c19c52b 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -4,7 +4,6 @@ import React from 'react'; import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../helpers'; export class ReviewCommentsController extends React.Component { - componentDidMount() { this.props.aggregateComments(this.props.review.id, this.props.review.comments); this._attemptToLoadMoreComments(); @@ -66,7 +65,6 @@ export default createPaginationContainer(ReviewCommentsController, { avatarUrl login } - body bodyHTML path position diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index 73318ce8b6..d75b7040e7 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 6c0ffc5e033e9c921a9287900c81cc20 + * @relayHash 7e3a37960cf0fb44d162883d95c9838d */ /* eslint-disable */ @@ -473,7 +473,6 @@ fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { id } } - body bodyHTML path position @@ -692,14 +691,7 @@ v18 = { "args": null, "storageKey": null }, -v19 = { - "kind": "ScalarField", - "alias": null, - "name": "body", - "args": null, - "storageKey": null -}, -v20 = [ +v19 = [ { "kind": "ScalarField", "alias": null, @@ -709,21 +701,21 @@ v20 = [ }, v6 ], -v21 = { +v20 = { "kind": "ScalarField", "alias": null, "name": "state", "args": null, "storageKey": null }, -v22 = { +v21 = { "kind": "ScalarField", "alias": null, "name": "avatarUrl", "args": null, "storageKey": null }, -v23 = [ +v22 = [ { "kind": "Variable", "name": "after", @@ -737,13 +729,13 @@ v23 = [ "type": "Int" } ], -v24 = [ +v23 = [ v5, - v22, + v21, v8, v6 ], -v25 = { +v24 = { "kind": "LinkedField", "alias": null, "name": "author", @@ -751,37 +743,37 @@ v25 = { "args": null, "concreteType": null, "plural": false, - "selections": v24 + "selections": v23 }, -v26 = { +v25 = { "kind": "ScalarField", "alias": null, "name": "bodyHTML", "args": null, "storageKey": null }, -v27 = { +v26 = { "kind": "ScalarField", "alias": null, "name": "path", "args": null, "storageKey": null }, -v28 = { +v27 = { "kind": "ScalarField", "alias": null, "name": "position", "args": null, "storageKey": null }, -v29 = { +v28 = { "kind": "ScalarField", "alias": null, "name": "createdAt", "args": null, "storageKey": null }, -v30 = [ +v29 = [ { "kind": "Variable", "name": "after", @@ -795,7 +787,7 @@ v30 = [ "type": "Int" } ], -v31 = { +v30 = { "kind": "LinkedField", "alias": null, "name": "pageInfo", @@ -808,14 +800,14 @@ v31 = { v15 ] }, -v32 = { +v31 = { "kind": "ScalarField", "alias": "sha", "name": "oid", "args": null, "storageKey": null }, -v33 = [ +v32 = [ { "kind": "ScalarField", "alias": null, @@ -824,17 +816,17 @@ v33 = [ "storageKey": null } ], -v34 = { +v33 = { "kind": "ScalarField", "alias": null, "name": "title", "args": null, "storageKey": null }, -v35 = [ +v34 = [ v13 ], -v36 = [ +v35 = [ { "kind": "Variable", "name": "after", @@ -848,13 +840,13 @@ v36 = [ "type": "Int" } ], -v37 = [ +v36 = [ v5, v8, - v22, + v21, v6 ], -v38 = { +v37 = { "kind": "LinkedField", "alias": null, "name": "commit", @@ -862,9 +854,9 @@ v38 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v20 + "selections": v19 }, -v39 = { +v38 = { "kind": "LinkedField", "alias": null, "name": "actor", @@ -872,9 +864,9 @@ v39 = { "args": null, "concreteType": null, "plural": false, - "selections": v24 + "selections": v23 }, -v40 = { +v39 = { "kind": "LinkedField", "alias": null, "name": "user", @@ -892,7 +884,7 @@ return { "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n body\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1059,7 +1051,13 @@ return { "plural": false, "selections": [ v6, - v19, + { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null + }, { "kind": "LinkedField", "alias": "commitId", @@ -1068,9 +1066,9 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v20 + "selections": v19 }, - v21, + v20, { "kind": "ScalarField", "alias": null, @@ -1098,7 +1096,7 @@ return { "plural": false, "selections": [ v5, - v22, + v21, v6 ] }, @@ -1107,7 +1105,7 @@ return { "alias": null, "name": "comments", "storageKey": null, - "args": v23, + "args": v22, "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ @@ -1132,11 +1130,10 @@ return { "plural": false, "selections": [ v6, + v24, v25, - v19, v26, v27, - v28, { "kind": "LinkedField", "alias": null, @@ -1149,7 +1146,7 @@ return { v6 ] }, - v29, + v28, v13, v5 ] @@ -1162,7 +1159,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "comments", - "args": v23, + "args": v22, "handle": "connection", "key": "PrReviewCommentsContainer_comments", "filters": null @@ -1188,11 +1185,11 @@ return { "alias": null, "name": "commits", "storageKey": null, - "args": v30, + "args": v29, "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v31, + v30, { "kind": "LinkedField", "alias": null, @@ -1231,7 +1228,7 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v22, + v21, v7, { "kind": "ScalarField", @@ -1263,7 +1260,7 @@ return { "args": null, "storageKey": null }, - v32, + v31, v13 ] }, @@ -1279,7 +1276,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v30, + "args": v29, "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -1292,7 +1289,7 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v33 + "selections": v32 }, { "kind": "LinkedField", @@ -1346,7 +1343,7 @@ return { "concreteType": "Status", "plural": false, "selections": [ - v21, + v20, { "kind": "LinkedField", "alias": null, @@ -1357,7 +1354,7 @@ return { "plural": true, "selections": [ v6, - v21, + v20, { "kind": "ScalarField", "alias": null, @@ -1394,9 +1391,9 @@ return { } ] }, - v21, - v34, - v26, + v20, + v33, + v25, { "kind": "ScalarField", "alias": null, @@ -1422,17 +1419,17 @@ return { "selections": [ v5, v8, - v22, + v21, v6, { "kind": "InlineFragment", "type": "Bot", - "selections": v35 + "selections": v34 }, { "kind": "InlineFragment", "type": "User", - "selections": v35 + "selections": v34 } ] }, @@ -1464,11 +1461,11 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v36, + "args": v35, "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v31, + v30, { "kind": "LinkedField", "alias": null, @@ -1510,7 +1507,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v37 + "selections": v36 }, { "kind": "LinkedField", @@ -1549,7 +1546,7 @@ return { "type": "PullRequest", "selections": [ v11, - v34, + v33, v13, { "kind": "ScalarField", @@ -1565,7 +1562,7 @@ return { "type": "Issue", "selections": [ v11, - v34, + v33, v13, { "kind": "ScalarField", @@ -1584,7 +1581,7 @@ return { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v38, + v37, { "kind": "LinkedField", "alias": null, @@ -1628,13 +1625,13 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v37 + "selections": v36 }, - v38, + v37, + v25, + v28, v26, - v29, - v27, - v28 + v27 ] } ] @@ -1647,7 +1644,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v39, + v38, { "kind": "LinkedField", "alias": null, @@ -1656,7 +1653,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v20 + "selections": v19 }, { "kind": "LinkedField", @@ -1666,17 +1663,17 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v20 + "selections": v19 }, - v29 + v28 ] }, { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v39, v38, + v37, { "kind": "ScalarField", "alias": null, @@ -1684,16 +1681,16 @@ return { "args": null, "storageKey": null }, - v29 + v28 ] }, { "kind": "InlineFragment", "type": "IssueComment", "selections": [ + v24, v25, - v26, - v29, + v28, v13 ] }, @@ -1711,8 +1708,8 @@ return { "plural": false, "selections": [ v7, - v40, - v22 + v39, + v21 ] }, { @@ -1725,8 +1722,8 @@ return { "plural": false, "selections": [ v7, - v22, - v40 + v21, + v39 ] }, { @@ -1736,7 +1733,7 @@ return { "args": null, "storageKey": null }, - v32, + v31, { "kind": "ScalarField", "alias": null, @@ -1770,7 +1767,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v36, + "args": v35, "handle": "connection", "key": "prTimelineContainer_timeline", "filters": null @@ -1799,7 +1796,7 @@ return { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v33 + "selections": v32 } ] } From 6cc0b89530d047831c52079f916b968b93f830d8 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 28 Dec 2018 16:50:18 -0800 Subject: [PATCH 1646/4053] :fire: unused prop Co-Authored-By: Tilde Ann Thurium --- lib/views/pr-review-comments-view.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index 286615ab76..7d437ec71d 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import {Point, Range} from 'atom'; -import {RelayConnectionPropType} from '../prop-types'; import {toNativePathSep} from '../helpers'; import Marker from '../atom/marker'; From 3c21f20c0ef2ad29049bbc1123615066a9b093a8 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 28 Dec 2018 17:21:22 -0800 Subject: [PATCH 1647/4053] Get rid of hack for ensuring comment threads appear in the right order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No idea why this stopped being necessary... ¯\_(ツ)_/¯ Co-Authored-By: Tilde Ann Thurium --- lib/views/pr-review-comments-view.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index 7d437ec71d..f14da94c3c 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -8,8 +8,6 @@ import Decoration from '../atom/decoration'; import GithubDotcomMarkdown from './github-dotcom-markdown'; import Timeago from './timeago'; -let count = 0; - export default class PullRequestCommentsView extends React.Component { static propTypes = { relay: PropTypes.shape({ @@ -38,7 +36,7 @@ export default class PullRequestCommentsView extends React.Component { // TODO: find way to re-use nodes by using same key. this count++ hack is in place to get the comments to show up // in the correct order after new pages of data are fetched. Test it by reducing the reviewCount to a small number return ( - + {comments.map(comment => { return ( From 287b609981c62a548c034216d0949a26f1bc1c0a Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 2 Jan 2019 18:00:02 +0900 Subject: [PATCH 1648/4053] Add borders to comment threads --- lib/views/pr-review-comments-view.js | 2 +- styles/pr-comment.less | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index f14da94c3c..279e4deb51 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -59,7 +59,7 @@ export class PullRequestCommentView extends React.Component { const author = this.props.comment.author; const login = author ? author.login : 'someone'; return ( -
    +
    {login} {login} commented{' '} diff --git a/styles/pr-comment.less b/styles/pr-comment.less index 427dd1a0b3..65887aef25 100644 --- a/styles/pr-comment.less +++ b/styles/pr-comment.less @@ -2,18 +2,21 @@ @avatar-size: 16px; + +.github-PrCommentThread { + padding: @component-padding/2 0; + border-bottom: 1px solid @base-border-color; +} + + .github-PrComment { + max-width: 60em; + margin: @component-padding 0; + padding-right: @component-padding*2; font-family: @font-family; font-size: @font-size; - &-wrapper { - max-width: 60em; - padding-right: @component-padding*2; - padding-bottom: @component-padding; - } - &-header { - padding: @component-padding 0 @component-padding/2 0; color: @text-color-subtle; } From 2e3a244578d6e8e652868c94b9e79dc18b79ad22 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 2 Jan 2019 13:41:57 +0100 Subject: [PATCH 1649/4053] fix some linter errors --- lib/containers/pr-review-comments-container.js | 18 +++++++++++++++++- lib/controllers/multi-file-patch-controller.js | 2 ++ lib/controllers/pr-reviews-controller.js | 3 ++- lib/views/multi-file-patch-view.js | 9 ++++++++- lib/views/pr-review-comments-view.js | 15 +++++++++++++++ 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index a06c19c52b..d0a7ca7c31 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -1,9 +1,24 @@ import {graphql, createPaginationContainer} from 'react-relay'; +import PropTypes from 'prop-types'; import React from 'react'; import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../helpers'; export class ReviewCommentsController extends React.Component { + + static propTypes = { + aggregateComments: PropTypes.func.isRequired, + review: PropTypes.shape({ + id: PropTypes.number.isRequired, + comments: PropTypes.arrayOf(PropTypes.object).isRequired, + }), + relay: PropTypes.shape({ + hasMore: PropTypes.func.isRequired, + loadMore: PropTypes.func.isRequired, + isLoading: PropTypes.func.isRequired, + }).isRequired, + } + componentDidMount() { this.props.aggregateComments(this.props.review.id, this.props.review.comments); this._attemptToLoadMoreComments(); @@ -15,7 +30,8 @@ export class ReviewCommentsController extends React.Component { error => { this._attemptToLoadMoreComments(); if (error) { - console.log(error); + // eslint-disable-next-line no-console + console.error(error); } }, ); diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 0214aa2db9..449747a3d0 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -25,6 +25,8 @@ export default class MultiFilePatchController extends React.Component { discardLines: PropTypes.func, undoLastDiscard: PropTypes.func, surface: PropTypes.func, + + switchToIssueish: PropTypes.func, } constructor(props) { diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 38479ad8b3..963c9d33bf 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -36,7 +36,8 @@ export default class PullRequestReviewsController extends React.Component { error => { this._attemptToLoadMoreReviews(); if (error) { - console.log(error); + // eslint-disable-next-line no-console + console.error(error); } }, ); diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 7296aee04d..80e46a0b86 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -47,8 +47,10 @@ export default class MultiFilePatchView extends React.Component { keymaps: PropTypes.object.isRequired, tooltips: PropTypes.object.isRequired, config: PropTypes.object.isRequired, + pullRequest: PropTypes.object, selectedRowsChanged: PropTypes.func, + switchToIssueish: PropTypes.func, diveIntoMirrorPatch: PropTypes.func, surface: PropTypes.func, @@ -313,7 +315,12 @@ export default class MultiFilePatchView extends React.Component { renderPullRequestReviews() { if (this.props.itemType === IssueishDetailItem) { - return ; + return ( + ); } else { return null; } diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index 279e4deb51..963abbe122 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -20,6 +20,7 @@ export default class PullRequestCommentsView extends React.Component { comments: PropTypes.arrayOf(PropTypes.object).isRequired, })), multiFilePatch: PropTypes.object.isRequired, + switchToIssueish: PropTypes.func.isRequired, } render() { @@ -55,6 +56,20 @@ export default class PullRequestCommentsView extends React.Component { } export class PullRequestCommentView extends React.Component { + + static propTypes = { + switchToIssueish: PropTypes.func.isRequired, + comment: PropTypes.shape({ + author: PropTypes.shape({ + avatarUrl: PropTypes.string, + login: PropTypes.string, + }), + bodyHTML: PropTypes.string, + url: PropTypes.string, + createdAt: PropTypes.string.isRequired, + }).isRequired, + } + render() { const author = this.props.comment.author; const login = author ? author.login : 'someone'; From 7c14d0c75f5886773e95320d7f533acb7d5fd623 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 2 Jan 2019 14:22:22 +0100 Subject: [PATCH 1650/4053] make tests run --- lib/containers/pr-review-comments-container.js | 2 +- test/views/pr-comments-view.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index d0a7ca7c31..6e634e3ca3 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -9,7 +9,7 @@ export class ReviewCommentsController extends React.Component { static propTypes = { aggregateComments: PropTypes.func.isRequired, review: PropTypes.shape({ - id: PropTypes.number.isRequired, + id: PropTypes.string.isRequired, comments: PropTypes.arrayOf(PropTypes.object).isRequired, }), relay: PropTypes.shape({ diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index ccb7b78673..7be8aa440a 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -3,7 +3,7 @@ import {shallow} from 'enzyme'; import {multiFilePatchBuilder} from '../builder/patch'; import {pullRequestBuilder} from '../builder/pr'; -import PullRequestCommentsView, {PullRequestCommentView} from '../../lib/views/pr-comments-view'; +import PullRequestCommentsView, {PullRequestCommentView} from '../../lib/views/pr-review-comments-view'; describe('PullRequestCommentsView', function() { it('adjusts the position for comments after hunk headers', function() { From 1bdb59e02ae4106d6e995bb0eb0ce025a35cc4dd Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 2 Jan 2019 16:44:24 +0100 Subject: [PATCH 1651/4053] compute comment threads in pr builder helper --- test/builder/pr.js | 23 +++++++++++++++++++++++ test/views/pr-comments-view.test.js | 5 +++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/test/builder/pr.js b/test/builder/pr.js index 24758ece0b..63352c8e8b 100644 --- a/test/builder/pr.js +++ b/test/builder/pr.js @@ -8,6 +8,7 @@ class CommentBuilder { this._url = 'https://github.com/atom/github/pull/1829/files#r242224689'; this._createdAt = 0; this._body = 'Lorem ipsum dolor sit amet, te urbanitas appellantur est.'; + this._replyTo = null; } id(i) { @@ -50,6 +51,11 @@ class CommentBuilder { return this; } + replyTo(replyToId) { + this._replyTo = replyToId; + return this; + } + build() { return { id: this._id, @@ -62,6 +68,7 @@ class CommentBuilder { position: this._position, createdAt: this._createdAt, url: this._url, + replyTo: this._replyTo, }; } } @@ -115,8 +122,24 @@ class PullRequestBuilder { } build() { + const commentThreads = {}; + this._reviews.forEach(review => { + review.comments.nodes.forEach(comment => { + if (comment.replyTo && commentThreads[comment.replyTo]) { + commentThreads[comment.replyTo].push(comment); + } else { + commentThreads[comment.id] = [comment]; + } + }); + }); return { reviews: {nodes: this._reviews}, + commentThreads: Object.keys(commentThreads).map(rootCommentId => { + return { + rootCommentId, + comments: commentThreads[rootCommentId], + }; + }), }; } } diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index 7be8aa440a..75e5f4d7fc 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -31,12 +31,13 @@ describe('PullRequestCommentsView', function() { }) .build(); - const wrapper = shallow(); + const wrapper = shallow(); assert.deepEqual(wrapper.find('Marker').at(0).prop('bufferRange').serialize(), [[1, 0], [1, 0]]); assert.deepEqual(wrapper.find('Marker').at(1).prop('bufferRange').serialize(), [[12, 0], [12, 0]]); assert.deepEqual(wrapper.find('Marker').at(2).prop('bufferRange').serialize(), [[20, 0], [20, 0]]); }); + it('does not render comment if position is null', function() { const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(fp => { @@ -56,7 +57,7 @@ describe('PullRequestCommentsView', function() { }) .build(); - const wrapper = shallow(); + const wrapper = shallow(); const comments = wrapper.find('PullRequestCommentView'); assert.lengthOf(comments, 1); From 304ff66fae7d8c84df8a3443f938c74ece7e64c2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 2 Jan 2019 11:12:05 -0500 Subject: [PATCH 1652/4053] Fork codecov-node so apm isn't unhappy with the devDependency format --- package-lock.json | 49 ++++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 712f5bcef8..eba0c11b99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -214,6 +214,31 @@ "tmp": "0.0.31" } }, + "@smashwilson/codecov": { + "version": "3.1.1-azure0.0", + "resolved": "https://registry.npmjs.org/@smashwilson/codecov/-/codecov-3.1.1-azure0.0.tgz", + "integrity": "sha512-ctT6EDZ+uQpZhixzTJoWBOF32ZKEaAg+h4iVApEhTMxtLFNYzGSepz6hS5enIXzxMzQiMEzZgVUqGtk1G7kRXg==", + "dev": true, + "requires": { + "argv": "^0.0.2", + "ignore-walk": "^3.0.1", + "js-yaml": "^3.12.0", + "teeny-request": "^3.7.0", + "urlgrey": "^0.4.4" + }, + "dependencies": { + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, "@types/node": { "version": "10.12.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.12.tgz", @@ -2024,30 +2049,6 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, - "codecov": { - "version": "github:codecov/codecov-node#e427d900309adb50746a39a50aa7d80071a5ddd0", - "from": "github:codecov/codecov-node#e427d90", - "dev": true, - "requires": { - "argv": "^0.0.2", - "ignore-walk": "^3.0.1", - "js-yaml": "^3.12.0", - "teeny-request": "^3.7.0", - "urlgrey": "^0.4.4" - }, - "dependencies": { - "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - } - } - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", diff --git a/package.json b/package.json index 6ab320fd1c..1504a16ad7 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "babel-plugin-istanbul": "4.1.6", "chai": "4.1.2", "chai-as-promised": "7.1.1", - "codecov": "codecov/codecov-node#e427d90", + "@smashwilson/codecov": "3.1.1-azure0.0", "cross-env": "5.2.0", "cross-unzip": "0.2.1", "dedent-js": "1.0.1", From 94b8d95d4ddf2d3de781de799dfd429665182743 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 2 Jan 2019 11:13:16 -0500 Subject: [PATCH 1653/4053] Prepare 0.24.0-0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index eba0c11b99..cdae93d690 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.23.0", + "version": "0.24.0-0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1504a16ad7..16bb4a45fa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.23.0", + "version": "0.24.0-0", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From 378a113a8188820415b4fcb1f2b4c9806c2f1de6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 2 Jan 2019 13:03:26 -0500 Subject: [PATCH 1654/4053] Unit test to catch an empty repository --- test/models/user-store.test.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/test/models/user-store.test.js b/test/models/user-store.test.js index 6d5d255928..0d70bfd23c 100644 --- a/test/models/user-store.test.js +++ b/test/models/user-store.test.js @@ -38,6 +38,7 @@ describe('UserStore', function() { const opts = { owner: 'me', name: 'stuff', + repositoryFound: true, ...options, }; @@ -50,7 +51,7 @@ describe('UserStore', function() { name: 'GetMentionableUsers', variables: {owner: opts.owner, name: opts.name, first: 100, after: lastCursor}, }, { - repository: { + repository: !opts.repositoryFound ? null : { mentionableUsers: { nodes: page, pageInfo: { @@ -173,6 +174,27 @@ describe('UserStore', function() { ]); }); + it('skips GitHub remotes that no longer exist', async function() { + await login.setToken('https://api.github.com', '1234'); + + const workdirPath = await cloneRepository('multiple-commits'); + const repository = await buildRepository(workdirPath); + + await repository.setConfig('remote.origin.url', 'git@github.com:me/stuff.git'); + await repository.setConfig('remote.origin.fetch', '+refs/heads/*:refs/remotes/origin/*'); + + const [{resolve, promise}] = expectPagedRelayQueries({repositoryFound: false}, []); + + store = new UserStore({repository, login, config}); + await nextUpdatePromise(); + + resolve(); + // nextUpdatePromise will not fire because the update is empty + await promise; + + assert.deepEqual(store.getUsers(), []); + }); + it('infers no-reply emails for users without a public email address', async function() { await login.setToken('https://api.github.com', '1234'); @@ -406,8 +428,8 @@ describe('UserStore', function() { const actualToken = await store.getToken(loginModel, 'https://api.github.com'); assert.strictEqual(expectedToken, actualToken); }); - }); + describe('loadMentionableUsers', function() { it('returns undefined if token is null', async function() { const workdirPath = await cloneRepository('multiple-commits'); From fe93f9b7c8cee857ba5dfd9a0df009d44103ed91 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 2 Jan 2019 13:03:34 -0500 Subject: [PATCH 1655/4053] Break on a missing repository --- lib/models/user-store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/user-store.js b/lib/models/user-store.js index ae11ba48a2..30a8c97355 100644 --- a/lib/models/user-store.js +++ b/lib/models/user-store.js @@ -175,7 +175,7 @@ export default class UserStore { console.error(`Error fetching mentionable users:\n${response.errors.map(e => e.message).join('\n')}`); } - if (!response.data) { + if (!response.data || !response.data.repository) { break; } From c2da60107a46e4154aa51e9f209da05d75d271fb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 2 Jan 2019 14:20:17 -0500 Subject: [PATCH 1656/4053] Prepare 0.24.0-1 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index cdae93d690..bcac3e3ae6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.24.0-0", + "version": "0.24.0-1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 16bb4a45fa..86bae59b72 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.24.0-0", + "version": "0.24.0-1", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From 84fb79a907fba92f41c34db02108e530bfb889f7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 2 Jan 2019 15:17:00 -0500 Subject: [PATCH 1657/4053] Sinon has a `.resolves()` helper to save some typing --- test/models/github-login-model.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/models/github-login-model.test.js b/test/models/github-login-model.test.js index 816c9ce428..bfc2240263 100644 --- a/test/models/github-login-model.test.js +++ b/test/models/github-login-model.test.js @@ -47,19 +47,19 @@ describe('GithubLoginModel', function() { }); it('returns INSUFFICIENT if scopes are present', async function() { - sinon.stub(loginModel, 'getScopes').returns(Promise.resolve(['repo', 'read:org'])); + sinon.stub(loginModel, 'getScopes').resolves(['repo', 'read:org']); assert.strictEqual(await loginModel.getToken('https://api.github.com'), INSUFFICIENT); }); it('returns the token if at least the required scopes are present', async function() { - sinon.stub(loginModel, 'getScopes').returns(Promise.resolve(['repo', 'read:org', 'user:email', 'extra'])); + sinon.stub(loginModel, 'getScopes').resolves(['repo', 'read:org', 'user:email', 'extra']); assert.strictEqual(await loginModel.getToken('https://api.github.com'), '1234'); }); it('caches checked tokens', async function() { - sinon.stub(loginModel, 'getScopes').returns(Promise.resolve(['repo', 'read:org', 'user:email'])); + sinon.stub(loginModel, 'getScopes').resolves(['repo', 'read:org', 'user:email']); assert.strictEqual(await loginModel.getToken('https://api.github.com'), '1234'); assert.strictEqual(loginModel.getScopes.callCount, 1); From cf36d8a6c0b25cccbb6738aa642378694f7cc3f6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 2 Jan 2019 15:17:12 -0500 Subject: [PATCH 1658/4053] Tests for failure caching --- test/models/github-login-model.test.js | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/models/github-login-model.test.js b/test/models/github-login-model.test.js index bfc2240263..d770ac317f 100644 --- a/test/models/github-login-model.test.js +++ b/test/models/github-login-model.test.js @@ -67,5 +67,35 @@ describe('GithubLoginModel', function() { assert.strictEqual(await loginModel.getToken('https://api.github.com'), '1234'); assert.strictEqual(loginModel.getScopes.callCount, 1); }); + + it('caches tokens that failed to authenticate correctly', async function() { + sinon.stub(loginModel, 'getScopes').resolves(GithubLoginModel.UNAUTHORIZED); + + assert.strictEqual(await loginModel.getToken('https://api.github.com'), UNAUTHENTICATED); + assert.strictEqual(loginModel.getScopes.callCount, 1); + + assert.strictEqual(await loginModel.getToken('https://api.github.com'), UNAUTHENTICATED); + assert.strictEqual(loginModel.getScopes.callCount, 1); + }); + + it('caches tokens that had insufficient scopes', async function() { + sinon.stub(loginModel, 'getScopes').resolves(['repo', 'read:org']); + + assert.strictEqual(await loginModel.getToken('https://api.github.com'), INSUFFICIENT); + assert.strictEqual(loginModel.getScopes.callCount, 1); + + assert.strictEqual(await loginModel.getToken('https://api.github.com'), INSUFFICIENT); + assert.strictEqual(loginModel.getScopes.callCount, 1); + }); + + it('does not cache network errors', async function() { + sinon.stub(loginModel, 'getScopes').rejects(new Error('You unplugged your ethernet cable')); + + assert.strictEqual(await loginModel.getToken('https://api.github.com'), UNAUTHENTICATED); + assert.strictEqual(loginModel.getScopes.callCount, 1); + + assert.strictEqual(await loginModel.getToken('https://api.github.com'), UNAUTHENTICATED); + assert.strictEqual(loginModel.getScopes.callCount, 2); + }); }); }); From 92849a97068d7c131cca7a6b5514fe70018e9e1a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 2 Jan 2019 15:17:49 -0500 Subject: [PATCH 1659/4053] Cache the outcome of the scope check regardless of success or failure --- lib/models/github-login-model.js | 34 +++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/models/github-login-model.js b/lib/models/github-login-model.js index 99554e64bd..04e02d3dd6 100644 --- a/lib/models/github-login-model.js +++ b/lib/models/github-login-model.js @@ -10,6 +10,9 @@ export default class GithubLoginModel { // give everyone a really frustrating experience ;-) static REQUIRED_SCOPES = ['repo', 'read:org', 'user:email'] + // Returned from getScopes if the HEAD request fails with a 401 + static UNAUTHORIZED = Symbol('unauthorized') + static get() { if (!instance) { instance = new GithubLoginModel(); @@ -21,7 +24,7 @@ export default class GithubLoginModel { this._Strategy = Strategy; this._strategy = null; this.emitter = new Emitter(); - this.checked = new Set(); + this.checked = new Map(); } async getStrategy() { @@ -53,21 +56,33 @@ export default class GithubLoginModel { hash.update(password); const fingerprint = hash.digest('base64'); - if (!this.checked.has(fingerprint)) { + const outcome = this.checked.get(fingerprint); + if (outcome === UNAUTHENTICATED || outcome === INSUFFICIENT) { + // Cached failure + return outcome; + } else if (!outcome) { + // No cached outcome. Query for scopes. try { - const scopes = new Set(await this.getScopes(account, password)); + const scopes = await this.getScopes(account, password); + if (scopes === this.constructor.UNAUTHORIZED) { + // password is incorrect + this.checked.set(fingerprint, UNAUTHENTICATED); + return UNAUTHENTICATED; + } + const scopeSet = new Set(scopes); for (const scope of this.constructor.REQUIRED_SCOPES) { - if (!scopes.has(scope)) { + if (!scopeSet.has(scope)) { // Token doesn't have enough OAuth scopes, need to reauthenticate + this.checked.set(fingerprint, INSUFFICIENT); return INSUFFICIENT; } } - // We're good - this.checked.add(fingerprint); + // Successfully authenticated and had all required scopes. + this.checked.set(fingerprint, true); } catch (e) { - // Bad credential most likely + // Most likely a network error. Do not cache the failure. // eslint-disable-next-line no-console console.error(`Unable to validate token scopes against ${account}`, e); return UNAUTHENTICATED; @@ -104,6 +119,11 @@ export default class GithubLoginModel { headers: {Authorization: `bearer ${token}`}, }); + if (response.status === 401) { + // Unauthorized + return this.constructor.UNAUTHORIZED; + } + if (response.status !== 200) { throw new Error(`Unable to check token for OAuth scopes against ${host}: ${await response.text()}`); } From ea391fb3425ef864e30999eedf6c2810d22949e7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 2 Jan 2019 15:44:06 -0500 Subject: [PATCH 1660/4053] getScopes() exists to do a `fetch`, skip it for coverage --- lib/models/github-login-model.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/models/github-login-model.js b/lib/models/github-login-model.js index 04e02d3dd6..394dbfd71f 100644 --- a/lib/models/github-login-model.js +++ b/lib/models/github-login-model.js @@ -105,6 +105,7 @@ export default class GithubLoginModel { this.didUpdate(); } + /* istanbul ignore next */ async getScopes(host, token) { if (atom.inSpecMode()) { if (token === 'good-token') { From 3b61634adf5339a29e8e9ca0ead8909a590e96af Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 2 Jan 2019 13:03:23 -0800 Subject: [PATCH 1661/4053] :fire: stale todo --- lib/views/pr-review-comments-view.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index 963abbe122..ea926a730d 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -34,8 +34,6 @@ export default class PullRequestCommentsView extends React.Component { const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, rootComment.position); const point = new Point(row, 0); const range = new Range(point, point); - // TODO: find way to re-use nodes by using same key. this count++ hack is in place to get the comments to show up - // in the correct order after new pages of data are fetched. Test it by reducing the reviewCount to a small number return ( From 97fad343dc2d6691b0c0b1a17c65fa261c029ca6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 2 Jan 2019 21:49:07 +0000 Subject: [PATCH 1662/4053] chore(package): update electron-link to version 0.3.2 Closes #1861 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 86bae59b72..b2f0b0ca8f 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "cross-unzip": "0.2.1", "dedent-js": "1.0.1", "electron-devtools-installer": "2.2.4", - "electron-link": "0.2.2", + "electron-link": "0.3.2", "electron-mksnapshot": "3.0.0-beta.1", "enzyme": "3.8.0", "enzyme-adapter-react-16": "1.7.1", From 9e5047d4f9bda961b3fcf3fd6dacfba4677fc1b8 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 2 Jan 2019 21:49:10 +0000 Subject: [PATCH 1663/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index bcac3e3ae6..390a2f75e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1643,9 +1643,9 @@ } }, "bindings": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", - "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.1.tgz", + "integrity": "sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==", "dev": true }, "bl": { @@ -2667,19 +2667,27 @@ } }, "electron-link": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/electron-link/-/electron-link-0.2.2.tgz", - "integrity": "sha1-uWvx/MrowwyAuiaTBq+UVOYtP2U=", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/electron-link/-/electron-link-0.3.2.tgz", + "integrity": "sha512-V7QmtujzWgvrW5BI2CKmIRF+q+pkrFO5Lecd8TpibbBz+FfW5WQ4kCN0sZjNaUOMtGGroCib721OqIDEynjwgA==", "dev": true, "requires": { "ast-util": "^0.6.0", "encoding-down": "~5.0.0", - "indent-string": "^2.1.0", + "indent-string": "^3.2.0", "leveldown": "~4.0.0", "levelup": "~3.0.0", "recast": "^0.12.6", "resolve": "^1.5.0", "source-map": "^0.5.6" + }, + "dependencies": { + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + } } }, "electron-mksnapshot": { @@ -6968,12 +6976,12 @@ } }, "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", + "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", "dev": true, "requires": { - "path-parse": "^1.0.5" + "path-parse": "^1.0.6" } }, "resolve-from": { From 9a81c9a1434c07a37caca80fd1884bcc66be13d7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 3 Jan 2019 08:45:20 -0500 Subject: [PATCH 1664/4053] Move UNAUTHORIZED to keytar-strategy with the rest of the token states --- lib/models/github-login-model.js | 12 ++++-------- lib/shared/keytar-strategy.js | 6 ++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/models/github-login-model.js b/lib/models/github-login-model.js index 394dbfd71f..ab95551915 100644 --- a/lib/models/github-login-model.js +++ b/lib/models/github-login-model.js @@ -1,7 +1,7 @@ import crypto from 'crypto'; import {Emitter} from 'event-kit'; -import {UNAUTHENTICATED, INSUFFICIENT, createStrategy} from '../shared/keytar-strategy'; +import {UNAUTHENTICATED, INSUFFICIENT, UNAUTHORIZED, createStrategy} from '../shared/keytar-strategy'; let instance = null; @@ -10,9 +10,6 @@ export default class GithubLoginModel { // give everyone a really frustrating experience ;-) static REQUIRED_SCOPES = ['repo', 'read:org', 'user:email'] - // Returned from getScopes if the HEAD request fails with a 401 - static UNAUTHORIZED = Symbol('unauthorized') - static get() { if (!instance) { instance = new GithubLoginModel(); @@ -64,8 +61,8 @@ export default class GithubLoginModel { // No cached outcome. Query for scopes. try { const scopes = await this.getScopes(account, password); - if (scopes === this.constructor.UNAUTHORIZED) { - // password is incorrect + if (scopes === UNAUTHORIZED) { + // Password is incorrect. Treat it as though you aren't authenticated at all. this.checked.set(fingerprint, UNAUTHENTICATED); return UNAUTHENTICATED; } @@ -121,8 +118,7 @@ export default class GithubLoginModel { }); if (response.status === 401) { - // Unauthorized - return this.constructor.UNAUTHORIZED; + return UNAUTHORIZED; } if (response.status !== 200) { diff --git a/lib/shared/keytar-strategy.js b/lib/shared/keytar-strategy.js index 86dcea24e2..51aa16c3d3 100644 --- a/lib/shared/keytar-strategy.js +++ b/lib/shared/keytar-strategy.js @@ -16,10 +16,15 @@ if (typeof atom === 'undefined') { }; } +// No token available in your OS keychain. const UNAUTHENTICATED = Symbol('UNAUTHENTICATED'); +// The token in your keychain isn't granted all of the required OAuth scopes. const INSUFFICIENT = Symbol('INSUFFICIENT'); +// The token in your keychain is not accepted by GitHub. +const UNAUTHORIZED = Symbol('UNAUTHORIZED'); + class KeytarStrategy { static get keytar() { return require('keytar'); @@ -248,6 +253,7 @@ async function createStrategy() { module.exports = { UNAUTHENTICATED, INSUFFICIENT, + UNAUTHORIZED, KeytarStrategy, SecurityBinaryStrategy, InMemoryStrategy, From e8f41f1c240161576cd74921f3fae339ba7954d6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 3 Jan 2019 08:55:58 -0500 Subject: [PATCH 1665/4053] Use UNAUTHORIZED constant in GithubLoginModel tests --- test/models/github-login-model.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/models/github-login-model.test.js b/test/models/github-login-model.test.js index d770ac317f..e4d2f754be 100644 --- a/test/models/github-login-model.test.js +++ b/test/models/github-login-model.test.js @@ -5,6 +5,7 @@ import { InMemoryStrategy, UNAUTHENTICATED, INSUFFICIENT, + UNAUTHORIZED, } from '../../lib/shared/keytar-strategy'; describe('GithubLoginModel', function() { @@ -69,7 +70,7 @@ describe('GithubLoginModel', function() { }); it('caches tokens that failed to authenticate correctly', async function() { - sinon.stub(loginModel, 'getScopes').resolves(GithubLoginModel.UNAUTHORIZED); + sinon.stub(loginModel, 'getScopes').resolves(UNAUTHORIZED); assert.strictEqual(await loginModel.getToken('https://api.github.com'), UNAUTHENTICATED); assert.strictEqual(loginModel.getScopes.callCount, 1); From 9b61d09f2287acba52590a0a0178c63e7e8c88b6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 3 Jan 2019 09:47:29 -0500 Subject: [PATCH 1666/4053] Give the "show more" button a left margin --- styles/pr-commit-view.less | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/styles/pr-commit-view.less b/styles/pr-commit-view.less index f39b4b3e33..eab4fbbe89 100644 --- a/styles/pr-commit-view.less +++ b/styles/pr-commit-view.less @@ -3,14 +3,6 @@ @default-padding: @component-padding; @avatar-dimensions: 16px; -.github-PrCommitsView { - - &-load-more-button { - display: block; - margin: 20px auto 0; - } -} - .github-PrCommitView { &-container { @@ -72,7 +64,7 @@ } &-moreButton { - border: none; + margin: 0 auto 0 @default-padding; padding: 0em .2em; color: @text-color-subtle; font-style: italic; From 358efdc64937534ff0c6b902bce68ec3a45ecb5e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 3 Jan 2019 16:01:49 +0100 Subject: [PATCH 1667/4053] make title texts in PR details tabs not selectable --- styles/issueish-detail-view.less | 1 + 1 file changed, 1 insertion(+) diff --git a/styles/issueish-detail-view.less b/styles/issueish-detail-view.less index 89781361f1..704b4de843 100644 --- a/styles/issueish-detail-view.less +++ b/styles/issueish-detail-view.less @@ -156,6 +156,7 @@ font-weight: 600; color: mix(@text-color, @app-background-color, 75%); cursor: default; + user-select: none; } &-tab-icon { From 2630e7aada692bce3946774781a93ac9384c9d6a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 3 Jan 2019 11:33:30 -0500 Subject: [PATCH 1668/4053] Prepare 0.24.0-2 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bcac3e3ae6..df8492f3da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.24.0-1", + "version": "0.24.0-2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 86bae59b72..1effff30f5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.24.0-1", + "version": "0.24.0-2", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From 10fb0861626700eefa9b7c47229117f6a53433b7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 3 Jan 2019 12:41:47 -0500 Subject: [PATCH 1669/4053] Prepare 0.24.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index df8492f3da..76cc2d0cbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "github", - "version": "0.24.0-2", + "version": "0.24.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1effff30f5..b5fd068b65 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "github", "main": "./lib/index", - "version": "0.24.0-2", + "version": "0.24.0", "description": "GitHub integration", "repository": "https://github.com/atom/github", "license": "MIT", From f47c68b7ae3456a62c763eedec139895841b027f Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 2 Jan 2019 12:08:46 -0800 Subject: [PATCH 1670/4053] follow bare container component naming convention --- lib/containers/pr-review-comments-container.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 6e634e3ca3..d1e8789740 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -4,7 +4,7 @@ import React from 'react'; import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../helpers'; -export class ReviewCommentsController extends React.Component { +export class BarePullRequestReviewCommentsContainer extends React.Component { static propTypes = { aggregateComments: PropTypes.func.isRequired, @@ -56,7 +56,7 @@ export class ReviewCommentsController extends React.Component { } } -export default createPaginationContainer(ReviewCommentsController, { +export default createPaginationContainer(BarePullRequestReviewCommentsContainer, { review: graphql` fragment prReviewCommentsContainer_review on PullRequestReview @argumentDefinitions( From 72af9951a97da9b1df7c62df0aa25c63cc9cfe87 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 3 Jan 2019 15:37:28 -0800 Subject: [PATCH 1671/4053] Fix issue with comment grouping Previously, we were not grouping comments correctly if there were multiple pages of comments. The best approach we found was to wait until we have all reviews and all comments, and then group them by root comment ID. Co-Authored-By: Katrina Uychaco --- .../issueishDetailContainerQuery.graphql.js | 5 +- .../prReviewCommentsContainerQuery.graphql.js | 12 ++- ...rReviewCommentsContainer_review.graphql.js | 10 ++- .../prReviewsContainerQuery.graphql.js | 5 +- .../pr-review-comments-container.js | 9 ++- lib/controllers/pr-reviews-controller.js | 73 ++++++++++++------- .../prDetailViewRefetchQuery.graphql.js | 5 +- 7 files changed, 80 insertions(+), 39 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 6f0b3544a4..0ee982a7d9 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 8593181024464fe44267e78f2c66b059 + * @relayHash 86a6d997a1bfd3d4f567397ecdaa752b */ /* eslint-disable */ @@ -540,6 +540,7 @@ fragment prCommitView_item on Commit { fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { id + submittedAt comments(first: $commentCount, after: $commentCursor) { pageInfo { hasNextPage @@ -1206,7 +1207,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", diff --git a/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js index 6fc33f5de0..0904ea42ba 100644 --- a/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash aa28e03673267c061792b18ed9e13038 + * @relayHash 5e0b465f16f55322d70100cd893a1c04 */ /* eslint-disable */ @@ -44,6 +44,7 @@ query prReviewCommentsContainerQuery( fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { id + submittedAt comments(first: $commentCount, after: $commentCursor) { pageInfo { hasNextPage @@ -138,7 +139,7 @@ return { "operationKind": "query", "name": "prReviewCommentsContainerQuery", "id": null, - "text": "query prReviewCommentsContainerQuery(\n $commentCount: Int!\n $commentCursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on PullRequestReview {\n ...prReviewCommentsContainer_review_1VbUmL\n }\n id\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "text": "query prReviewCommentsContainerQuery(\n $commentCount: Int!\n $commentCursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on PullRequestReview {\n ...prReviewCommentsContainer_review_1VbUmL\n }\n id\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -204,6 +205,13 @@ return { "kind": "InlineFragment", "type": "PullRequestReview", "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "submittedAt", + "args": null, + "storageKey": null + }, { "kind": "LinkedField", "alias": null, diff --git a/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js b/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js index 7f1b8d16e1..aec8134452 100644 --- a/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js +++ b/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js @@ -12,6 +12,7 @@ import type { FragmentReference } from "relay-runtime"; declare export opaque type prReviewCommentsContainer_review$ref: FragmentReference; export type prReviewCommentsContainer_review = {| +id: string, + +submittedAt: ?any, +comments: {| +pageInfo: {| +hasNextPage: boolean, @@ -81,6 +82,13 @@ return { ], "selections": [ v0, + { + "kind": "ScalarField", + "alias": null, + "name": "submittedAt", + "args": null, + "storageKey": null + }, { "kind": "LinkedField", "alias": "comments", @@ -230,5 +238,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'd2db363653fb73e8f4d019026668babc'; +(node/*: any*/).hash = '63492273ddd049ed59809581c7795811'; module.exports = node; diff --git a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js index 63254fbff3..7e64650ea5 100644 --- a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash a2fda52ce9272b72cf214c6bcd70719a + * @relayHash 455845bc2fe2987a2c32d72dbc9247b6 */ /* eslint-disable */ @@ -89,6 +89,7 @@ fragment prReviewsContainer_pullRequest_y4qc0 on PullRequest { fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { id + submittedAt comments(first: $commentCount, after: $commentCursor) { pageInfo { hasNextPage @@ -262,7 +263,7 @@ return { "operationKind": "query", "name": "prReviewsContainerQuery", "id": null, - "text": "query prReviewsContainerQuery(\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_y4qc0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "text": "query prReviewsContainerQuery(\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_y4qc0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index d1e8789740..67151ff784 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -7,9 +7,10 @@ import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../helpers'; export class BarePullRequestReviewCommentsContainer extends React.Component { static propTypes = { - aggregateComments: PropTypes.func.isRequired, + collectComments: PropTypes.func.isRequired, review: PropTypes.shape({ id: PropTypes.string.isRequired, + submittedAt: PropTypes.string.isRequired, comments: PropTypes.arrayOf(PropTypes.object).isRequired, }), relay: PropTypes.shape({ @@ -20,14 +21,17 @@ export class BarePullRequestReviewCommentsContainer extends React.Component { } componentDidMount() { - this.props.aggregateComments(this.props.review.id, this.props.review.comments); this._attemptToLoadMoreComments(); + const {submittedAt, comments, id} = this.props.review; + this.props.collectComments({reviewId: id, submittedAt, comments, hasMore: this.props.relay.hasMore()}); } _loadMoreComments = () => { this.props.relay.loadMore( PAGE_SIZE, error => { + const {submittedAt, comments, id} = this.props.review; + this.props.collectComments({reviewId: id, submittedAt, comments, hasMore: this.props.relay.hasMore()}); this._attemptToLoadMoreComments(); if (error) { // eslint-disable-next-line no-console @@ -64,6 +68,7 @@ export default createPaginationContainer(BarePullRequestReviewCommentsContainer, commentCursor: {type: "String"} ) { id + submittedAt comments( first: $commentCount, after: $commentCursor diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 963c9d33bf..dca64a9bfa 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -24,6 +24,7 @@ export default class PullRequestReviewsController extends React.Component { constructor(props) { super(props); this.state = {}; + this.reviewsById = new Map(); } componentDidMount() { @@ -57,40 +58,15 @@ export default class PullRequestReviewsController extends React.Component { } } - aggregateComments = (reviewId, comments) => { - // react batches calls to setState and does not update state synchronously - // therefore we need an intermediate state so we can do checks against keys - // we have just added. - const state = this.state; - comments.edges.forEach(({node}) => { - const comment = node; - if (!comment.replyTo) { - state[comment.id] = [comment]; - } else { - // When comment being replied to is outdated...?? Not 100% sure... - // Why would we even get an outdated comment or a response to one here? - // Ran into this error when viewing files for https://github.com/numpy/numpy/pull/9998 - // for comment MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE1MzA1NTUzMw, - // who's replyTo comment is an outdated comment - if (!state[comment.replyTo.id]) { - state[comment.id] = [comment]; - } else { - state[comment.replyTo.id].push(comment); - } - } - }); - this.setState(state); - } - renderCommentFetchingContainers() { - // Aggregate comments from all reviews - return this.props.pullRequest.reviews.edges.map(({node}) => { + this.props.pullRequest.reviews.edges.map(({node}) => { const review = node; + return ( ); }); @@ -124,4 +100,45 @@ export default class PullRequestReviewsController extends React.Component { ); } + + collectComments = ({reviewId, submittedAt, comments, hasMore}) => { + this.reviewsById.set(reviewId, {submittedAt, comments, hasMore}); + const noMoreReviewsToFetch = !this.props.relay.hasMore(); + if (noMoreReviewsToFetch) { + const noMoreCommentsToFetch = [...this.reviewsById.values()].every(review => !review.hasMore) + if (noMoreCommentsToFetch) { + this.groupCommentsByThread(); + } + } + } + + groupCommentsByThread() { + // TODO make sure they're sorted according to submitted at + + // react batches calls to setState and does not update state synchronously + // therefore we need an intermediate state so we can do checks against keys + // we have just added. + const state = {}; + [...this.reviewsById.values()].forEach(({comments}) => { + comments.edges.forEach(({node}) => { + const comment = node; + if (!comment.replyTo) { + state[comment.id] = [comment]; + } else { + // When comment being replied to is outdated...?? Not 100% sure... + // Why would we even get an outdated comment or a response to one here? + // Ran into this error when viewing files for https://github.com/numpy/numpy/pull/9998 + // for comment MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE1MzA1NTUzMw, + // who's replyTo comment is an outdated comment + if (!state[comment.replyTo.id]) { + state[comment.id] = [comment]; + } else { + state[comment.replyTo.id].push(comment); + } + } + }); + }) + + this.setState(state); + } } diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index d75b7040e7..8b5d424baa 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 7e3a37960cf0fb44d162883d95c9838d + * @relayHash 631a28049723f0b6b8a3f8eb25a4f955 */ /* eslint-disable */ @@ -456,6 +456,7 @@ fragment prCommitView_item on Commit { fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { id + submittedAt comments(first: $commentCount, after: $commentCursor) { pageInfo { hasNextPage @@ -884,7 +885,7 @@ return { "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", From 033c18d4744a6e06ce7a1d257481686d4ddef752 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 3 Jan 2019 15:59:39 -0800 Subject: [PATCH 1672/4053] render helpers actually need to return some jsx, dawg --- lib/controllers/pr-reviews-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index dca64a9bfa..6dc8050e23 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -59,7 +59,7 @@ export default class PullRequestReviewsController extends React.Component { } renderCommentFetchingContainers() { - this.props.pullRequest.reviews.edges.map(({node}) => { + return this.props.pullRequest.reviews.edges.map(({node}) => { const review = node; return ( From 065c78bff8d060a2d7efa36bedcc4090ecbfd8b6 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 3 Jan 2019 16:21:52 -0800 Subject: [PATCH 1673/4053] sort reviews by date --- lib/controllers/pr-reviews-controller.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 6dc8050e23..c5976e1a58 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -113,13 +113,24 @@ export default class PullRequestReviewsController extends React.Component { } groupCommentsByThread() { - // TODO make sure they're sorted according to submitted at + // we have no guarantees that reviews will return in order so sort them by date. + const sortedReviews = [...this.reviewsById.values()].sort((a, b) => { + const dateA = new Date(a.submittedAt); + const dateB = new Date(b.submittedAt); + if (dateA > dateB) { + return 1; + } else if (dateB > dateA) { + return -1; + } else { + return 0; + } + }); // react batches calls to setState and does not update state synchronously // therefore we need an intermediate state so we can do checks against keys // we have just added. const state = {}; - [...this.reviewsById.values()].forEach(({comments}) => { + sortedReviews.forEach(({comments}) => { comments.edges.forEach(({node}) => { const comment = node; if (!comment.replyTo) { From be7659698210dbd43742919ea6ab949099ce92b0 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 3 Jan 2019 16:26:31 -0800 Subject: [PATCH 1674/4053] :art: move sort comparator to be its own function --- lib/controllers/pr-reviews-controller.js | 25 +++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index c5976e1a58..f70ef509ea 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -101,6 +101,19 @@ export default class PullRequestReviewsController extends React.Component { ); } + // sorts reviews by date ascending (oldest to newest) + sortComparator(a, b) { + const dateA = new Date(a.submittedAt); + const dateB = new Date(b.submittedAt); + if (dateA > dateB) { + return 1; + } else if (dateB > dateA) { + return -1; + } else { + return 0; + } + } + collectComments = ({reviewId, submittedAt, comments, hasMore}) => { this.reviewsById.set(reviewId, {submittedAt, comments, hasMore}); const noMoreReviewsToFetch = !this.props.relay.hasMore(); @@ -114,17 +127,7 @@ export default class PullRequestReviewsController extends React.Component { groupCommentsByThread() { // we have no guarantees that reviews will return in order so sort them by date. - const sortedReviews = [...this.reviewsById.values()].sort((a, b) => { - const dateA = new Date(a.submittedAt); - const dateB = new Date(b.submittedAt); - if (dateA > dateB) { - return 1; - } else if (dateB > dateA) { - return -1; - } else { - return 0; - } - }); + const sortedReviews = [...this.reviewsById.values()].sort(this.sortComparator); // react batches calls to setState and does not update state synchronously // therefore we need an intermediate state so we can do checks against keys From 5706820aaca0a9ed2ed1eaf332af088f447de365 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 3 Jan 2019 16:30:12 -0800 Subject: [PATCH 1675/4053] :shirt: --- lib/controllers/pr-reviews-controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index f70ef509ea..282793e740 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -109,8 +109,8 @@ export default class PullRequestReviewsController extends React.Component { return 1; } else if (dateB > dateA) { return -1; - } else { - return 0; + } else { + return 0; } } @@ -118,7 +118,7 @@ export default class PullRequestReviewsController extends React.Component { this.reviewsById.set(reviewId, {submittedAt, comments, hasMore}); const noMoreReviewsToFetch = !this.props.relay.hasMore(); if (noMoreReviewsToFetch) { - const noMoreCommentsToFetch = [...this.reviewsById.values()].every(review => !review.hasMore) + const noMoreCommentsToFetch = [...this.reviewsById.values()].every(review => !review.hasMore); if (noMoreCommentsToFetch) { this.groupCommentsByThread(); } @@ -151,7 +151,7 @@ export default class PullRequestReviewsController extends React.Component { } } }); - }) + }); this.setState(state); } From 1c9912003d732c77d54b2ef24dae93f2e1b76ddb Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 3 Jan 2019 16:32:03 -0800 Subject: [PATCH 1676/4053] to the PropTypes mobile, batman!! --- lib/containers/pr-review-comments-container.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 67151ff784..5e5f0f24c1 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -11,7 +11,7 @@ export class BarePullRequestReviewCommentsContainer extends React.Component { review: PropTypes.shape({ id: PropTypes.string.isRequired, submittedAt: PropTypes.string.isRequired, - comments: PropTypes.arrayOf(PropTypes.object).isRequired, + comments: PropTypes.object.isRequired, }), relay: PropTypes.shape({ hasMore: PropTypes.func.isRequired, From 915947102ffeee6667ee957f24349ba5159cefce Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 3 Jan 2019 17:02:30 -0800 Subject: [PATCH 1677/4053] add some tests for `PullRequestReviewCommentsContainer` --- .../pr-review-comments-container.test.js | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 test/containers/pr-review-comments-container.test.js diff --git a/test/containers/pr-review-comments-container.test.js b/test/containers/pr-review-comments-container.test.js new file mode 100644 index 0000000000..fe83100f18 --- /dev/null +++ b/test/containers/pr-review-comments-container.test.js @@ -0,0 +1,72 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import {BarePullRequestReviewCommentsContainer} from '../../lib/containers/pr-review-comments-container'; + +describe('PullRequestReviewCommentsContainer', function() { + function buildApp(opts, overrideProps = {}) { + const o = { + relayHasMore: () => { return false; }, + relayLoadMore: () => {}, + relayIsLoading: () => { return false; }, + ...opts, + }; + + const props = { + relay: { + hasMore: o.relayHasMore, + loadMore: o.relayLoadMore, + isLoading: o.relayIsLoading, + }, + collectComments: () => {}, + review: { + id: '123', + submittedAt: '2018-12-27T20:40:55Z', + comments: {edges: ['this kiki is marvelous']}, + }, + ...overrideProps, + }; + return ; + } + it('aggregates the comments after component has been mounted', function() { + const collectCommentsStub = sinon.stub(); + shallow(buildApp({}, {collectComments: collectCommentsStub})); + assert.strictEqual(collectCommentsStub.callCount, 1); + const args = collectCommentsStub.lastCall.args[0]; + + assert.strictEqual(args.reviewId, '123'); + assert.strictEqual(args.submittedAt, '2018-12-27T20:40:55Z'); + assert.deepEqual(args.comments, {edges: ['this kiki is marvelous']}); + assert.isFalse(args.hasMore); + }); + + it('attempts to load comments after component has been mounted', function() { + const wrapper = shallow(buildApp()); + sinon.stub(wrapper.instance(), '_attemptToLoadMoreComments'); + wrapper.instance().componentDidMount(); + + }); + + + describe('loadMoreComments', function() { + it('calls this.props.relay.loadMore with correct arguments', function() { + }); + }); + + it('handles errors', function() { + + }); + + describe('attemptToLoadMoreComments', function() { + it('does not call loadMore if hasMore prop is falsy', function() { + }); + + it('calls loadMore immediately if not already loading more', function() { + }); + + it('if already loading more, calls loadMore after a timeout', function() { + }); + + }); + +}); From 3f61b5dea358101c882d83820e3408df0448f981 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 3 Jan 2019 17:57:09 -0800 Subject: [PATCH 1678/4053] add some more tests for PullRequestReviewCommentsContainer Co-Authored-By: Katrina Uychaco --- .../pr-review-comments-container.test.js | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/test/containers/pr-review-comments-container.test.js b/test/containers/pr-review-comments-container.test.js index fe83100f18..d37df302fc 100644 --- a/test/containers/pr-review-comments-container.test.js +++ b/test/containers/pr-review-comments-container.test.js @@ -1,6 +1,8 @@ import React from 'react'; import {shallow} from 'enzyme'; +import {PAGE_SIZE} from '../../lib/helpers'; + import {BarePullRequestReviewCommentsContainer} from '../../lib/containers/pr-review-comments-container'; describe('PullRequestReviewCommentsContainer', function() { @@ -28,7 +30,7 @@ describe('PullRequestReviewCommentsContainer', function() { }; return ; } - it('aggregates the comments after component has been mounted', function() { + it('collects the comments after component has been mounted', function() { const collectCommentsStub = sinon.stub(); shallow(buildApp({}, {collectComments: collectCommentsStub})); assert.strictEqual(collectCommentsStub.callCount, 1); @@ -44,19 +46,48 @@ describe('PullRequestReviewCommentsContainer', function() { const wrapper = shallow(buildApp()); sinon.stub(wrapper.instance(), '_attemptToLoadMoreComments'); wrapper.instance().componentDidMount(); - + assert.strictEqual(wrapper.instance()._attemptToLoadMoreComments.callCount, 1); }); describe('loadMoreComments', function() { - it('calls this.props.relay.loadMore with correct arguments', function() { + it('calls this.props.relay.loadMore with correct args', function() { + const relayLoadMoreStub = sinon.stub(); + const wrapper = shallow(buildApp({relayLoadMore: relayLoadMoreStub})); + wrapper.instance()._loadMoreComments(); + + const args = relayLoadMoreStub.lastCall.args; + assert.strictEqual(args[0], PAGE_SIZE); + assert.strictEqual(args[1], wrapper.instance().onDidLoadMore); }); }); - it('handles errors', function() { + describe('onDidLoadMore', function() { + it('collects comments and attempts to load more comments', function() { + const collectCommentsStub = sinon.stub(); + const wrapper = shallow(buildApp({}, {collectComments: collectCommentsStub})); + // collect comments is called when mounted, we don't care about that in this test so reset the count + collectCommentsStub.reset(); + sinon.stub(wrapper.instance(), '_attemptToLoadMoreComments'); + wrapper.instance().onDidLoadMore(); + + assert.strictEqual(collectCommentsStub.callCount, 1); + const args = collectCommentsStub.lastCall.args[0]; + + assert.strictEqual(args.reviewId, '123'); + assert.strictEqual(args.submittedAt, '2018-12-27T20:40:55Z'); + assert.deepEqual(args.comments, {edges: ['this kiki is marvelous']}); + assert.isFalse(args.hasMore); + }); + + it('handles errors', function() { + + }); + }); + describe('attemptToLoadMoreComments', function() { it('does not call loadMore if hasMore prop is falsy', function() { }); From 968ad4da85012601231f8f9d201d6bdba4854667 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 4 Jan 2019 20:07:55 +0900 Subject: [PATCH 1679/4053] Put comments into boxes --- styles/pr-comment.less | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/styles/pr-comment.less b/styles/pr-comment.less index 65887aef25..f2ff654985 100644 --- a/styles/pr-comment.less +++ b/styles/pr-comment.less @@ -1,30 +1,45 @@ @import 'variables'; @avatar-size: 16px; +@avatar-spacing: 6px; .github-PrCommentThread { - padding: @component-padding/2 0; - border-bottom: 1px solid @base-border-color; + padding: @component-padding 0; } .github-PrComment { max-width: 60em; - margin: @component-padding 0; - padding-right: @component-padding*2; + padding: @component-padding; font-family: @font-family; font-size: @font-size; + border: 1px solid @base-border-color; + + &:first-child { + border-top-left-radius: @component-border-radius; + border-top-right-radius: @component-border-radius; + } + &:last-child { + border-bottom-left-radius: @component-border-radius; + border-bottom-right-radius: @component-border-radius; + } + + & + .github-PrComment { + border-top: none; + } &-header { color: @text-color-subtle; + line-height: @avatar-size; } &-avatar { - margin-right: 4px; + margin-right: @avatar-spacing; height: @avatar-size; width: @avatar-size; border-radius: @component-border-radius; + vertical-align: top; } &-timeAgo { @@ -32,7 +47,7 @@ } &-body { - margin-left: @avatar-size + 4px; // avatar + margin + margin-left: @avatar-size + @avatar-spacing; // avatar + margin } } From c4ee2dd8704e2b18473389b1ad62ae02295c1a8e Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 4 Jan 2019 12:49:01 +0000 Subject: [PATCH 1680/4053] fix(package): update react-tabs to version 3.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b5fd068b65..087d28235f 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "react-dom": "16.4.0", "react-relay": "1.6.0", "react-select": "1.2.1", - "react-tabs": "^2.3.0", + "react-tabs": "^3.0.0", "relay-runtime": "1.6.0", "temp": "0.9.0", "tinycolor2": "1.4.1", From 65fbf592bdd95fb706b0d9710c3644fd66eec9a4 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 4 Jan 2019 12:49:05 +0000 Subject: [PATCH 1681/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 76cc2d0cbe..1fcba091f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6550,9 +6550,9 @@ } }, "react-tabs": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-2.3.0.tgz", - "integrity": "sha512-pYaefgVy76/36AMEP+B8YuVVzDHa3C5UFZ3REU78zolk0qMxEhKvUFofvDCXyLZwf0RZjxIfiwok1BEb18nHyA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-3.0.0.tgz", + "integrity": "sha512-z90cDIb+5V7MzjXFHq1VLxYiMH7dDQWan7mXSw6BWQtw+9pYAnq/fEDvsPaXNyevYitvLetdW87C61uu27JVMA==", "requires": { "classnames": "^2.2.0", "prop-types": "^15.5.0" From b36f05bcca7fd71671305dff5473c26ed9d818e7 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 4 Jan 2019 10:58:45 -0800 Subject: [PATCH 1682/4053] import PAGE_SIZE in `IssueishDetailContainer` tests --- test/containers/issueish-detail-container.test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/containers/issueish-detail-container.test.js b/test/containers/issueish-detail-container.test.js index 934f24df9f..d8323e8f89 100644 --- a/test/containers/issueish-detail-container.test.js +++ b/test/containers/issueish-detail-container.test.js @@ -5,6 +5,7 @@ import {cloneRepository, buildRepository} from '../helpers'; import {expectRelayQuery} from '../../lib/relay-network-layer-manager'; import {issueishDetailContainerProps} from '../fixtures/props/issueish-pane-props'; import {createPullRequestDetailResult} from '../fixtures/factories/pull-request-result'; +import {PAGE_SIZE} from '../../lib/helpers'; import GithubLoginModel from '../../lib/models/github-login-model'; import {InMemoryStrategy, UNAUTHENTICATED} from '../../lib/shared/keytar-strategy'; import IssueishDetailContainer from '../../lib/containers/issueish-detail-container'; @@ -26,12 +27,14 @@ describe('IssueishDetailContainer', function() { repoOwner: 'owner', repoName: 'repo', issueishNumber: 1, - timelineCount: 100, + timelineCount: PAGE_SIZE, timelineCursor: null, - commitCount: 100, + commitCount: PAGE_SIZE, commitCursor: null, - commentCount: 100, + commentCount: PAGE_SIZE, commentCursor: null, + reviewCount: PAGE_SIZE, + reviewCursor: null, }, }, { repository: { From bf3d4c17f842059ee0eaf78bb0c3087159a66f7f Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 4 Jan 2019 11:02:08 -0800 Subject: [PATCH 1683/4053] import PAGE_SIXE in checkout pr integration tests --- test/integration/checkout-pr.test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/integration/checkout-pr.test.js b/test/integration/checkout-pr.test.js index a15a1adced..6509d22b8a 100644 --- a/test/integration/checkout-pr.test.js +++ b/test/integration/checkout-pr.test.js @@ -2,6 +2,7 @@ import hock from 'hock'; import http from 'http'; import {setup, teardown} from './helpers'; +import {PAGE_SIZE} from '../../lib/helpers'; import {expectRelayQuery} from '../../lib/relay-network-layer-manager'; import GitShellOutStrategy from '../../lib/git-shell-out-strategy'; import {createRepositoryResult} from '../fixtures/factories/repository-result'; @@ -136,11 +137,13 @@ describe('integration: check out a pull request', function() { repoOwner: 'owner', repoName: 'repo', issueishNumber: 1, - timelineCount: 100, + timelineCount: PAGE_SIZE, timelineCursor: null, - commitCount: 100, + commitCount: PAGE_SIZE, commitCursor: null, - commentCount: 100, + reviewCount: PAGE_SIZE, + reviewCursor: null, + commentCount: PAGE_SIZE, commentCursor: null, }, }, result); From e927e76c1c67819762785074a4e7952b590493ed Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 4 Jan 2019 11:07:32 -0800 Subject: [PATCH 1684/4053] extract callback into its own function for better testability Co-Authored-By: Katrina Uychaco --- .../pr-review-comments-container.js | 20 ++++++++++--------- .../pr-review-comments-container.test.js | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 5e5f0f24c1..885da598ff 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -26,18 +26,20 @@ export class BarePullRequestReviewCommentsContainer extends React.Component { this.props.collectComments({reviewId: id, submittedAt, comments, hasMore: this.props.relay.hasMore()}); } + onDidLoadMore = error => { + const {submittedAt, comments, id} = this.props.review; + this.props.collectComments({reviewId: id, submittedAt, comments, hasMore: this.props.relay.hasMore()}); + this._attemptToLoadMoreComments(); + if (error) { + // eslint-disable-next-line no-console + console.error(error); + } + } + _loadMoreComments = () => { this.props.relay.loadMore( PAGE_SIZE, - error => { - const {submittedAt, comments, id} = this.props.review; - this.props.collectComments({reviewId: id, submittedAt, comments, hasMore: this.props.relay.hasMore()}); - this._attemptToLoadMoreComments(); - if (error) { - // eslint-disable-next-line no-console - console.error(error); - } - }, + this.onDidLoadMore, ); } diff --git a/test/containers/pr-review-comments-container.test.js b/test/containers/pr-review-comments-container.test.js index d37df302fc..c9477a195c 100644 --- a/test/containers/pr-review-comments-container.test.js +++ b/test/containers/pr-review-comments-container.test.js @@ -89,7 +89,7 @@ describe('PullRequestReviewCommentsContainer', function() { describe('attemptToLoadMoreComments', function() { - it('does not call loadMore if hasMore prop is falsy', function() { + it('does not call loadMore if hasMore is false', function() { }); it('calls loadMore immediately if not already loading more', function() { From 1c60c1e74d5ab3daf2e41120c25c534ef802a171 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 4 Jan 2019 14:57:01 -0500 Subject: [PATCH 1685/4053] :arrow_up: react and react-dom --- package-lock.json | 42 ++++++++++++++++++++++++++++++++---------- package.json | 4 ++-- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 76cc2d0cbe..c0f3b89033 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6461,25 +6461,47 @@ } }, "react": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.4.0.tgz", - "integrity": "sha512-K0UrkLXSAekf5nJu89obKUM7o2vc6MMN9LYoKnCa+c+8MJRAT120xzPLENcWSRc7GYKIg0LlgJRDorrufdglQQ==", + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.7.0.tgz", + "integrity": "sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==", "requires": { - "fbjs": "^0.8.16", "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "prop-types": "^15.6.0" + "prop-types": "^15.6.2", + "scheduler": "^0.12.0" + }, + "dependencies": { + "scheduler": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.12.0.tgz", + "integrity": "sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + } } }, "react-dom": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.4.0.tgz", - "integrity": "sha512-bbLd+HYpBEnYoNyxDe9XpSG2t9wypMohwQPvKw8Hov3nF7SJiJIgK56b46zHpBUpHb06a1iEuw7G3rbrsnNL6w==", + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.7.0.tgz", + "integrity": "sha512-D0Ufv1ExCAmF38P2Uh1lwpminZFRXEINJe53zRAbm4KPwSyd6DY/uDoS0Blj9jvPpn1+wivKpZYc8aAAN/nAkg==", "requires": { - "fbjs": "^0.8.16", "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "prop-types": "^15.6.0" + "prop-types": "^15.6.2", + "scheduler": "^0.12.0" + }, + "dependencies": { + "scheduler": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.12.0.tgz", + "integrity": "sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + } } }, "react-input-autosize": { diff --git a/package.json b/package.json index b5fd068b65..67c2ff5a0f 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,8 @@ "moment": "2.22.2", "node-emoji": "^1.8.1", "prop-types": "15.6.2", - "react": "16.4.0", - "react-dom": "16.4.0", + "react": "16.7.0", + "react-dom": "16.7.0", "react-relay": "1.6.0", "react-select": "1.2.1", "react-tabs": "^2.3.0", From 415435ea0fb2a8232b4251f4cb13c4cf8984fc7d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 4 Jan 2019 15:09:13 -0500 Subject: [PATCH 1686/4053] Lock electron-mksnapshot to the version used in Atom --- package-lock.json | 20 ++++++++++---------- package.json | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 390a2f75e5..4e76304d5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2650,9 +2650,9 @@ }, "dependencies": { "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { "ms": "^2.1.1" @@ -2691,9 +2691,9 @@ } }, "electron-mksnapshot": { - "version": "3.0.0-beta.1", - "resolved": "https://registry.npmjs.org/electron-mksnapshot/-/electron-mksnapshot-3.0.0-beta.1.tgz", - "integrity": "sha512-0Q4yV7jCnXiCFOAih8ZvmFBS492kPzQYtsIj30pVyWsytAEyxmyPQD5NbSAaAAJt5di2XocxfvbAEgcAhUXEqA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/electron-mksnapshot/-/electron-mksnapshot-2.0.0.tgz", + "integrity": "sha512-OoZwZJNKgHP+DwhCGVTJEuDSeb478hOzAbHeg7dKGCHDbKKmUWmjGc+pEjxGutpqQ3Mn8hCdLzdx2c/lAJcTLA==", "dev": true, "requires": { "electron-download": "^4.1.0", @@ -6340,7 +6340,7 @@ }, "pretty-bytes": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", + "resolved": "http://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", "dev": true, "requires": { @@ -7176,7 +7176,7 @@ "dependencies": { "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { @@ -7898,7 +7898,7 @@ }, "through2": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", + "resolved": "http://registry.npmjs.org/through2/-/through2-0.2.3.tgz", "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", "dev": true, "requires": { @@ -7926,7 +7926,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, diff --git a/package.json b/package.json index b2f0b0ca8f..127f8160c0 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "dedent-js": "1.0.1", "electron-devtools-installer": "2.2.4", "electron-link": "0.3.2", - "electron-mksnapshot": "3.0.0-beta.1", + "electron-mksnapshot": "~2.0", "enzyme": "3.8.0", "enzyme-adapter-react-16": "1.7.1", "eslint": "5.0.1", From fd590c2d18a7072a6726f350983fdca22353e7d1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 4 Jan 2019 15:09:25 -0500 Subject: [PATCH 1687/4053] Ignore updates to electron-link and electron-mksnapshot --- package.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.json b/package.json index 127f8160c0..52d475cf19 100644 --- a/package.json +++ b/package.json @@ -204,5 +204,11 @@ "FilePatchControllerStub": "createFilePatchControllerStub", "CommitPreviewStub": "createCommitPreviewStub", "CommitDetailStub": "createCommitDetailStub" + }, + "greenkeeper": { + "ignore": [ + "electron-link", + "electron-mksnapshot" + ] } } From 083c048b726f14964b9e4c3b5dd04c4047ffc6c3 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 4 Jan 2019 13:45:36 -0800 Subject: [PATCH 1688/4053] moar tests for `PullRequestReviewCommentsContainer` --- .../pr-review-comments-container.test.js | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/test/containers/pr-review-comments-container.test.js b/test/containers/pr-review-comments-container.test.js index c9477a195c..342909610f 100644 --- a/test/containers/pr-review-comments-container.test.js +++ b/test/containers/pr-review-comments-container.test.js @@ -1,7 +1,7 @@ import React from 'react'; import {shallow} from 'enzyme'; -import {PAGE_SIZE} from '../../lib/helpers'; +import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../../lib/helpers'; import {BarePullRequestReviewCommentsContainer} from '../../lib/containers/pr-review-comments-container'; @@ -89,15 +89,53 @@ describe('PullRequestReviewCommentsContainer', function() { describe('attemptToLoadMoreComments', function() { - it('does not call loadMore if hasMore is false', function() { + let clock; + beforeEach(function() { + clock = sinon.useFakeTimers(); + }); + afterEach(function() { + clock = sinon.restore(); }); + it('does not call loadMore if hasMore is false', function() { + const relayLoadMoreStub = sinon.stub(); + const wrapper = shallow(buildApp({relayLoadMore: relayLoadMoreStub})); + relayLoadMoreStub.reset(); - it('calls loadMore immediately if not already loading more', function() { + wrapper.instance()._attemptToLoadMoreComments(); + assert.strictEqual(relayLoadMoreStub.callCount, 0); }); - it('if already loading more, calls loadMore after a timeout', function() { + it('calls loadMore immediately if hasMore is true and isLoading is false', function() { + const relayLoadMoreStub = sinon.stub(); + const relayHasMore = () => { return true; }; + const wrapper = shallow(buildApp({relayHasMore, relayLoadMore: relayLoadMoreStub})); + relayLoadMoreStub.reset(); + + wrapper.instance()._attemptToLoadMoreComments(); + assert.strictEqual(relayLoadMoreStub.callCount, 1); + const args = relayLoadMoreStub.lastCall.args; + assert.strictEqual(args[0], PAGE_SIZE); + assert.strictEqual(args[1], wrapper.instance().onDidLoadMore); }); + it('calls loadMore after a timeout if hasMore is true and isLoading is true', function() { + const relayLoadMoreStub = sinon.stub(); + const relayHasMore = () => { return true; }; + const relayIsLoading = () => { return true; }; + const wrapper = shallow(buildApp({relayHasMore, relayIsLoading, relayLoadMore: relayLoadMoreStub})); + // advancing the timer and resetting the stub to clear the initial calls of + // _attemptToLoadMoreComments when the component is initially mounted. + clock.tick(PAGINATION_WAIT_TIME_MS); + relayLoadMoreStub.reset(); + + wrapper.instance()._attemptToLoadMoreComments(); + assert.strictEqual(relayLoadMoreStub.callCount, 0); + + clock.tick(PAGINATION_WAIT_TIME_MS); + assert.strictEqual(relayLoadMoreStub.callCount, 1); + const args = relayLoadMoreStub.lastCall.args; + assert.strictEqual(args[0], PAGE_SIZE); + assert.strictEqual(args[1], wrapper.instance().onDidLoadMore); + }); }); - }); From 1e24ba2287f0224a7e4971f546d2181ac602efee Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 4 Jan 2019 13:45:47 -0800 Subject: [PATCH 1689/4053] :fire: unnecessary arrow function --- lib/containers/pr-review-comments-container.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 885da598ff..72cda3b23f 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -49,9 +49,7 @@ export class BarePullRequestReviewCommentsContainer extends React.Component { } if (this.props.relay.isLoading()) { - setTimeout(() => { - this._loadMoreComments(); - }, PAGINATION_WAIT_TIME_MS); + setTimeout(this._loadMoreComments, PAGINATION_WAIT_TIME_MS); } else { this._loadMoreComments(); } From 5f15a181c96a62a1e804d3fd53eee8c46687b96e Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 4 Jan 2019 14:00:32 -0800 Subject: [PATCH 1690/4053] we don't need that other arrow function either --- lib/controllers/pr-reviews-controller.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 282793e740..f95135643a 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -50,9 +50,7 @@ export default class PullRequestReviewsController extends React.Component { } if (this.props.relay.isLoading()) { - setTimeout(() => { - this._loadMoreReviews(); - }, PAGINATION_WAIT_TIME_MS); + setTimeout(this._loadMoreReviews, PAGINATION_WAIT_TIME_MS); } else { this._loadMoreReviews(); } From cdc717f69361d67e632bcda72bf395a2db6173de Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 4 Jan 2019 14:21:07 -0800 Subject: [PATCH 1691/4053] pass getBufferRowForDiffPosition function as a prop we don't really need the entire multiFilePatch, dawg. --- lib/controllers/pr-reviews-controller.js | 2 +- lib/models/patch/multi-file-patch.js | 2 +- lib/views/multi-file-patch-view.js | 2 +- lib/views/pr-review-comments-view.js | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index f95135643a..85d9687cae 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -18,7 +18,7 @@ export default class PullRequestReviewsController extends React.Component { PropTypes.object, ), }), - multiFilePatch: PropTypes.object.isRequired, + getBufferRowForDiffPosition: PropTypes.func.isRequired, } constructor(props) { diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 132573214d..e04a498416 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -339,7 +339,7 @@ export default class MultiFilePatch { return false; } - getBufferRowForDiffPosition(fileName, diffRow) { + getBufferRowForDiffPosition = (fileName, diffRow) => { // TODO verify that this works on Windows const {startBufferRow, index} = this.diffRowOffsetIndices.get(fileName); const {offset} = index.lowerBound({diffRow}).data(); diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 80e46a0b86..9d0655b770 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -318,7 +318,7 @@ export default class MultiFilePatchView extends React.Component { return ( ); } else { diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index ea926a730d..7eb1276902 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -19,7 +19,7 @@ export default class PullRequestCommentsView extends React.Component { rootCommentId: PropTypes.string.isRequired, comments: PropTypes.arrayOf(PropTypes.object).isRequired, })), - multiFilePatch: PropTypes.object.isRequired, + getBufferRowForDiffPosition: PropTypes.func.isRequired, switchToIssueish: PropTypes.func.isRequired, } @@ -31,7 +31,7 @@ export default class PullRequestCommentsView extends React.Component { } const nativePath = toNativePathSep(rootComment.path); - const row = this.props.multiFilePatch.getBufferRowForDiffPosition(nativePath, rootComment.position); + const row = this.props.getBufferRowForDiffPosition(nativePath, rootComment.position); const point = new Point(row, 0); const range = new Range(point, point); return ( From 33a79c25cb7aa89c091ec900e7cbee824be11640 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 17:23:06 -0800 Subject: [PATCH 1692/4053] Istanbul ignore if statements that handle errors when loading more pages Co-Authored-By: Tilde Ann Thurium --- lib/containers/pr-review-comments-container.js | 2 ++ lib/controllers/pr-reviews-controller.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 72cda3b23f..9b3138f1fd 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -30,6 +30,8 @@ export class BarePullRequestReviewCommentsContainer extends React.Component { const {submittedAt, comments, id} = this.props.review; this.props.collectComments({reviewId: id, submittedAt, comments, hasMore: this.props.relay.hasMore()}); this._attemptToLoadMoreComments(); + + /* istanbul ignore if */ if (error) { // eslint-disable-next-line no-console console.error(error); diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 85d9687cae..62262e9130 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -36,6 +36,8 @@ export default class PullRequestReviewsController extends React.Component { PAGE_SIZE, error => { this._attemptToLoadMoreReviews(); + + /* istanbul ignore if */ if (error) { // eslint-disable-next-line no-console console.error(error); From d913c9fb8de6180836808365f7b627aff506c53d Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 17:24:13 -0800 Subject: [PATCH 1693/4053] :fire: unnecessary test for error handling Co-Authored-By: Tilde Ann Thurium --- test/containers/pr-review-comments-container.test.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/test/containers/pr-review-comments-container.test.js b/test/containers/pr-review-comments-container.test.js index 342909610f..d690000057 100644 --- a/test/containers/pr-review-comments-container.test.js +++ b/test/containers/pr-review-comments-container.test.js @@ -30,6 +30,7 @@ describe('PullRequestReviewCommentsContainer', function() { }; return ; } + it('collects the comments after component has been mounted', function() { const collectCommentsStub = sinon.stub(); shallow(buildApp({}, {collectComments: collectCommentsStub})); @@ -49,7 +50,6 @@ describe('PullRequestReviewCommentsContainer', function() { assert.strictEqual(wrapper.instance()._attemptToLoadMoreComments.callCount, 1); }); - describe('loadMoreComments', function() { it('calls this.props.relay.loadMore with correct args', function() { const relayLoadMoreStub = sinon.stub(); @@ -79,23 +79,18 @@ describe('PullRequestReviewCommentsContainer', function() { assert.deepEqual(args.comments, {edges: ['this kiki is marvelous']}); assert.isFalse(args.hasMore); }); - - it('handles errors', function() { - - }); - - }); - describe('attemptToLoadMoreComments', function() { let clock; beforeEach(function() { clock = sinon.useFakeTimers(); }); + afterEach(function() { clock = sinon.restore(); }); + it('does not call loadMore if hasMore is false', function() { const relayLoadMoreStub = sinon.stub(); const wrapper = shallow(buildApp({relayLoadMore: relayLoadMoreStub})); From e71dbb77a6763fcd0b3651538501dc039ef99a61 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 4 Jan 2019 16:56:12 -0800 Subject: [PATCH 1694/4053] [wip] add tests for PullRequestReviewsController --- .../controllers/pr-reviews-controller.test.js | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 test/controllers/pr-reviews-controller.test.js diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js new file mode 100644 index 0000000000..7105fcbcff --- /dev/null +++ b/test/controllers/pr-reviews-controller.test.js @@ -0,0 +1,57 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import PullRequestReviewsController from '../../lib/controllers/pr-reviews-controller'; + +describe('PullRequestReviewsController', function() { + function buildApp(opts, overrideProps = {}) { + const o = { + relayHasMore: () => { return false; }, + relayLoadMore: () => {}, + relayIsLoading: () => { return false; }, + reveiwSpecs: [], + reviewStartCursor: 0, + ...opts, + }; + + const review = { + edges: o.reviewItemSpecs.map((spec, i) => ({ + cursor: `result${i}`, + node: { + id: spec.id, + __typename: spec.kind, + }, + })), + pageInfo: { + startCursor: `result${o.reviewStartCursor}`, + endCursor: `result${o.reviewStartCursor + o.reviewItemSpecs.length}`, + hasNextPage: o.reviewStartCursor + o.reviewItemSpecs.length < o.reviewItemTotal, + hasPreviousPage: o.reviewStartCursor !== 0, + }, + totalCount: o.reviewItemTotal, + }; + + const props = { + relay: { + hasMore: o.relayHasMore, + loadMore: o.relayLoadMore, + isLoading: o.relayIsLoading, + }, + getBufferRowForDiffPosition: () => {}, + pullRequest: review, + ...overrideProps, + }; + return ; + } + it('renders a PullRequestReviewCommentsContainer for every review', function() { + + }); + + it('renders a PullRequestReviewCommentsView', function() { + + }); + + it('groups the comments into threads once all the data has been fetched', function() { + + }); +}); From 7d76f4f8b8ef3de1111fe4856abfe89a4b2fb6b0 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 4 Jan 2019 17:43:57 -0800 Subject: [PATCH 1695/4053] give `PullRequestCommentsView` tests the new getBufferRowForDiffPosition prop --- test/views/pr-comments-view.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index 75e5f4d7fc..346542323d 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -31,7 +31,7 @@ describe('PullRequestCommentsView', function() { }) .build(); - const wrapper = shallow(); + const wrapper = shallow(); assert.deepEqual(wrapper.find('Marker').at(0).prop('bufferRange').serialize(), [[1, 0], [1, 0]]); assert.deepEqual(wrapper.find('Marker').at(1).prop('bufferRange').serialize(), [[12, 0], [12, 0]]); @@ -57,7 +57,7 @@ describe('PullRequestCommentsView', function() { }) .build(); - const wrapper = shallow(); + const wrapper = shallow(); const comments = wrapper.find('PullRequestCommentView'); assert.lengthOf(comments, 1); From 2aa93bbcb4a683f9a8bc9e380ec845a630715d76 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 4 Jan 2019 18:20:24 -0800 Subject: [PATCH 1696/4053] reverse order of comment threads in pr builder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the test was asserting on 3 individual comments, that they rendered in the correct positions. when Katrina implemented grouping comments by thread, she did something like: `[...this.props.commentThreads].reverse().map(({rootCommentId, comments}) => {` tbh I don’t really understand why we need to call `reverse` there but we also need to be calling reverse in the comment builder, so that the tests are doing the same thing as the code. and then there was also a minor problem where we were passing in two comments with the same ID, which is now fixed. --- test/builder/pr.js | 2 +- test/views/pr-comments-view.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/builder/pr.js b/test/builder/pr.js index 63352c8e8b..55f3465fbc 100644 --- a/test/builder/pr.js +++ b/test/builder/pr.js @@ -134,7 +134,7 @@ class PullRequestBuilder { }); return { reviews: {nodes: this._reviews}, - commentThreads: Object.keys(commentThreads).map(rootCommentId => { + commentThreads: Object.keys(commentThreads).reverse().map(rootCommentId => { return { rootCommentId, comments: commentThreads[rootCommentId], diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index 346542323d..1a740bccae 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -27,7 +27,7 @@ describe('PullRequestCommentsView', function() { .addReview(r => { r.addComment(c => c.id(0).path('file0.txt').position(2).body('one')); r.addComment(c => c.id(1).path('file0.txt').position(15).body('three')); - r.addComment(c => c.id(1).path('file1.txt').position(7).body('three')); + r.addComment(c => c.id(2).path('file1.txt').position(7).body('three')); }) .build(); From 47dbdaf626b12c57f6d1de92973a7de69dac6af9 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 17:55:54 -0800 Subject: [PATCH 1697/4053] :fire: unnecessary commit-related arguments from issueDetailView fragment --- lib/controllers/issueish-detail-controller.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index 697a1f0e96..cd19de9636 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -287,7 +287,7 @@ export class BareIssueishDetailController extends React.Component { addEvent('open-commit-in-pane', {package: 'github', from: this.constructor.name}); } } -// todo: we probably don't need to commit count and cursor in the issue fragment + export default createFragmentContainer(BareIssueishDetailController, { repository: graphql` fragment issueishDetailController_repository on Repository @@ -316,8 +316,6 @@ export default createFragmentContainer(BareIssueishDetailController, { ...issueDetailView_issue @arguments( timelineCount: $timelineCount, timelineCursor: $timelineCursor, - commitCount: $commitCount, - commitCursor: $commitCursor, ) } } From bb47ca58a8498983a6f2412d9bb9ea2457d41343 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 18:16:03 -0800 Subject: [PATCH 1698/4053] Attempt to load more reviews only if no error ... in case we hit an error case where we fall into an infinite loop of retrying --- lib/controllers/pr-reviews-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 62262e9130..9021cfd1fa 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -35,12 +35,12 @@ export default class PullRequestReviewsController extends React.Component { this.props.relay.loadMore( PAGE_SIZE, error => { - this._attemptToLoadMoreReviews(); - /* istanbul ignore if */ if (error) { // eslint-disable-next-line no-console console.error(error); + } else { + this._attemptToLoadMoreReviews(); } }, ); From 99c6cc1e0712128915be4455e84d906d84f252d9 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 18:16:37 -0800 Subject: [PATCH 1699/4053] :art: re-order methods for readability --- lib/controllers/pr-reviews-controller.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 9021cfd1fa..1e1e2c06fa 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -31,6 +31,18 @@ export default class PullRequestReviewsController extends React.Component { this._attemptToLoadMoreReviews(); } + _attemptToLoadMoreReviews = () => { + if (!this.props.relay.hasMore()) { + return; + } + + if (this.props.relay.isLoading()) { + setTimeout(this._loadMoreReviews, PAGINATION_WAIT_TIME_MS); + } else { + this._loadMoreReviews(); + } + } + _loadMoreReviews = () => { this.props.relay.loadMore( PAGE_SIZE, @@ -46,18 +58,6 @@ export default class PullRequestReviewsController extends React.Component { ); } - _attemptToLoadMoreReviews = () => { - if (!this.props.relay.hasMore()) { - return; - } - - if (this.props.relay.isLoading()) { - setTimeout(this._loadMoreReviews, PAGINATION_WAIT_TIME_MS); - } else { - this._loadMoreReviews(); - } - } - renderCommentFetchingContainers() { return this.props.pullRequest.reviews.edges.map(({node}) => { const review = node; From 816ab62624396cf4ce3ef94ba464d846e3112f6c Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 18:31:36 -0800 Subject: [PATCH 1700/4053] Update generated relay code after :fire:ing commit-related arguments --- .../issueishDetailContainerQuery.graphql.js | 8 ++-- ...eishDetailController_repository.graphql.js | 38 +++++++++---------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 0ee982a7d9..08c8d4132a 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 86a6d997a1bfd3d4f567397ecdaa752b + * @relayHash b2844afc76c2e9c390039931a9af7d40 */ /* eslint-disable */ @@ -69,7 +69,7 @@ fragment issueishDetailController_repository_y3nHF on Repository { ... on Issue { title number - ...issueDetailView_issue_4cAEh0 + ...issueDetailView_issue_3D8CP9 } ... on Node { id @@ -120,7 +120,7 @@ fragment prDetailView_repository on Repository { } } -fragment issueDetailView_issue_4cAEh0 on Issue { +fragment issueDetailView_issue_3D8CP9 on Issue { __typename ... on Node { id @@ -1207,7 +1207,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_4cAEh0\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_4cAEh0 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_3D8CP9\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_3D8CP9 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", diff --git a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js index 12a4a5d6e8..fa323bdbdd 100644 --- a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js +++ b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js @@ -110,24 +110,12 @@ v5 = { "storageKey": null }, v6 = { - "kind": "Variable", - "name": "commitCount", - "variableName": "commitCount", - "type": null -}, -v7 = { - "kind": "Variable", - "name": "commitCursor", - "variableName": "commitCursor", - "type": null -}, -v8 = { "kind": "Variable", "name": "timelineCount", "variableName": "timelineCount", "type": null }, -v9 = { +v7 = { "kind": "Variable", "name": "timelineCursor", "variableName": "timelineCursor", @@ -228,9 +216,7 @@ return { "name": "issueDetailView_issue", "args": [ v6, - v7, - v8, - v9 + v7 ] } ] @@ -303,8 +289,18 @@ return { "variableName": "commentCursor", "type": null }, - v6, - v7, + { + "kind": "Variable", + "name": "commitCount", + "variableName": "commitCount", + "type": null + }, + { + "kind": "Variable", + "name": "commitCursor", + "variableName": "commitCursor", + "type": null + }, { "kind": "Variable", "name": "reviewCount", @@ -317,8 +313,8 @@ return { "variableName": "reviewCursor", "type": null }, - v8, - v9 + v6, + v7 ] } ] @@ -329,5 +325,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = 'e348cf91ee76425342abf4aa3c60b143'; +(node/*: any*/).hash = 'c06dfbb4f1cc1c45187449da61fd7328'; module.exports = node; From 8eba48bd7fb3f39fdc01933d7c5718ddeefd7c71 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 18:31:55 -0800 Subject: [PATCH 1701/4053] More :art: for readability. Fix up comment --- lib/controllers/pr-reviews-controller.js | 32 +++++++++++------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 1e1e2c06fa..0051b5abd5 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -58,20 +58,6 @@ export default class PullRequestReviewsController extends React.Component { ); } - renderCommentFetchingContainers() { - return this.props.pullRequest.reviews.edges.map(({node}) => { - const review = node; - - return ( - - ); - }); - } - render() { if (!this.props.pullRequest || !this.props.pullRequest.reviews) { return null; @@ -84,14 +70,14 @@ export default class PullRequestReviewsController extends React.Component { }; }); - /** Slightly hacky thing to deal with comment threading... + /** Dealing with comment threading... * * Threads can have comments belonging to multiple reviews. * We need a nested pagination container to fetch comment pages. - * Upon fetching new comments, the `aggregateComments` method is called with the comments to add. + * Upon fetching new comments, the `collectComments` method is called with all comments fetched for that review. * Ultimately we want to organize comments based on the root comment they are replies to. * So `renderCommentFetchingContainers` simply fetches data and doesn't render any DOM elements. - * `PullRequestReviewCommentsView` renders the comment thread data aggregated. + * `PullRequestReviewCommentsView` renders the aggregated comment thread data. * */ return ( @@ -101,6 +87,18 @@ export default class PullRequestReviewsController extends React.Component { ); } + renderCommentFetchingContainers() { + return this.props.pullRequest.reviews.edges.map(({node: review}) => { + return ( + + ); + }); + } + // sorts reviews by date ascending (oldest to newest) sortComparator(a, b) { const dateA = new Date(a.submittedAt); From 2c6c8eb54576c2168d04e8cb1cc4c6c528c86de5 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 19:11:44 -0800 Subject: [PATCH 1702/4053] :art: :art: :art: --- .../pr-review-comments-container.js | 33 ++++++++------ lib/controllers/pr-reviews-controller.js | 44 +++++++++---------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 9b3138f1fd..c514c9f313 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -21,28 +21,26 @@ export class BarePullRequestReviewCommentsContainer extends React.Component { } componentDidMount() { - this._attemptToLoadMoreComments(); - const {submittedAt, comments, id} = this.props.review; - this.props.collectComments({reviewId: id, submittedAt, comments, hasMore: this.props.relay.hasMore()}); + this.handleComments(); } - onDidLoadMore = error => { - const {submittedAt, comments, id} = this.props.review; - this.props.collectComments({reviewId: id, submittedAt, comments, hasMore: this.props.relay.hasMore()}); - this._attemptToLoadMoreComments(); - + handleComments = error => { /* istanbul ignore if */ if (error) { // eslint-disable-next-line no-console console.error(error); + return; } - } - _loadMoreComments = () => { - this.props.relay.loadMore( - PAGE_SIZE, - this.onDidLoadMore, - ); + const {submittedAt, comments, id} = this.props.review; + this.props.collectComments({ + reviewId: id, + submittedAt, + comments, + fetchingMoreComments: this.props.relay.hasMore(), + }); + + this._attemptToLoadMoreComments(); } _attemptToLoadMoreComments = () => { @@ -57,6 +55,13 @@ export class BarePullRequestReviewCommentsContainer extends React.Component { } } + _loadMoreComments = () => { + this.props.relay.loadMore( + PAGE_SIZE, + this.handleComments, + ); + } + render() { return null; } diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 0051b5abd5..ad3d89b652 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -99,25 +99,12 @@ export default class PullRequestReviewsController extends React.Component { }); } - // sorts reviews by date ascending (oldest to newest) - sortComparator(a, b) { - const dateA = new Date(a.submittedAt); - const dateB = new Date(b.submittedAt); - if (dateA > dateB) { - return 1; - } else if (dateB > dateA) { - return -1; - } else { - return 0; - } - } - - collectComments = ({reviewId, submittedAt, comments, hasMore}) => { - this.reviewsById.set(reviewId, {submittedAt, comments, hasMore}); - const noMoreReviewsToFetch = !this.props.relay.hasMore(); - if (noMoreReviewsToFetch) { - const noMoreCommentsToFetch = [...this.reviewsById.values()].every(review => !review.hasMore); - if (noMoreCommentsToFetch) { + collectComments = ({reviewId, submittedAt, comments, fetchingMoreCommnts}) => { + this.reviewsById.set(reviewId, {submittedAt, comments, fetchingMoreCommnts}); + const stillFetchingReviews = this.props.relay.hasMore(); + if (!stillFetchingReviews) { + const stillFetchingComments = [...this.reviewsById.values()].some(review => review.fetchingMoreCommnts); + if (!stillFetchingComments) { this.groupCommentsByThread(); } } @@ -125,18 +112,18 @@ export default class PullRequestReviewsController extends React.Component { groupCommentsByThread() { // we have no guarantees that reviews will return in order so sort them by date. - const sortedReviews = [...this.reviewsById.values()].sort(this.sortComparator); + const sortedReviews = [...this.reviewsById.values()].sort(this.sortByDate); // react batches calls to setState and does not update state synchronously // therefore we need an intermediate state so we can do checks against keys // we have just added. const state = {}; sortedReviews.forEach(({comments}) => { - comments.edges.forEach(({node}) => { - const comment = node; + comments.edges.forEach(({node: comment}) => { if (!comment.replyTo) { state[comment.id] = [comment]; } else { + // TODO: look at this more closely... // When comment being replied to is outdated...?? Not 100% sure... // Why would we even get an outdated comment or a response to one here? // Ran into this error when viewing files for https://github.com/numpy/numpy/pull/9998 @@ -153,4 +140,17 @@ export default class PullRequestReviewsController extends React.Component { this.setState(state); } + + // sorts reviews by date ascending (oldest to newest) + sortByDate(a, b) { + const dateA = new Date(a.submittedAt); + const dateB = new Date(b.submittedAt); + if (dateA > dateB) { + return 1; + } else if (dateB > dateA) { + return -1; + } else { + return 0; + } + } } From 708700825ddf8bab55d949b867399c533c387382 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 19:16:29 -0800 Subject: [PATCH 1703/4053] Reverse the array earlier --- lib/controllers/pr-reviews-controller.js | 2 +- lib/views/pr-review-comments-view.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index ad3d89b652..ba5674883f 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -63,7 +63,7 @@ export default class PullRequestReviewsController extends React.Component { return null; } - const commentThreads = Object.keys(this.state).map(rootCommentId => { + const commentThreads = Object.keys(this.state).reverse().map(rootCommentId => { return { rootCommentId, comments: this.state[rootCommentId], diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index 7eb1276902..09f8bc9bf9 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -24,7 +24,7 @@ export default class PullRequestCommentsView extends React.Component { } render() { - return [...this.props.commentThreads].reverse().map(({rootCommentId, comments}) => { + return [...this.props.commentThreads].map(({rootCommentId, comments}) => { const rootComment = comments[0]; if (!rootComment.position) { return null; From b42ab575763f51105fb135607c50bddaa2dbfda4 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 19:34:26 -0800 Subject: [PATCH 1704/4053] Use PAGE_SIZE for refetch variables --- lib/views/pr-detail-view.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index d0f18ae388..ed8d1f6d3d 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -15,6 +15,7 @@ import EmojiReactionsView from '../views/emoji-reactions-view'; import IssueishBadge from '../views/issueish-badge'; import PrCommitsView from '../views/pr-commits-view'; import PrStatusesView from '../views/pr-statuses-view'; +import {PAGE_SIZE} from '../helpers'; class CheckoutState { constructor(name) { @@ -348,13 +349,13 @@ export class BarePullRequestDetailView extends React.Component { this.props.relay.refetch({ repoId: this.props.repository.id, issueishId: this.props.pullRequest.id, - timelineCount: 100, + timelineCount: PAGE_SIZE, timelineCursor: null, - commitCount: 100, + commitCount: PAGE_SIZE, commitCursor: null, - reviewCount: 2, + reviewCount: PAGE_SIZE, reviewCursor: null, - commentCount: 2, + commentCount: PAGE_SIZE, commentCursor: null, }, null, () => { this.setState({refreshing: false}); From 0ce67983023f0307d6bba478ab6e99d5ff4f5993 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 19:59:26 -0800 Subject: [PATCH 1705/4053] Make tests consistent --- test/models/patch/multi-file-patch.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 767b17e233..cad02eaffa 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -710,7 +710,7 @@ describe('MultiFilePatch', function() { .addFilePatch(fp => { fp.setOldFile(f => f.path('file.txt')); fp.addHunk(h => { - h.unchanged('1 (0)').added('2 (1)', '3 (2)').deleted('4 (3)', '5 (4)', '6 (5)').unchanged('7 (6)'); + h.unchanged('0 (1)').added('1 (2)', '2 (3)').deleted('3 (4)', '4 (5)', '5 (6)').unchanged('6 (7)'); }); }) .build(); From 0498b50922bdcc7d1fae589c897fa871c6e5a5fc Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 23:02:18 -0800 Subject: [PATCH 1706/4053] Fix PullRequestReviewCommentsContainer tests --- .../pr-review-comments-container.test.js | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/test/containers/pr-review-comments-container.test.js b/test/containers/pr-review-comments-container.test.js index d690000057..43f3f9c08d 100644 --- a/test/containers/pr-review-comments-container.test.js +++ b/test/containers/pr-review-comments-container.test.js @@ -6,6 +6,12 @@ import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../../lib/helpers'; import {BarePullRequestReviewCommentsContainer} from '../../lib/containers/pr-review-comments-container'; describe('PullRequestReviewCommentsContainer', function() { + const review = { + id: '123', + submittedAt: '2018-12-27T20:40:55Z', + comments: {edges: ['this kiki is marvelous']}, + }; + function buildApp(opts, overrideProps = {}) { const o = { relayHasMore: () => { return false; }, @@ -21,11 +27,7 @@ describe('PullRequestReviewCommentsContainer', function() { isLoading: o.relayIsLoading, }, collectComments: () => {}, - review: { - id: '123', - submittedAt: '2018-12-27T20:40:55Z', - comments: {edges: ['this kiki is marvelous']}, - }, + review, ...overrideProps, }; return ; @@ -35,12 +37,14 @@ describe('PullRequestReviewCommentsContainer', function() { const collectCommentsStub = sinon.stub(); shallow(buildApp({}, {collectComments: collectCommentsStub})); assert.strictEqual(collectCommentsStub.callCount, 1); - const args = collectCommentsStub.lastCall.args[0]; - assert.strictEqual(args.reviewId, '123'); - assert.strictEqual(args.submittedAt, '2018-12-27T20:40:55Z'); - assert.deepEqual(args.comments, {edges: ['this kiki is marvelous']}); - assert.isFalse(args.hasMore); + const {submittedAt, comments, id} = review; + assert.deepEqual(collectCommentsStub.lastCall.args[0], { + reviewId: id, + submittedAt, + comments, + fetchingMoreComments: false, + }); }); it('attempts to load comments after component has been mounted', function() { @@ -56,28 +60,29 @@ describe('PullRequestReviewCommentsContainer', function() { const wrapper = shallow(buildApp({relayLoadMore: relayLoadMoreStub})); wrapper.instance()._loadMoreComments(); - const args = relayLoadMoreStub.lastCall.args; - assert.strictEqual(args[0], PAGE_SIZE); - assert.strictEqual(args[1], wrapper.instance().onDidLoadMore); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleComments]); }); }); - describe('onDidLoadMore', function() { + describe('handleComments', function() { it('collects comments and attempts to load more comments', function() { const collectCommentsStub = sinon.stub(); const wrapper = shallow(buildApp({}, {collectComments: collectCommentsStub})); // collect comments is called when mounted, we don't care about that in this test so reset the count collectCommentsStub.reset(); sinon.stub(wrapper.instance(), '_attemptToLoadMoreComments'); - wrapper.instance().onDidLoadMore(); + wrapper.instance().handleComments(); assert.strictEqual(collectCommentsStub.callCount, 1); const args = collectCommentsStub.lastCall.args[0]; - assert.strictEqual(args.reviewId, '123'); - assert.strictEqual(args.submittedAt, '2018-12-27T20:40:55Z'); - assert.deepEqual(args.comments, {edges: ['this kiki is marvelous']}); - assert.isFalse(args.hasMore); + const {submittedAt, comments, id} = review; + assert.deepEqual(collectCommentsStub.lastCall.args[0], { + reviewId: id, + submittedAt, + comments, + fetchingMoreComments: false, + }); }); }); @@ -108,9 +113,7 @@ describe('PullRequestReviewCommentsContainer', function() { wrapper.instance()._attemptToLoadMoreComments(); assert.strictEqual(relayLoadMoreStub.callCount, 1); - const args = relayLoadMoreStub.lastCall.args; - assert.strictEqual(args[0], PAGE_SIZE); - assert.strictEqual(args[1], wrapper.instance().onDidLoadMore); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleComments]); }); it('calls loadMore after a timeout if hasMore is true and isLoading is true', function() { @@ -128,9 +131,7 @@ describe('PullRequestReviewCommentsContainer', function() { clock.tick(PAGINATION_WAIT_TIME_MS); assert.strictEqual(relayLoadMoreStub.callCount, 1); - const args = relayLoadMoreStub.lastCall.args; - assert.strictEqual(args[0], PAGE_SIZE); - assert.strictEqual(args[1], wrapper.instance().onDidLoadMore); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleComments]); }); }); }); From 80c97c5aeb7e964b1cec9b7ab5c6f5f8836cef12 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 23:06:04 -0800 Subject: [PATCH 1707/4053] Don't `reverse` in PullRequestBuilder --- test/builder/pr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/builder/pr.js b/test/builder/pr.js index 55f3465fbc..63352c8e8b 100644 --- a/test/builder/pr.js +++ b/test/builder/pr.js @@ -134,7 +134,7 @@ class PullRequestBuilder { }); return { reviews: {nodes: this._reviews}, - commentThreads: Object.keys(commentThreads).reverse().map(rootCommentId => { + commentThreads: Object.keys(commentThreads).map(rootCommentId => { return { rootCommentId, comments: commentThreads[rootCommentId], From 905a1a27dbe7a02b7d159b74b7a6ec125d963b71 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 4 Jan 2019 23:16:31 -0800 Subject: [PATCH 1708/4053] :art: tests --- test/views/pr-comments-view.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index 1a740bccae..3a87f20737 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -73,7 +73,7 @@ describe('PullRequestCommentView', function() { const bodyHTML = '
    yo yo
    '; const switchToIssueish = () => {}; - function buildApp(overrideProps = {}, opts = {}) { + function buildApp(commentOverrideProps = {}, opts = {}) { const props = { comment: { bodyHTML, @@ -83,7 +83,7 @@ describe('PullRequestCommentView', function() { avatarUrl, login, }, - ...overrideProps, + ...commentOverrideProps, }, switchToIssueish, ...opts, @@ -93,6 +93,7 @@ describe('PullRequestCommentView', function() { ); } + it('renders the PullRequestCommentReview information', function() { const wrapper = shallow(buildApp()); const avatar = wrapper.find('.github-PrComment-avatar'); From 5ca856826a1d8770e72a3cf68ee3ac35969af76a Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 7 Jan 2019 18:08:40 +0100 Subject: [PATCH 1709/4053] fall back to using git log authors if remote users cannot be loaded --- lib/models/user-store.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/models/user-store.js b/lib/models/user-store.js index 30a8c97355..57077e3144 100644 --- a/lib/models/user-store.js +++ b/lib/models/user-store.js @@ -100,11 +100,13 @@ export default class UserStore { this.setCommitter(data.committer); const githubRemotes = Array.from(data.remotes).filter(remote => remote.isGithubRepo()); - if (githubRemotes.length === 0) { - this.addUsers(data.authors, source.GITLOG); - } else { + if (githubRemotes.length > 0) { await this.loadUsersFromGraphQL(githubRemotes); } + + if (this.getUsers().length === 0) { + this.addUsers(data.authors, source.GITLOG); + } } loadUsersFromGraphQL(remotes) { From bb1536275ea1982b686e4b8ce4bf66be45126c71 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 7 Jan 2019 19:09:24 +0100 Subject: [PATCH 1710/4053] add test --- test/models/user-store.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/models/user-store.test.js b/test/models/user-store.test.js index 0d70bfd23c..51e130c864 100644 --- a/test/models/user-store.test.js +++ b/test/models/user-store.test.js @@ -97,6 +97,21 @@ describe('UserStore', function() { assert.deepEqual(store.committer, new Author(FAKE_USER.email, FAKE_USER.name)); }); + it('falls back to local git users and committers if laodMentionableUsers cannot load any user for whatever reason', async function() { + const workdirPath = await cloneRepository('multiple-commits'); + const repository = await buildRepository(workdirPath); + + store = new UserStore({repository, login, config}); + sinon.stub(store, 'loadMentionableUsers').returns(undefined); + + await store.loadUsers(); + await nextUpdatePromise(); + + assert.deepEqual(store.getUsers(), [ + new Author('kuychaco@github.com', 'Katrina Uychaco'), + ]); + }); + it('loads store with mentionable users from the GitHub API in a repo with a GitHub remote', async function() { await login.setToken('https://api.github.com', '1234'); From 8915ec0337991a7316319680a81e3b8f42ccf990 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 7 Jan 2019 19:38:31 +0100 Subject: [PATCH 1711/4053] don't need login stub --- test/models/user-store.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/user-store.test.js b/test/models/user-store.test.js index 51e130c864..4b78905532 100644 --- a/test/models/user-store.test.js +++ b/test/models/user-store.test.js @@ -101,7 +101,7 @@ describe('UserStore', function() { const workdirPath = await cloneRepository('multiple-commits'); const repository = await buildRepository(workdirPath); - store = new UserStore({repository, login, config}); + store = new UserStore({repository, config}); sinon.stub(store, 'loadMentionableUsers').returns(undefined); await store.loadUsers(); From d08daca581f51e57871a9c214fee21b222e6caeb Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 7 Jan 2019 14:04:26 -0800 Subject: [PATCH 1712/4053] fix typo in method name --- lib/controllers/pr-reviews-controller.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index ba5674883f..18f503b996 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -99,11 +99,11 @@ export default class PullRequestReviewsController extends React.Component { }); } - collectComments = ({reviewId, submittedAt, comments, fetchingMoreCommnts}) => { - this.reviewsById.set(reviewId, {submittedAt, comments, fetchingMoreCommnts}); + collectComments = ({reviewId, submittedAt, comments, fetchingMoreComments}) => { + this.reviewsById.set(reviewId, {submittedAt, comments, fetchingMoreComments}); const stillFetchingReviews = this.props.relay.hasMore(); if (!stillFetchingReviews) { - const stillFetchingComments = [...this.reviewsById.values()].some(review => review.fetchingMoreCommnts); + const stillFetchingComments = [...this.reviewsById.values()].some(review => review.fetchingMoreComments); if (!stillFetchingComments) { this.groupCommentsByThread(); } From edc49781f2ee6cadbfab161234797aa30b92bfc0 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 7 Jan 2019 14:08:33 -0800 Subject: [PATCH 1713/4053] pedantic af function name change --- lib/controllers/pr-reviews-controller.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 18f503b996..73ac847aa9 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -112,7 +112,7 @@ export default class PullRequestReviewsController extends React.Component { groupCommentsByThread() { // we have no guarantees that reviews will return in order so sort them by date. - const sortedReviews = [...this.reviewsById.values()].sort(this.sortByDate); + const sortedReviews = [...this.reviewsById.values()].sort(this.compareReviewsByDate); // react batches calls to setState and does not update state synchronously // therefore we need an intermediate state so we can do checks against keys @@ -141,10 +141,10 @@ export default class PullRequestReviewsController extends React.Component { this.setState(state); } - // sorts reviews by date ascending (oldest to newest) - sortByDate(a, b) { - const dateA = new Date(a.submittedAt); - const dateB = new Date(b.submittedAt); + // compare reviews by date ascending (in order to sort oldest to newest) + compareReviewsByDate(reviewA, reviewB) { + const dateA = new Date(reviewA.submittedAt); + const dateB = new Date(reviewB.submittedAt); if (dateA > dateB) { return 1; } else if (dateB > dateA) { From 73b06c69a56dac66612c51c2978e7cf380c9d95f Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 7 Jan 2019 15:59:20 -0800 Subject: [PATCH 1714/4053] :fire: unused variable --- test/containers/pr-review-comments-container.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/containers/pr-review-comments-container.test.js b/test/containers/pr-review-comments-container.test.js index 43f3f9c08d..ca3bbcc1f0 100644 --- a/test/containers/pr-review-comments-container.test.js +++ b/test/containers/pr-review-comments-container.test.js @@ -74,7 +74,6 @@ describe('PullRequestReviewCommentsContainer', function() { wrapper.instance().handleComments(); assert.strictEqual(collectCommentsStub.callCount, 1); - const args = collectCommentsStub.lastCall.args[0]; const {submittedAt, comments, id} = review; assert.deepEqual(collectCommentsStub.lastCall.args[0], { From dcaee75346663874a3d30f88621c2847bf6e86d2 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 7 Jan 2019 17:24:40 -0800 Subject: [PATCH 1715/4053] make builder reflect the new improved shape of our data - we're using edges now instead of nodes, as the query was originally using - add `submittedAt` to reviews - `replyTo` is an object with an id - --- test/builder/pr.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/test/builder/pr.js b/test/builder/pr.js index 63352c8e8b..f6f14e8fdb 100644 --- a/test/builder/pr.js +++ b/test/builder/pr.js @@ -52,7 +52,7 @@ class CommentBuilder { } replyTo(replyToId) { - this._replyTo = replyToId; + this._replyTo = {id: replyToId}; return this; } @@ -78,6 +78,7 @@ class ReviewBuilder { this.nextCommentID = 0; this._id = 0; this._comments = []; + this._submittedAt = '2018-12-28T20:40:55Z'; } id(i) { @@ -85,6 +86,11 @@ class ReviewBuilder { return this; } + submittedAt(timestamp) { + this._submittedAt = timestamp; + return this; + } + addComment(block = () => {}) { const builder = new CommentBuilder(); builder.id(this.nextCommentID); @@ -97,9 +103,12 @@ class ReviewBuilder { } build() { + const comments = this._comments.map(comment => { + return {node: comment}; + }); return { id: this._id, - comments: {nodes: this._comments}, + comments: {edges: comments}, }; } } @@ -124,9 +133,9 @@ class PullRequestBuilder { build() { const commentThreads = {}; this._reviews.forEach(review => { - review.comments.nodes.forEach(comment => { - if (comment.replyTo && commentThreads[comment.replyTo]) { - commentThreads[comment.replyTo].push(comment); + review.comments.edges.forEach(({node: comment}) => { + if (comment.replyTo && comment.replyTo.id && commentThreads[comment.replyTo.id]) { + commentThreads[comment.replyTo.id].push(comment); } else { commentThreads[comment.id] = [comment]; } From 8d83ebd55a25301221422824193f8df0d4bc35f0 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 7 Jan 2019 17:27:19 -0800 Subject: [PATCH 1716/4053] add more tests for `PullRequestReviewsController` --- .../controllers/pr-reviews-controller.test.js | 94 +++++++++++++++++-- 1 file changed, 86 insertions(+), 8 deletions(-) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index 7105fcbcff..41bcbff8fb 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -1,5 +1,6 @@ import React from 'react'; import {shallow} from 'enzyme'; +import {reviewBuilder} from '../builder/pr'; import PullRequestReviewsController from '../../lib/controllers/pr-reviews-controller'; @@ -9,23 +10,23 @@ describe('PullRequestReviewsController', function() { relayHasMore: () => { return false; }, relayLoadMore: () => {}, relayIsLoading: () => { return false; }, - reveiwSpecs: [], + reviewSpecs: [], reviewStartCursor: 0, ...opts, }; - const review = { - edges: o.reviewItemSpecs.map((spec, i) => ({ + const reviews = { + edges: o.reviewSpecs.map((spec, i) => ({ cursor: `result${i}`, node: { id: spec.id, - __typename: spec.kind, + __typename: 'review', }, })), pageInfo: { startCursor: `result${o.reviewStartCursor}`, - endCursor: `result${o.reviewStartCursor + o.reviewItemSpecs.length}`, - hasNextPage: o.reviewStartCursor + o.reviewItemSpecs.length < o.reviewItemTotal, + endCursor: `result${o.reviewStartCursor + o.reviewSpecs.length}`, + hasNextPage: o.reviewStartCursor + o.reviewSpecs.length < o.reviewItemTotal, hasPreviousPage: o.reviewStartCursor !== 0, }, totalCount: o.reviewItemTotal, @@ -37,21 +38,98 @@ describe('PullRequestReviewsController', function() { loadMore: o.relayLoadMore, isLoading: o.relayIsLoading, }, + + switchToIssueish: () => {}, getBufferRowForDiffPosition: () => {}, - pullRequest: review, + pullRequest: {reviews}, ...overrideProps, }; return ; } + it('returns null if props.pullRequest is falsy', function() { + const wrapper = shallow(buildApp({}, {pullRequest: null})); + assert.isNull(wrapper.getElement()); + }); + + it('returns null if props.pullRequest.reviews is falsy', function() { + const wrapper = shallow(buildApp({}, {pullRequest: {reviews: null}})); + assert.isNull(wrapper.getElement()); + }); + it('renders a PullRequestReviewCommentsContainer for every review', function() { + const review1 = reviewBuilder().build(); + const review2 = reviewBuilder().build(); + const reviewSpecs = [review1, review2]; + const wrapper = shallow(buildApp({reviewSpecs})); + const containers = wrapper.find('Relay(BarePullRequestReviewCommentsContainer)'); + assert.strictEqual(containers.length, 2); + // should I assert on props here? }); - it('renders a PullRequestReviewCommentsView', function() { + it('renders a PullRequestReviewCommentsView and passes props through', function() { + const review1 = reviewBuilder().build(); + const review2 = reviewBuilder().build(); + + const reviewSpecs = [review1, review2]; + const passThroughProp = 'I only exist for the children'; + const wrapper = shallow(buildApp({reviewSpecs}, {passThroughProp})); + const view = wrapper.find('PullRequestCommentsView'); + assert.strictEqual(view.length, 1); + assert.strictEqual(wrapper.instance().props.passThroughProp, view.prop('passThroughProp')); + + // should I assert on the commentThreads prop? }); + describe('collectComments', function() { + it('sets this.reviewsById with correct data', function() { + const wrapper = shallow(buildApp()); + const args = {reviewId: 123, submittedAt: '2018-12-27T20:40:55Z', comments: ['a comment', + ], fetchingMoreComments: true}; + assert.strictEqual(wrapper.instance().reviewsById.size, 0); + wrapper.instance().collectComments(args); + const review = wrapper.instance().reviewsById.get(args.reviewId); + delete args.reviewId; + assert.deepEqual(review, args); + }); + + it('calls groupCommentsByThread if there are no more reviews or comments to be fetched', function() { + const wrapper = shallow(buildApp()); + const groupCommentsStub = sinon.stub(wrapper.instance(), 'groupCommentsByThread'); + assert.isFalse(groupCommentsStub.called); + const args = {reviewId: 123, submittedAt: '2018-12-27T20:40:55Z', comments: ['a comment', + ], fetchingMoreComments: false}; + wrapper.instance().collectComments(args); + assert.strictEqual(groupCommentsStub.callCount, 1); + }); + }); + + it('groups the comments into threads once all the data has been fetched', function() { + const review1 = reviewBuilder() + .id(0) + .submittedAt('2018-12-27T20:40:55Z') + .addComment(c => c.id(1).path('file0.txt').body('OG comment')) + .build(); + + const review2 = reviewBuilder() + .id(1) + .submittedAt('2018-12-28T20:40:55Z') + .addComment(c => c.id(2).path('file0.txt').replyTo(1).body('reply to OG comment')) + .build(); + + const reviewSpecs = [review1, review2]; + + const wrapper = shallow(buildApp({reviewSpecs})); + + // adding this manually to reviewsById because the last time you call collectComments + wrapper.instance().reviewsById.set(review1.id, {submittedAt: review1.submittedAt, comments: review1.comments, fetchingMoreComments: false}); + wrapper.instance().collectComments({reviewId: review2.id, submittedAt: review2.submittedAt, comments: review2.comments, fetchingMoreComments: false}); + const threadedComments = wrapper.instance().state[1]; + assert.lengthOf(threadedComments, 2); + assert.strictEqual(threadedComments[0].body, 'OG comment'); + assert.strictEqual(threadedComments[1].body, 'reply to OG comment'); }); }); From 8baab2f0f257315794b4620928516e560c015aab Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 7 Jan 2019 17:28:37 -0800 Subject: [PATCH 1717/4053] :shirt: --- test/builder/pr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/builder/pr.js b/test/builder/pr.js index f6f14e8fdb..e7b78ba2db 100644 --- a/test/builder/pr.js +++ b/test/builder/pr.js @@ -133,7 +133,7 @@ class PullRequestBuilder { build() { const commentThreads = {}; this._reviews.forEach(review => { - review.comments.edges.forEach(({node: comment}) => { + review.comments.edges.forEach(({node: comment}) => { if (comment.replyTo && comment.replyTo.id && commentThreads[comment.replyTo.id]) { commentThreads[comment.replyTo.id].push(comment); } else { From 7542c0d36988becbf70fb421373137cbc9ab0043 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Mon, 7 Jan 2019 17:34:19 -0800 Subject: [PATCH 1718/4053] clean up some cruft --- test/controllers/pr-reviews-controller.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index 41bcbff8fb..66d6f78896 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -64,7 +64,9 @@ describe('PullRequestReviewsController', function() { const wrapper = shallow(buildApp({reviewSpecs})); const containers = wrapper.find('Relay(BarePullRequestReviewCommentsContainer)'); assert.strictEqual(containers.length, 2); - // should I assert on props here? + + assert.strictEqual(containers.at(0).prop('review').id, review1.id); + assert.strictEqual(containers.at(1).prop('review').id, review2.id); }); it('renders a PullRequestReviewCommentsView and passes props through', function() { @@ -78,8 +80,6 @@ describe('PullRequestReviewsController', function() { assert.strictEqual(view.length, 1); assert.strictEqual(wrapper.instance().props.passThroughProp, view.prop('passThroughProp')); - - // should I assert on the commentThreads prop? }); describe('collectComments', function() { From c6c99cc11543dfdf1eda139dd4aaca795066221e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 8 Jan 2019 11:50:47 +0100 Subject: [PATCH 1719/4053] use `allUsers` size instead --- lib/models/user-store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/user-store.js b/lib/models/user-store.js index 57077e3144..a4b5f894d2 100644 --- a/lib/models/user-store.js +++ b/lib/models/user-store.js @@ -104,7 +104,7 @@ export default class UserStore { await this.loadUsersFromGraphQL(githubRemotes); } - if (this.getUsers().length === 0) { + if (this.allUsers.size === 0) { this.addUsers(data.authors, source.GITLOG); } } From 5302d3845b1233fbbdfe0a9bc3c2675b253f1625 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 8 Jan 2019 11:51:09 +0100 Subject: [PATCH 1720/4053] fix test --- test/models/user-store.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/models/user-store.test.js b/test/models/user-store.test.js index 4b78905532..7b9d8afc5c 100644 --- a/test/models/user-store.test.js +++ b/test/models/user-store.test.js @@ -238,12 +238,12 @@ describe('UserStore', function() { const workdirPath = await cloneRepository('multiple-commits'); const repository = await buildRepository(workdirPath); store = new UserStore({repository, config}); + sinon.spy(store, 'addUsers'); await assert.async.lengthOf(store.getUsers(), 1); + await assert.async.equal(store.addUsers.callCount, 1); - sinon.spy(store, 'addUsers'); // make a commit with FAKE_USER as committer await repository.commit('made a new commit', {allowEmpty: true}); - await assert.async.equal(store.addUsers.callCount, 1); // verify that FAKE_USER is in commit history const lastCommit = await repository.getLastCommit(); From b697a730c1a60c9b484d5eb29eda05fb45ef6769 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 8 Jan 2019 11:51:20 +0100 Subject: [PATCH 1721/4053] typo! --- test/models/user-store.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/user-store.test.js b/test/models/user-store.test.js index 7b9d8afc5c..0092288e3d 100644 --- a/test/models/user-store.test.js +++ b/test/models/user-store.test.js @@ -97,7 +97,7 @@ describe('UserStore', function() { assert.deepEqual(store.committer, new Author(FAKE_USER.email, FAKE_USER.name)); }); - it('falls back to local git users and committers if laodMentionableUsers cannot load any user for whatever reason', async function() { + it('falls back to local git users and committers if loadMentionableUsers cannot load any user for whatever reason', async function() { const workdirPath = await cloneRepository('multiple-commits'); const repository = await buildRepository(workdirPath); From f982959c32c0bcf81afb17acafb996d9514b99f1 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Tue, 8 Jan 2019 12:14:33 +0100 Subject: [PATCH 1722/4053] add some comments + tweaks --- lib/models/user-store.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/models/user-store.js b/lib/models/user-store.js index a4b5f894d2..ccea24f90f 100644 --- a/lib/models/user-store.js +++ b/lib/models/user-store.js @@ -102,8 +102,12 @@ export default class UserStore { if (githubRemotes.length > 0) { await this.loadUsersFromGraphQL(githubRemotes); + } else { + this.addUsers(data.authors, source.GITLOG); } + // if for whatever reason, no committers can be added, fall back to + // using git log committers as the last resort if (this.allUsers.size === 0) { this.addUsers(data.authors, source.GITLOG); } From 838784dd14304fdffc5ef13f5a9ac53e45d2654e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 8 Jan 2019 11:01:48 -0500 Subject: [PATCH 1723/4053] Exclude Relay-generated GraphQL files from code coverage --- .nycrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.nycrc.json b/.nycrc.json index 7135cec850..ba08fac8e8 100644 --- a/.nycrc.json +++ b/.nycrc.json @@ -7,6 +7,7 @@ "exclude": [ "lib/views/git-cache-view.js", "lib/views/git-timings-view.js", - "lib/relay-network-layer-manager.js" + "lib/relay-network-layer-manager.js", + "*.graphql.js" ] } From b8f1482d5b5c2ab47dd4f304ee92ab4be0652194 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 8 Jan 2019 11:04:44 -0500 Subject: [PATCH 1724/4053] More specific exclusion glob --- .nycrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nycrc.json b/.nycrc.json index ba08fac8e8..a9ba28c23f 100644 --- a/.nycrc.json +++ b/.nycrc.json @@ -8,6 +8,6 @@ "lib/views/git-cache-view.js", "lib/views/git-timings-view.js", "lib/relay-network-layer-manager.js", - "*.graphql.js" + "**/__generated__/*.graphql.js" ] } From 2c75c8b6db22b90f9aa9a80ffbae78c67f8ad610 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 8 Jan 2019 10:31:15 -0800 Subject: [PATCH 1725/4053] test that mfpView renders the `PullRequestReviewsController` --- test/views/multi-file-patch-view.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 30cf1d92af..813c206af9 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -9,6 +9,7 @@ import FilePatch from '../../lib/models/patch/file-patch'; import RefHolder from '../../lib/models/ref-holder'; import CommitPreviewItem from '../../lib/items/commit-preview-item'; import ChangedFileItem from '../../lib/items/changed-file-item'; +import IssueishDetailItem from '../../lib/items/issueish-detail-item'; describe('MultiFilePatchView', function() { let atomEnv, workspace, repository, filePatches; @@ -134,6 +135,11 @@ describe('MultiFilePatchView', function() { assert.isFalse(wrapper.find('FilePatchHeaderView[relPath="1"]').prop('hasMultipleFileSelections')); }); + it('renders a PullRequestsReviewsContainer if itemType is IssueishDetailItem', function() { + const wrapper = shallow(buildApp({itemType: IssueishDetailItem})); + assert.lengthOf(wrapper.find('Relay(PullRequestReviewsController)'), 1); + }); + it('renders the file patch within an editor', function() { const wrapper = mount(buildApp()); From 8817466d3c39299e4882149c14e16adcdf60b20b Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 8 Jan 2019 11:05:57 -0800 Subject: [PATCH 1726/4053] add test for error handling in `PullRequestReviewsController` --- lib/controllers/pr-reviews-controller.js | 23 +++--- .../pr-review-comments-container.test.js | 2 +- .../controllers/pr-reviews-controller.test.js | 80 ++++++++++++++----- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 73ac847aa9..140586d79f 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -43,19 +43,18 @@ export default class PullRequestReviewsController extends React.Component { } } + handleError = error => { + /* istanbul ignore if */ + if (error) { + // eslint-disable-next-line no-console + console.error(error); + } else { + this._attemptToLoadMoreReviews(); + } + } + _loadMoreReviews = () => { - this.props.relay.loadMore( - PAGE_SIZE, - error => { - /* istanbul ignore if */ - if (error) { - // eslint-disable-next-line no-console - console.error(error); - } else { - this._attemptToLoadMoreReviews(); - } - }, - ); + this.props.relay.loadMore(PAGE_SIZE, this.handleError); } render() { diff --git a/test/containers/pr-review-comments-container.test.js b/test/containers/pr-review-comments-container.test.js index ca3bbcc1f0..8eb7916221 100644 --- a/test/containers/pr-review-comments-container.test.js +++ b/test/containers/pr-review-comments-container.test.js @@ -54,7 +54,7 @@ describe('PullRequestReviewCommentsContainer', function() { assert.strictEqual(wrapper.instance()._attemptToLoadMoreComments.callCount, 1); }); - describe('loadMoreComments', function() { + describe('_loadMoreComments', function() { it('calls this.props.relay.loadMore with correct args', function() { const relayLoadMoreStub = sinon.stub(); const wrapper = shallow(buildApp({relayLoadMore: relayLoadMoreStub})); diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index 66d6f78896..6a9d13ed34 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -4,6 +4,8 @@ import {reviewBuilder} from '../builder/pr'; import PullRequestReviewsController from '../../lib/controllers/pr-reviews-controller'; +import {PAGE_SIZE} from '../../lib/helpers'; + describe('PullRequestReviewsController', function() { function buildApp(opts, overrideProps = {}) { const o = { @@ -105,31 +107,69 @@ describe('PullRequestReviewsController', function() { }); }); + describe('_loadMoreReviews', function() { + it('calls this.props.relay.loadMore with correct args', function() { + const relayLoadMoreStub = sinon.stub(); + const wrapper = shallow(buildApp({relayLoadMore: relayLoadMoreStub})); + wrapper.instance()._loadMoreReviews(); - it('groups the comments into threads once all the data has been fetched', function() { - const review1 = reviewBuilder() - .id(0) - .submittedAt('2018-12-27T20:40:55Z') - .addComment(c => c.id(1).path('file0.txt').body('OG comment')) - .build(); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleError]); + }); + }); - const review2 = reviewBuilder() - .id(1) - .submittedAt('2018-12-28T20:40:55Z') - .addComment(c => c.id(2).path('file0.txt').replyTo(1).body('reply to OG comment')) - .build(); + describe('grouping and ordering comments', function() { + it('groups the comments into threads based on replyId', function() { + const review1 = reviewBuilder() + .id(0) + .submittedAt('2018-12-27T20:40:55Z') + .addComment(c => c.id(1).path('file0.txt').body('OG comment')) + .build(); - const reviewSpecs = [review1, review2]; + const review2 = reviewBuilder() + .id(1) + .submittedAt('2018-12-28T20:40:55Z') + .addComment(c => c.id(2).path('file0.txt').replyTo(1).body('reply to OG comment')) + .build(); - const wrapper = shallow(buildApp({reviewSpecs})); + const reviewSpecs = [review1, review2]; - // adding this manually to reviewsById because the last time you call collectComments - wrapper.instance().reviewsById.set(review1.id, {submittedAt: review1.submittedAt, comments: review1.comments, fetchingMoreComments: false}); + const wrapper = shallow(buildApp({reviewSpecs})); + + // adding this manually to reviewsById because the last time you call collectComments it groups them, and we don't want to do that just yet. + wrapper.instance().reviewsById.set(review1.id, {submittedAt: review1.submittedAt, comments: review1.comments, fetchingMoreComments: false}); + + wrapper.instance().collectComments({reviewId: review2.id, submittedAt: review2.submittedAt, comments: review2.comments, fetchingMoreComments: false}); + const threadedComments = wrapper.instance().state[1]; + assert.lengthOf(threadedComments, 2); + assert.strictEqual(threadedComments[0].body, 'OG comment'); + assert.strictEqual(threadedComments[1].body, 'reply to OG comment'); + }); + it('sorts replies based on date', function() { + const review1 = reviewBuilder() + .id(0) + .submittedAt('2018-12-27T20:40:55Z') + .addComment(c => c.id(1).path('file0.txt').body('OG comment')) + .build(); + + const review2 = reviewBuilder() + .id(1) + .submittedAt('2018-12-28T20:40:55Z') + .addComment(c => c.id(2).path('file0.txt').replyTo(1).body('reply to OG comment')) + .build(); + + const reviewSpecs = [review1, review2]; + + const wrapper = shallow(buildApp({reviewSpecs})); + + // adding this manually to reviewsById because the last time you call collectComments it groups them, and we don't want to do that just yet. + wrapper.instance().reviewsById.set(review1.id, {submittedAt: review1.submittedAt, comments: review1.comments, fetchingMoreComments: false}); + + wrapper.instance().collectComments({reviewId: review2.id, submittedAt: review2.submittedAt, comments: review2.comments, fetchingMoreComments: false}); + const threadedComments = wrapper.instance().state[1]; + assert.lengthOf(threadedComments, 2); + assert.strictEqual(threadedComments[0].body, 'OG comment'); + assert.strictEqual(threadedComments[1].body, 'reply to OG comment'); + }); - wrapper.instance().collectComments({reviewId: review2.id, submittedAt: review2.submittedAt, comments: review2.comments, fetchingMoreComments: false}); - const threadedComments = wrapper.instance().state[1]; - assert.lengthOf(threadedComments, 2); - assert.strictEqual(threadedComments[0].body, 'OG comment'); - assert.strictEqual(threadedComments[1].body, 'reply to OG comment'); }); }); From 24e00fc0beb736ec686b3fb94632a286b0771498 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 8 Jan 2019 11:22:32 -0800 Subject: [PATCH 1727/4053] yummy copypasta --- .../controllers/pr-reviews-controller.test.js | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index 6a9d13ed34..682550dc2b 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -4,7 +4,7 @@ import {reviewBuilder} from '../builder/pr'; import PullRequestReviewsController from '../../lib/controllers/pr-reviews-controller'; -import {PAGE_SIZE} from '../../lib/helpers'; +import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../../lib/helpers'; describe('PullRequestReviewsController', function() { function buildApp(opts, overrideProps = {}) { @@ -107,6 +107,55 @@ describe('PullRequestReviewsController', function() { }); }); + describe('attemptToLoadMoreReviews', function() { + let clock; + beforeEach(function() { + clock = sinon.useFakeTimers(); + }); + + afterEach(function() { + clock = sinon.restore(); + }); + + it('does not call loadMore if hasMore is false', function() { + const relayLoadMoreStub = sinon.stub(); + const wrapper = shallow(buildApp({relayLoadMore: relayLoadMoreStub})); + relayLoadMoreStub.reset(); + + wrapper.instance()._attemptToLoadMoreReviews(); + assert.strictEqual(relayLoadMoreStub.callCount, 0); + }); + + it('calls loadMore immediately if hasMore is true and isLoading is false', function() { + const relayLoadMoreStub = sinon.stub(); + const relayHasMore = () => { return true; }; + const wrapper = shallow(buildApp({relayHasMore, relayLoadMore: relayLoadMoreStub})); + relayLoadMoreStub.reset(); + + wrapper.instance()._attemptToLoadMoreReviews(); + assert.strictEqual(relayLoadMoreStub.callCount, 1); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleError]); + }); + + it('calls loadMore after a timeout if hasMore is true and isLoading is true', function() { + const relayLoadMoreStub = sinon.stub(); + const relayHasMore = () => { return true; }; + const relayIsLoading = () => { return true; }; + const wrapper = shallow(buildApp({relayHasMore, relayIsLoading, relayLoadMore: relayLoadMoreStub})); + // advancing the timer and resetting the stub to clear the initial calls of + // _attemptToLoadMoreReviews when the component is initially mounted. + clock.tick(PAGINATION_WAIT_TIME_MS); + relayLoadMoreStub.reset(); + + wrapper.instance()._attemptToLoadMoreReviews(); + assert.strictEqual(relayLoadMoreStub.callCount, 0); + + clock.tick(PAGINATION_WAIT_TIME_MS); + assert.strictEqual(relayLoadMoreStub.callCount, 1); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleError]); + }); + }); + describe('_loadMoreReviews', function() { it('calls this.props.relay.loadMore with correct args', function() { const relayLoadMoreStub = sinon.stub(); From 5a9fa489d3868650e6af0dea2abe842c60642b45 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 8 Jan 2019 14:30:54 -0500 Subject: [PATCH 1728/4053] :arrow_up: Relay and family --- package-lock.json | 205 ++++++++++++++++++++++++++-------------------- package.json | 8 +- 2 files changed, 119 insertions(+), 94 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6deb8786ee..a400756b8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -186,9 +186,9 @@ } }, "@nodelib/fs.stat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.2.tgz", - "integrity": "sha512-yprFYuno9FtNsSHVlSWd+nRlmGoAbqbeCwOryP6sC/zoCjhpArcRMYp19EvpSUSizJAlsXEwJv+wcWS9XaXdMw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, "@sinonjs/formatio": { @@ -370,7 +370,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -956,11 +955,21 @@ } } }, + "babel-plugin-macros": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.4.5.tgz", + "integrity": "sha512-+/9yteNQw3yuZ3krQUfjAeoT/f4EAdn3ELwhFfDj0rTMIaoHfIdrcLePOfIaL0qmFLpIcgPIL2Lzm58h+CGWaw==", + "requires": { + "cosmiconfig": "^5.0.5", + "resolve": "^1.8.1" + } + }, "babel-plugin-relay": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-relay/-/babel-plugin-relay-1.6.0.tgz", - "integrity": "sha1-oiTaUkNi1pA6UkIUobhAUw/fvSg=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/babel-plugin-relay/-/babel-plugin-relay-1.7.0.tgz", + "integrity": "sha512-4kDgElsQ3+m1YHGinm2CWu55XzpPqEzf42JuYWUAJWvTBcHkd/VGVftO9C6BjnssUU7fDH9izn3qMtp0XFWGKw==", "requires": { + "babel-plugin-macros": "^2.0.0", "babel-runtime": "^6.23.0", "babel-types": "^6.24.1" } @@ -1821,6 +1830,21 @@ "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", "dev": true }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + } + } + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -2152,6 +2176,28 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cosmiconfig": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz", + "integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, "cross-env": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", @@ -2842,7 +2888,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -3164,8 +3209,7 @@ "esprima": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=", - "dev": true + "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=" }, "esquery": { "version": "1.0.1", @@ -3417,16 +3461,16 @@ "dev": true }, "fast-glob": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.3.tgz", - "integrity": "sha512-NiX+JXjnx43RzvVFwRWfPKo4U+1BrK5pJPsHQdKMlLoFHrrGktXglQhHliSihWAq+m1z6fHk3uwGHrtRbS9vLA==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.6.tgz", + "integrity": "sha512-0BvMaZc1k9F+MeWWMe8pL6YltFzZYcJsYU7D4JyDA6PAczaXvxqQQ/z+mDF7/4Mw01DeUc+i3CTKajnkANkV4w==", "dev": true, "requires": { "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.0.1", + "@nodelib/fs.stat": "^1.1.2", "glob-parent": "^3.1.0", "is-glob": "^4.0.0", - "merge2": "^1.2.1", + "merge2": "^1.2.3", "micromatch": "^3.1.10" } }, @@ -3451,9 +3495,9 @@ } }, "fbjs": { - "version": "0.8.16", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", - "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", + "version": "0.8.17", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", + "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", "requires": { "core-js": "^1.0.0", "isomorphic-fetch": "^2.1.1", @@ -3461,22 +3505,13 @@ "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.9" + "ua-parser-js": "^0.7.18" }, "dependencies": { "core-js": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } } } }, @@ -4116,6 +4151,30 @@ "integrity": "sha1-E7TTyxK++hVIKib+Gy665kAHHks=", "dev": true }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "dependencies": { + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + } + } + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4260,8 +4319,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-boolean-object": { "version": "1.0.0", @@ -4334,6 +4392,11 @@ } } }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -4705,7 +4768,6 @@ "version": "3.11.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", "integrity": "sha1-WXwai9VxUvJtYizkEXhRpR9euu8=", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -4722,6 +4784,11 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -6230,8 +6297,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "1.7.0", @@ -6543,14 +6609,14 @@ "dev": true }, "react-relay": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/react-relay/-/react-relay-1.6.0.tgz", - "integrity": "sha512-8clmRHXNo96pcdkA8ZeiqF7xGjE+mjSbdX/INj5upRm2M8AprSrFk2Oz5nH084O+0hvXQhZtFyraXJWQO9ld3A==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/react-relay/-/react-relay-1.7.0.tgz", + "integrity": "sha512-vZOs1iK6LxqeaAelwSuD5eVXnQux5eVIrik/kxKt6Y3j6ylrjrdTadmgO6sapGc0TG61VtFK5CKPOtW+XSNotg==", "requires": { "babel-runtime": "^6.23.0", - "fbjs": "^0.8.14", + "fbjs": "0.8.17", "prop-types": "^15.5.8", - "relay-runtime": "1.6.0" + "relay-runtime": "1.7.0" } }, "react-select": { @@ -6843,33 +6909,10 @@ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, - "fbjs": { - "version": "0.8.17", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", - "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", - "dev": true, - "requires": { - "core-js": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" - }, - "dependencies": { - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", - "dev": true - } - } - }, "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, "lodash": { @@ -6883,32 +6926,16 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", "dev": true - }, - "relay-runtime": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-1.7.0.tgz", - "integrity": "sha512-gvx01aRoLHdIMQoIjMQ79js4BR9JZVfF/SoSiLXvWOgDWEnD7RKb80zmCZTByCpka0GwFzkVwBWUy1gW6g0zlQ==", - "dev": true, - "requires": { - "babel-runtime": "^6.23.0", - "fbjs": "0.8.17" - } - }, - "ua-parser-js": { - "version": "0.7.19", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", - "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==", - "dev": true } } }, "relay-runtime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-1.6.0.tgz", - "integrity": "sha512-UJiEHp8CX2uFxXdM0nVLTCQ6yAT0GLmyMceXLISuW/l2a9jrS9a4MdZgdr/9UkkYno7Sj1hU/EUIQ0GaVkou8g==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-1.7.0.tgz", + "integrity": "sha512-gvx01aRoLHdIMQoIjMQ79js4BR9JZVfF/SoSiLXvWOgDWEnD7RKb80zmCZTByCpka0GwFzkVwBWUy1gW6g0zlQ==", "requires": { "babel-runtime": "^6.23.0", - "fbjs": "^0.8.14" + "fbjs": "0.8.17" } }, "repeat-element": { @@ -7001,7 +7028,6 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", - "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -7484,8 +7510,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.14.1", @@ -8101,9 +8126,9 @@ "dev": true }, "ua-parser-js": { - "version": "0.7.14", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.14.tgz", - "integrity": "sha1-EQ1T+kw/MmwSEpK76skE0uAzh8o=" + "version": "0.7.19", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", + "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==" }, "uglify-js": { "version": "2.8.29", diff --git a/package.json b/package.json index 7979e96b16..2a6656ad67 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "atom-babel6-transpiler": "1.2.0", "babel-generator": "6.26.1", "babel-plugin-chai-assert-async": "0.1.0", - "babel-plugin-relay": "1.6.0", + "babel-plugin-relay": "1.7.0", "babel-plugin-transform-class-properties": "6.24.1", "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", "babel-plugin-transform-object-rest-spread": "6.26.0", @@ -61,10 +61,10 @@ "prop-types": "15.6.2", "react": "16.7.0", "react-dom": "16.7.0", - "react-relay": "1.6.0", + "react-relay": "1.7.0", "react-select": "1.2.1", "react-tabs": "^3.0.0", - "relay-runtime": "1.6.0", + "relay-runtime": "1.7.0", "temp": "0.9.0", "tinycolor2": "1.4.1", "tree-kill": "1.2.1", @@ -75,10 +75,10 @@ }, "devDependencies": { "@smashwilson/atom-mocha-test-runner": "1.4.0", + "@smashwilson/codecov": "3.1.1-azure0.0", "babel-plugin-istanbul": "4.1.6", "chai": "4.1.2", "chai-as-promised": "7.1.1", - "@smashwilson/codecov": "3.1.1-azure0.0", "cross-env": "5.2.0", "cross-unzip": "0.2.1", "dedent-js": "1.0.1", From 5a899aa4ffb848bb958af530f3c36953562b10e3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 8 Jan 2019 14:44:57 -0500 Subject: [PATCH 1729/4053] :arrow_up: dev dependencies --- package-lock.json | 2630 +++++++++++++++++++++++++++------------------ package.json | 16 +- 2 files changed, 1569 insertions(+), 1077 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6deb8786ee..7a11d91370 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,73 +11,79 @@ "dev": true }, "@babel/code-frame": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.49.tgz", - "integrity": "sha1-vs2AVIJzREDJ0TfkbXc0DmTX9Rs=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", "dev": true, "requires": { - "@babel/highlight": "7.0.0-beta.49" + "@babel/highlight": "^7.0.0" } }, "@babel/generator": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.49.tgz", - "integrity": "sha1-6c/9qROZaszseTu8JauRvBnQv3o=", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.2.tgz", + "integrity": "sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.49", + "@babel/types": "^7.2.2", "jsesc": "^2.5.1", - "lodash": "^4.17.5", + "lodash": "^4.17.10", "source-map": "^0.5.0", "trim-right": "^1.0.1" }, "dependencies": { "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true } } }, "@babel/helper-function-name": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.49.tgz", - "integrity": "sha1-olwRGbnwNSeGcBJuAiXAMEHI3jI=", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.49", - "@babel/template": "7.0.0-beta.49", - "@babel/types": "7.0.0-beta.49" + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" } }, "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.49.tgz", - "integrity": "sha1-z1Aj8y0q2S0Ic3STnOwJUby1FEE=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.49" + "@babel/types": "^7.0.0" } }, "@babel/helper-split-export-declaration": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.49.tgz", - "integrity": "sha1-QNeO2glo0BGxxShm5XRs+yPldUg=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.49" + "@babel/types": "^7.0.0" } }, "@babel/highlight": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.49.tgz", - "integrity": "sha1-lr3GtD4TSCASumaRsQGEktOWIsw=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", "dev": true, "requires": { "chalk": "^2.0.0", "esutils": "^2.0.2", - "js-tokens": "^3.0.0" + "js-tokens": "^4.0.0" }, "dependencies": { "ansi-styles": { @@ -90,9 +96,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -100,10 +106,16 @@ "supports-color": "^5.3.0" } }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -112,67 +124,85 @@ } }, "@babel/parser": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.49.tgz", - "integrity": "sha1-lE0MW6KBK7FZ7b0iZ0Ov0mUXm9w=", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.3.tgz", + "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==", "dev": true }, "@babel/template": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.49.tgz", - "integrity": "sha1-44q+ghfLl5P0YaUwbXrXRdg+HSc=", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.49", - "@babel/parser": "7.0.0-beta.49", - "@babel/types": "7.0.0-beta.49", - "lodash": "^4.17.5" + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.2.2", + "@babel/types": "^7.2.2" } }, "@babel/traverse": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.49.tgz", - "integrity": "sha1-TypzaCoYM07WYl0QCo0nMZ98LWg=", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", + "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.49", - "@babel/generator": "7.0.0-beta.49", - "@babel/helper-function-name": "7.0.0-beta.49", - "@babel/helper-split-export-declaration": "7.0.0-beta.49", - "@babel/parser": "7.0.0-beta.49", - "@babel/types": "7.0.0-beta.49", - "debug": "^3.1.0", + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.2.3", + "@babel/types": "^7.2.2", + "debug": "^4.1.0", "globals": "^11.1.0", - "invariant": "^2.2.0", - "lodash": "^4.17.5" + "lodash": "^4.17.10" }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", + "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", + "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true } } }, "@babel/types": { - "version": "7.0.0-beta.49", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.49.tgz", - "integrity": "sha1-t+Oxw/TUz+Eb34yJ8e/V4WF7h6Y=", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.2.tgz", + "integrity": "sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==", "dev": true, "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.5", + "lodash": "^4.17.10", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + } } }, "@mrmlnc/readdir-enhanced": { @@ -191,13 +221,33 @@ "integrity": "sha512-yprFYuno9FtNsSHVlSWd+nRlmGoAbqbeCwOryP6sC/zoCjhpArcRMYp19EvpSUSizJAlsXEwJv+wcWS9XaXdMw==", "dev": true }, + "@sinonjs/commons": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", + "integrity": "sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, "@sinonjs/formatio": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", - "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz", + "integrity": "sha512-ZAR2bPHOl4Xg6eklUGpsdiIJ4+J1SNag1DHHrG/73Uz/nVwXqjgUtRPLoS+aVyieN9cSbc0E4LsU984tWcDyNg==", + "dev": true, + "requires": { + "@sinonjs/samsam": "^2 || ^3" + } + }, + "@sinonjs/samsam": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.0.2.tgz", + "integrity": "sha512-m08g4CS3J6lwRQk1pj1EO+KEVWbrbXsmi9Pw0ySmrIbcVxVaedoFgLvFsV8wHLwh01EpROVz3KvVcD1Jmks9FQ==", "dev": true, "requires": { - "samsam": "1.3.0" + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash.get": "^4.4.2" } }, "@smashwilson/atom-mocha-test-runner": { @@ -255,19 +305,16 @@ } }, "acorn": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", - "integrity": "sha1-8JWCkpdwanyXdpWMCvyJMKm52dg=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz", + "integrity": "sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg==", "dev": true }, "acorn-jsx": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", - "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", - "dev": true, - "requires": { - "acorn": "^5.0.3" - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true }, "agent-base": { "version": "4.2.1", @@ -289,38 +336,6 @@ "json-schema-traverse": "^0.3.0" } }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", - "dev": true - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, "ansi-escapes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", @@ -337,26 +352,11 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, - "append-transform": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", - "dev": true, - "requires": { - "default-require-extensions": "^2.0.0" - } - }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, "are-we-there-yet": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", @@ -415,6 +415,12 @@ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "dev": true }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, "array-includes": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", @@ -491,7 +497,7 @@ "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha1-5gtrDo8wG9l+U3UhW9pAbIURjAs=", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, "assign-symbols": { @@ -522,10 +528,10 @@ "private": "~0.1.6" } }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, "asynckit": { @@ -559,9 +565,9 @@ "dev": true }, "axobject-query": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.1.tgz", - "integrity": "sha1-Bd+nBa2orZ25k/polvItOVsLCgc=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", + "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", "dev": true, "requires": { "ast-types-flow": "0.0.7" @@ -916,43 +922,58 @@ } }, "babel-plugin-istanbul": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", - "integrity": "sha1-NsWbIZLvzoHFs3gyG3QXWt0cmkU=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.0.tgz", + "integrity": "sha512-CLoXPRSUWiR8yao8bShqZUIC6qLfZVVY3X1wj+QPNXu0wfmrRRfarh1LYy+dYMVI+bDj0ghy3tuqFFRFZmL1Nw==", "dev": true, "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.13.0", - "find-up": "^2.1.0", - "istanbul-lib-instrument": "^1.10.1", - "test-exclude": "^4.2.1" + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.0.0", + "test-exclude": "^5.0.0" }, "dependencies": { - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } }, - "istanbul-lib-coverage": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", - "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", - "dev": true + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } }, - "istanbul-lib-instrument": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz", - "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.0", - "semver": "^5.3.0" + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true } } }, @@ -1804,36 +1825,16 @@ "unset-value": "^1.0.0" } }, - "caching-transform": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-1.0.1.tgz", - "integrity": "sha1-bb2y8g+Nj7znnz6U6dF0Lc31wKE=", - "dev": true, - "requires": { - "md5-hex": "^1.2.0", - "mkdirp": "^0.5.1", - "write-file-atomic": "^1.1.4" - } - }, "call-me-maybe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", "dev": true }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } - }, "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", "dev": true }, "camelcase": { @@ -1871,27 +1872,18 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } - }, "chai": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", - "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", "dev": true, "requires": { - "assertion-error": "^1.0.1", - "check-error": "^1.0.1", - "deep-eql": "^3.0.0", + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", "get-func-name": "^2.0.0", - "pathval": "^1.0.0", - "type-detect": "^4.0.0" + "pathval": "^1.1.0", + "type-detect": "^4.0.5" } }, "chai-as-promised": { @@ -1916,9 +1908,9 @@ } }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "charenc": { @@ -1963,7 +1955,7 @@ "circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY=", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, "class-utils": { @@ -2060,17 +2052,19 @@ } }, "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha1-wSYRB66y8pTr/+ye2eytUppgl+0=", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true }, "colors": { "version": "0.5.1", @@ -2092,12 +2086,6 @@ "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, "compare-sets": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/compare-sets/-/compare-sets-1.0.1.tgz", @@ -2255,12 +2243,6 @@ "ms": "2.0.0" } }, - "debug-log": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", - "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", - "dev": true - }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -2290,7 +2272,7 @@ "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { "type-detect": "^4.0.0" @@ -2313,15 +2295,6 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "default-require-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", - "dev": true, - "requires": { - "strip-bom": "^3.0.0" - } - }, "deferred-leveldown": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-4.0.2.tgz", @@ -2383,21 +2356,6 @@ } } }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2427,6 +2385,33 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, "discontinuous-range": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", @@ -2892,31 +2877,32 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.0.1.tgz", - "integrity": "sha512-D5nG2rErquLUstgUaxJlWB5+gu+U/3VDY0fk/Iuq8y9CUFy/7Y6oF4N2cR1tV8knzQvciIbfqfohd359xTLIKQ==", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.12.0.tgz", + "integrity": "sha512-LntwyPxtOHrsJdcSwyQKVtHofPHdv+4+mFwEe91r2V13vqpM8yLr7b1sW+Oo/yheOPkWYsYlYJCkzlFAt8KV7g==", "dev": true, "requires": { - "ajv": "^6.5.0", - "babel-code-frame": "^6.26.0", + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", "chalk": "^2.1.0", "cross-spawn": "^6.0.5", - "debug": "^3.1.0", + "debug": "^4.0.1", "doctrine": "^2.1.0", "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", "eslint-visitor-keys": "^1.0.0", - "espree": "^4.0.0", + "espree": "^5.0.0", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^2.0.0", "functional-red-black-tree": "^1.0.1", "glob": "^7.1.2", - "globals": "^11.5.0", - "ignore": "^3.3.3", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^5.2.0", - "is-resolvable": "^1.1.0", - "js-yaml": "^3.11.0", + "inquirer": "^6.1.0", + "js-yaml": "^3.12.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", "lodash": "^4.17.5", @@ -2927,26 +2913,24 @@ "path-is-inside": "^1.0.2", "pluralize": "^7.0.0", "progress": "^2.0.0", - "regexpp": "^1.1.0", - "require-uncached": "^1.0.3", - "semver": "^5.5.0", - "string.prototype.matchall": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", "strip-ansi": "^4.0.0", "strip-json-comments": "^2.0.1", - "table": "^4.0.3", + "table": "^5.0.2", "text-table": "^0.2.0" }, "dependencies": { "ajv": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", - "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.1" + "uri-js": "^4.2.2" } }, "ansi-regex": { @@ -2958,16 +2942,16 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha1-GMSasWoDe26wFSzIPjRxM4IVtm4=", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -2989,12 +2973,12 @@ } }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "doctrine": { @@ -3013,9 +2997,9 @@ "dev": true }, "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", + "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", "dev": true }, "json-schema-traverse": { @@ -3024,6 +3008,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -3034,9 +3024,9 @@ } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -3081,9 +3071,9 @@ "dev": true }, "eslint-plugin-jsx-a11y": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.1.tgz", - "integrity": "sha512-JsxNKqa3TwmPypeXNnI75FntkUktGzI1wSa1LgNZdSOMI+B4sxnr1lSF8m8lPiz4mKiC+14ysZQM4scewUrP7A==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.2.tgz", + "integrity": "sha512-7gSSmwb3A+fQwtw0arguwMdOdzmKUgnUcbSNlo+GjKLAQFuC2EZxWqG9XHRI8VscBJD5a8raz3RuxQNFW+XJbw==", "dev": true, "requires": { "aria-query": "^3.0.0", @@ -3145,20 +3135,27 @@ "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", "dev": true }, "espree": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", - "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.0.tgz", + "integrity": "sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA==", "dev": true, "requires": { - "acorn": "^5.6.0", - "acorn-jsx": "^4.1.1" + "acorn": "^6.0.2", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" } }, "esprima": { @@ -3179,7 +3176,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -3302,16 +3299,25 @@ } }, "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" }, "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -3537,17 +3543,6 @@ } } }, - "find-cache-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^1.0.0", - "pkg-dir": "^2.0.0" - } - }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -3558,14 +3553,14 @@ } }, "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", "dev": true, "requires": { "circular-json": "^0.3.1", - "del": "^2.0.2", "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", "write": "^0.2.1" } }, @@ -3581,32 +3576,6 @@ "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", "dev": true }, - "foreground-child": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", - "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", - "dev": true, - "requires": { - "cross-spawn": "^4", - "signal-exit": "^3.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "yallist": { - "version": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - } - } - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -3808,21 +3777,36 @@ "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=" }, "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", + "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", "dev": true, "requires": { "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "graceful-fs": { - "version": "4.1.11", + "dir-glob": "^2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "dependencies": { + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, @@ -3860,45 +3844,6 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", - "dev": true, - "requires": { - "async": "^1.4.0", - "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -3934,7 +3879,8 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, "has-symbols": { "version": "1.0.0", @@ -4089,9 +4035,9 @@ "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" }, "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha1-Cpf7h2mG6AgcYxFg+PnziRV/AEM=", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "ignore-walk": { @@ -4116,6 +4062,16 @@ "integrity": "sha1-E7TTyxK++hVIKib+Gy665kAHHks=", "dev": true }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4157,45 +4113,45 @@ "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" }, "inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", + "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", "dev": true, "requires": { "ansi-escapes": "^3.0.0", "chalk": "^2.0.0", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^2.1.0", + "external-editor": "^3.0.0", "figures": "^2.0.0", - "lodash": "^4.3.0", + "lodash": "^4.17.10", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rxjs": "^5.5.2", + "rxjs": "^6.1.0", "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.0.0", "through": "^2.3.6" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", "dev": true }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha1-GMSasWoDe26wFSzIPjRxM4IVtm4=", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -4203,19 +4159,25 @@ "supports-color": "^5.3.0" } }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", + "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.0.0" } }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -4272,7 +4234,8 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=" + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", + "dev": true }, "is-builtin-module": { "version": "1.0.0", @@ -4420,30 +4383,6 @@ } } }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -4467,12 +4406,6 @@ "has": "^1.0.1" } }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", - "dev": true - }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -4522,7 +4455,8 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isobject": { "version": "3.0.1", @@ -4555,140 +4489,24 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul-lib-coverage": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz", - "integrity": "sha512-yMSw5xLIbdaxiVXHk3amfNM2WeBxLrwH/BCyZ9HvA/fylwziAIJOG2rKqWyLqEJqwKT725vxxqidv+SyynnGAA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", + "integrity": "sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==", "dev": true }, - "istanbul-lib-hook": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.0.tgz", - "integrity": "sha512-qm3dt628HKpCVtIjbdZLuQyXn0+LO8qz+YHQDfkeXuSk5D+p299SEV5DrnUUnPi2SXvdMmWapMYWiuE75o2rUQ==", - "dev": true, - "requires": { - "append-transform": "^1.0.0" - } - }, "istanbul-lib-instrument": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-2.2.0.tgz", - "integrity": "sha512-ozQGtlIw+/a/F3n6QwWiuuyRAPp64+g2GVsKYsIez0sgIEzkU5ZpL2uZ5pmAzbEJ82anlRaPlOQZzkRXspgJyg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.0.0.tgz", + "integrity": "sha512-eQY9vN9elYjdgN9Iv6NS/00bptm02EBBk70lRMaVjeA6QYocQgenVrSgC28TJurdnZa80AGO3ASdFN+w/njGiQ==", "dev": true, "requires": { - "@babel/generator": "7.0.0-beta.49", - "@babel/parser": "7.0.0-beta.49", - "@babel/template": "7.0.0-beta.49", - "@babel/traverse": "7.0.0-beta.49", - "@babel/types": "7.0.0-beta.49", - "istanbul-lib-coverage": "^2.0.0", + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.1", "semver": "^5.5.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==" - }, - "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=" - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "istanbul-lib-report": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.0.tgz", - "integrity": "sha512-RiELmy9oIRYUv36ITOAhVum9PUvuj6bjyXVEKEHNiD1me6qXtxfx7vSEJWnjOGk2QmYw/GRFjLXWJv3qHpLceQ==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^2.0.0", - "make-dir": "^1.3.0", - "supports-color": "^5.4.0" - }, - "dependencies": { - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-2.0.0.tgz", - "integrity": "sha512-jenUeC0gMSSMGkvqD9xuNfs3nD7XWeXLhqaIkqHsNZ3DJBWPdlKEydE7Ya5aTgdWjrEQhrCYTv+J606cGC2vuQ==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^2.0.0", - "make-dir": "^1.3.0", - "rimraf": "^2.6.2", - "source-map": "^0.6.1" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.0.tgz", - "integrity": "sha512-HeZG0WHretI9FXBni5wZ9DOgNziqDCEwetxnme5k1Vv5e81uTqcsy3fMH99gXGDGKr1ea87TyGseDMa2h4HEUA==", - "dev": true, - "requires": { - "handlebars": "^4.0.11" } }, "iterall": { @@ -4702,9 +4520,9 @@ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", - "integrity": "sha1-WXwai9VxUvJtYizkEXhRpR9euu8=", + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", + "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -4722,6 +4540,12 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -4774,9 +4598,9 @@ "dev": true }, "just-extend": { - "version": "1.1.27", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", - "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", "dev": true }, "keytar": { @@ -4794,11 +4618,6 @@ "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" - }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -4994,16 +4813,11 @@ "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" }, "lolex": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.1.tgz", - "integrity": "sha1-5AqMTR8UtTaqA+QqU3x6268MIL4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.0.0.tgz", + "integrity": "sha512-hcnW80h3j2lbUfFdMArd5UPA/vxZJ+G8vobd+wg3nVEQA0EigStbYcrG030FJxL6xiDDPEkoMatV9xIh5OecQQ==", "dev": true }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" - }, "loose-envify": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", @@ -5026,6 +4840,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", + "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -5034,23 +4849,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - } - } - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true } } @@ -5087,21 +4886,6 @@ "is-buffer": "~1.1.1" } }, - "md5-hex": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.3.0.tgz", - "integrity": "sha1-0sSv6YPENwZiF5uMrRRSGRNQRsQ=", - "dev": true, - "requires": { - "md5-o-matic": "^0.1.1" - } - }, - "md5-o-matic": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz", - "integrity": "sha1-givM1l4RfFFPqxdrJZRdVBAKA8M=", - "dev": true - }, "mem": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", @@ -5204,23 +4988,6 @@ } } }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "merge2": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", @@ -5546,16 +5313,24 @@ "dev": true }, "nise": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.2.tgz", - "integrity": "sha512-BxH/DxoQYYdhKgVAfqVy4pzXRZELHOIewzoesxpjYvpU+7YOalQhGNPf7wAx8pLrTNPrHRDlLOkAl8UI0ZpXjw==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.8.tgz", + "integrity": "sha512-kGASVhuL4tlAV0tvA34yJYZIVihrUt/5bDwpp4tTluigxUr2bBlJeDXmivb6NuEdFkqvdv/Ybb9dm16PSKUhtw==", "dev": true, "requires": { - "@sinonjs/formatio": "^2.0.0", - "just-extend": "^1.1.27", + "@sinonjs/formatio": "^3.1.0", + "just-extend": "^4.0.2", "lolex": "^2.3.2", "path-to-regexp": "^1.7.0", "text-encoding": "^0.6.4" + }, + "dependencies": { + "lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + } } }, "node-abi": { @@ -5663,242 +5438,1061 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nyc": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.0.0.tgz", - "integrity": "sha1-4Awm6b0zq16B7emSu+STCEhYmbY=", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.1.0.tgz", + "integrity": "sha512-3GyY6TpQ58z9Frpv4GMExE1SV2tAgYqC7HSy2omEhNiCT3mhT9NyiOvIE8zkbuJVFzmvvNTnE4h/7/wQae7xLg==", "dev": true, "requires": { "archy": "^1.0.0", "arrify": "^1.0.1", - "caching-transform": "^1.0.1", - "convert-source-map": "^1.5.1", + "caching-transform": "^2.0.0", + "convert-source-map": "^1.6.0", "debug-log": "^1.0.1", - "find-cache-dir": "^1.0.0", - "find-up": "^2.1.0", + "find-cache-dir": "^2.0.0", + "find-up": "^3.0.0", "foreground-child": "^1.5.6", - "glob": "^7.1.2", - "istanbul-lib-coverage": "^2.0.0", - "istanbul-lib-hook": "^2.0.0", - "istanbul-lib-instrument": "^2.2.0", - "istanbul-lib-report": "^2.0.0", - "istanbul-lib-source-maps": "^2.0.0", - "istanbul-reports": "^1.5.0", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.1", + "istanbul-lib-hook": "^2.0.1", + "istanbul-lib-instrument": "^3.0.0", + "istanbul-lib-report": "^2.0.2", + "istanbul-lib-source-maps": "^2.0.1", + "istanbul-reports": "^2.0.1", "make-dir": "^1.3.0", - "md5-hex": "^2.0.0", "merge-source-map": "^1.1.0", "resolve-from": "^4.0.0", "rimraf": "^2.6.2", "signal-exit": "^3.0.2", "spawn-wrap": "^1.4.2", - "test-exclude": "^4.2.2", + "test-exclude": "^5.0.0", + "uuid": "^3.3.2", "yargs": "11.1.0", "yargs-parser": "^9.0.2" }, "dependencies": { + "align-text": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "amdefine": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "bundled": true, "dev": true }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "append-transform": { + "version": "1.0.0", + "bundled": true, + "dev": true, "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" - } + "default-require-extensions": "^2.0.0" } }, - "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "requires": { - "lru-cache": "^4.0.1", + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "async": { + "version": "1.5.2", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "caching-transform": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "make-dir": "^1.0.0", + "md5-hex": "^2.0.0", + "package-hash": "^2.0.0", + "write-file-atomic": "^2.0.0" + } + }, + "camelcase": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true + }, + "center-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "cliui": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", "which": "^1.2.9" } }, "debug": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "bundled": true, + "dev": true, "requires": { "ms": "2.0.0" } }, + "debug-log": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "error-ex": { + "version": "1.3.2", + "bundled": true, + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-error": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "find-cache-dir": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "bundled": true, + "dev": true, + "requires": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "2.7.1", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "istanbul-lib-coverage": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.1", + "make-dir": "^1.3.0", + "supports-color": "^5.4.0" + } + }, + "istanbul-lib-source-maps": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "debug": "^3.1.0", + "istanbul-lib-coverage": "^2.0.1", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "^4.0.11" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true, + "dev": true }, "kind-of": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "bundled": true, + "dev": true, "requires": { "is-buffer": "^1.1.5" } }, + "lazy-cache": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash.flattendeep": { + "version": "4.4.0", + "bundled": true, + "dev": true + }, + "longest": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "lru-cache": { + "version": "4.1.3", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, "md5-hex": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-2.0.0.tgz", - "integrity": "sha1-0FiOnxx0lUSS7NJKwKxs6ZfZLjM=", + "bundled": true, "dev": true, "requires": { "md5-o-matic": "^0.1.1" } }, + "md5-o-matic": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "merge-source-map": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "version": "0.0.10", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } }, "optimist": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "bundled": true, + "dev": true, "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" } }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "package-hash": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "lodash.flattendeep": "^4.4.0", + "md5-hex": "^2.0.0", + "release-zalgo": "^1.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "release-zalgo": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true, + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, "resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "bundled": true, + "dev": true + }, + "right-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, "dev": true }, "semver": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true, + "optional": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } }, "strip-ansi": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "bundled": true, "dev": true, "requires": { "ansi-regex": "^3.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, "supports-color": { "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "bundled": true, + "dev": true, "requires": { "has-flag": "^3.0.0" } }, "test-exclude": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.2.tgz", - "integrity": "sha512-2kTGf+3tykCfrWVREgyTR0bmVO0afE6i7zVXi/m+bZZ8ujV89Aulxdcdv32yH+unVFg3Y5o6GA8IzsHnGQuFgQ==", + "version": "5.0.0", + "bundled": true, "dev": true, "requires": { "arrify": "^1.0.1", "minimatch": "^3.0.4", - "read-pkg-up": "^3.0.0", + "read-pkg-up": "^4.0.0", "require-main-filename": "^1.0.1" + } + }, + "uglify-js": { + "version": "2.8.29", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" }, "dependencies": { - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "yargs": { + "version": "3.10.0", + "bundled": true, "dev": true, + "optional": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "dependencies": { - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - } + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "uuid": { + "version": "3.3.2", + "bundled": true, + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "window-size": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, "dev": true, "requires": { - "pify": "^3.0.0" + "number-is-nan": "^1.0.0" } }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "string-width": { + "version": "1.0.2", + "bundled": true, "dev": true, "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "strip-ansi": { + "version": "3.0.1", + "bundled": true, "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" + "ansi-regex": "^2.0.0" } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true } } }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + }, "yallist": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "bundled": true, + "dev": true }, "yargs": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "bundled": true, "dev": true, "requires": { "cliui": "^4.0.0", @@ -5915,16 +6509,9 @@ "yargs-parser": "^9.0.2" }, "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, "cliui": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "bundled": true, "dev": true, "requires": { "string-width": "^2.1.1", @@ -5932,21 +6519,49 @@ "wrap-ansi": "^2.0.0" } }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, "dev": true, "requires": { - "camelcase": "^4.1.0" + "p-limit": "^1.1.0" } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, + "dev": true } } }, "yargs-parser": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "bundled": true, "dev": true, "requires": { "camelcase": "^4.1.0" @@ -5954,8 +6569,7 @@ "dependencies": { "camelcase": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "bundled": true, "dev": true } } @@ -6174,6 +6788,15 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, + "parent-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", + "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -6289,15 +6912,6 @@ "pinkie": "^2.0.0" } }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", @@ -6405,7 +7019,8 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true }, "psl": { "version": "1.1.29", @@ -6709,19 +7324,10 @@ } } }, - "regexp.prototype.flags": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", - "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", - "dev": true, - "requires": { - "define-properties": "^1.1.2" - } - }, "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, "relay-compiler": { @@ -6920,7 +7526,8 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true }, "repeating": { "version": "2.0.1", @@ -6987,16 +7594,6 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, "resolve": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", @@ -7007,9 +7604,9 @@ } }, "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "resolve-url": { @@ -7034,14 +7631,6 @@ "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", "dev": true }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "requires": { - "align-text": "^0.1.1" - } - }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", @@ -7070,12 +7659,12 @@ } }, "rxjs": { - "version": "5.5.11", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", - "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "dev": true, "requires": { - "symbol-observable": "1.0.1" + "tslib": "^1.9.0" } }, "safe-buffer": { @@ -7092,10 +7681,10 @@ "ret": "~0.1.10" } }, - "samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "scheduler": { @@ -7210,24 +7799,24 @@ } }, "sinon": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-6.0.1.tgz", - "integrity": "sha512-rfszhNcfamK2+ofIPi9XqeH89pH7KGDcAtM+F9CsjHXOK3jzWG99vyhyD2V+r7s4IipmWcWUFYq4ftZ9/Eu2Wg==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.2.tgz", + "integrity": "sha512-WLagdMHiEsrRmee3jr6IIDntOF4kbI6N2pfbi8wkv50qaUQcBglkzkjtoOEbeJ2vf1EsrHhLI+5Ny8//WHdMoA==", "dev": true, "requires": { - "@sinonjs/formatio": "^2.0.0", + "@sinonjs/commons": "^1.2.0", + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/samsam": "^3.0.2", "diff": "^3.5.0", - "lodash.get": "^4.4.2", - "lolex": "^2.4.2", - "nise": "^1.3.3", - "supports-color": "^5.4.0", - "type-detect": "^4.0.8" + "lolex": "^3.0.0", + "nise": "^1.4.7", + "supports-color": "^5.5.0" }, "dependencies": { "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -7241,14 +7830,25 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" }, "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.0.0.tgz", + "integrity": "sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ==", "dev": true, "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -7257,12 +7857,6 @@ } } }, - "slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", - "dev": true - }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -7402,20 +7996,6 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, - "spawn-wrap": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.2.tgz", - "integrity": "sha512-vMwR3OmmDhnxCVxM8M+xO/FtIp6Ju/mNaDfCMMW7FDcLRTPFWUswec4LXJHTJE2hwTI9O0YBfygu4DalFl7Ylg==", - "dev": true, - "requires": { - "foreground-child": "^1.5.6", - "mkdirp": "^0.5.0", - "os-homedir": "^1.0.1", - "rimraf": "^2.6.2", - "signal-exit": "^3.0.2", - "which": "^1.3.0" - } - }, "spdx-correct": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", @@ -7562,19 +8142,6 @@ } } }, - "string.prototype.matchall": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-2.0.0.tgz", - "integrity": "sha512-WoZ+B2ypng1dp4iFLF2kmZlwwlE19gmjgKuhL1FJfDgCREWb3ye3SDVHSzLH6bxfnvYmkCxbzkmWcQZHA4P//Q==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.10.0", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "regexp.prototype.flags": "^1.2.0" - } - }, "string.prototype.trim": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", @@ -7642,56 +8209,28 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", - "dev": true - }, "table": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", - "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/table/-/table-5.1.1.tgz", + "integrity": "sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw==", "dev": true, "requires": { - "ajv": "^6.0.1", - "ajv-keywords": "^3.0.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", + "ajv": "^6.6.1", + "lodash": "^4.17.11", + "slice-ansi": "2.0.0", "string-width": "^2.1.1" }, "dependencies": { "ajv": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", - "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.1" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha1-GMSasWoDe26wFSzIPjRxM4IVtm4=", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "uri-js": "^4.2.2" } }, "fast-deep-equal": { @@ -7706,14 +8245,11 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true } } }, @@ -7802,89 +8338,116 @@ } }, "test-exclude": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.1.tgz", - "integrity": "sha1-36Ii8DSAvKaSB8pyizfXS0X3JPo=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.0.0.tgz", + "integrity": "sha512-bO3Lj5+qFa9YLfYW2ZcXMOV1pmQvw+KS/DpjqhyX6Y6UZ8zstpZJ+mA2ERkXfpOqhxsJlQiLeVXD3Smsrs6oLw==", "dev": true, "requires": { "arrify": "^1.0.1", - "micromatch": "^3.1.8", - "object-assign": "^4.1.0", - "read-pkg-up": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", "require-main-filename": "^1.0.1" }, "dependencies": { "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" + "locate-path": "^3.0.0" } }, "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" } }, - "path-exists": { + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "p-try": "^2.0.0" } }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "p-limit": "^2.0.0" } }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "pify": "^3.0.0" } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" } } } @@ -8065,6 +8628,12 @@ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -8091,7 +8660,7 @@ "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "typedarray": { @@ -8105,66 +8674,6 @@ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.14.tgz", "integrity": "sha1-EQ1T+kw/MmwSEpK76skE0uAzh8o=" }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "optional": true, - "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" - }, - "dependencies": { - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "optional": true - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "optional": true, - "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true, - "optional": true - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, "underscore": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", @@ -8396,6 +8905,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -8431,13 +8941,6 @@ } } }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true - }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", @@ -8480,17 +8983,6 @@ "mkdirp": "^0.5.1" } }, - "write-file-atomic": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", - "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "slide": "^1.1.5" - } - }, "x-is-array": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/x-is-array/-/x-is-array-0.1.0.tgz", diff --git a/package.json b/package.json index 7979e96b16..61d7625d5a 100644 --- a/package.json +++ b/package.json @@ -75,10 +75,10 @@ }, "devDependencies": { "@smashwilson/atom-mocha-test-runner": "1.4.0", - "babel-plugin-istanbul": "4.1.6", - "chai": "4.1.2", - "chai-as-promised": "7.1.1", "@smashwilson/codecov": "3.1.1-azure0.0", + "babel-plugin-istanbul": "5.1.0", + "chai": "4.2.0", + "chai-as-promised": "7.1.1", "cross-env": "5.2.0", "cross-unzip": "0.2.1", "dedent-js": "1.0.1", @@ -87,10 +87,10 @@ "electron-mksnapshot": "~2.0", "enzyme": "3.8.0", "enzyme-adapter-react-16": "1.7.1", - "eslint": "5.0.1", + "eslint": "5.12.0", "eslint-config-fbjs-opensource": "1.0.0", - "eslint-plugin-jsx-a11y": "^6.1.1", - "globby": "5.0.0", + "eslint-plugin-jsx-a11y": "6.1.2", + "globby": "8.0.1", "hock": "1.3.3", "lodash.isequalwith": "4.4.0", "mkdirp": "0.5.1", @@ -99,10 +99,10 @@ "mocha-multi-reporters": "^1.1.7", "mocha-stress": "1.0.0", "node-fetch": "2.3.0", - "nyc": "13.0.0", + "nyc": "13.1.0", "relay-compiler": "1.7.0", "semver": "5.6.0", - "sinon": "6.0.1", + "sinon": "7.2.2", "test-until": "1.1.1" }, "consumedServices": { From eed5b9d2714fcd506a817120d30d84f6f9f14272 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 8 Jan 2019 15:18:14 -0500 Subject: [PATCH 1730/4053] Relay changed its generated component names --- .../issueish-detail-controller.test.js | 30 +++++++++---------- test/views/issue-detail-view.test.js | 6 ++-- test/views/issueish-timeline-view.test.js | 10 +++---- test/views/pr-detail-view.test.js | 29 +++++++++--------- test/views/pr-statuses-view.test.js | 2 +- .../commit-comment-thread-view.test.js | 2 +- .../cross-referenced-events-view.test.js | 2 +- 7 files changed, 41 insertions(+), 40 deletions(-) diff --git a/test/controllers/issueish-detail-controller.test.js b/test/controllers/issueish-detail-controller.test.js index 422b6b6aab..b3687ca7d2 100644 --- a/test/controllers/issueish-detail-controller.test.js +++ b/test/controllers/issueish-detail-controller.test.js @@ -105,29 +105,29 @@ describe('IssueishDetailController', function() { }); it('is disabled if the repository is loading or absent', function() { const wrapper = shallow(buildApp({}, {isAbsent: true})); - const op = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); + const op = wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp'); assert.isFalse(op.isEnabled()); assert.strictEqual(op.getMessage(), 'No repository found'); wrapper.setProps({isAbsent: false, isLoading: true}); - const op1 = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); + const op1 = wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp'); assert.isFalse(op1.isEnabled()); assert.strictEqual(op1.getMessage(), 'Loading'); wrapper.setProps({isAbsent: false, isLoading: false, isPresent: false}); - const op2 = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); + const op2 = wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp'); assert.isFalse(op2.isEnabled()); assert.strictEqual(op2.getMessage(), 'No repository found'); }); it('is disabled if the local repository is merging or rebasing', function() { const wrapper = shallow(buildApp({}, {isMerging: true})); - const op0 = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); + const op0 = wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp'); assert.isFalse(op0.isEnabled()); assert.strictEqual(op0.getMessage(), 'Merge in progress'); wrapper.setProps({isMerging: false, isRebasing: true}); - const op1 = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); + const op1 = wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp'); assert.isFalse(op1.isEnabled()); assert.strictEqual(op1.getMessage(), 'Rebase in progress'); }); @@ -135,7 +135,7 @@ describe('IssueishDetailController', function() { const props = issueishDetailControllerProps({}, {}); props.repository.pullRequest.headRepository = null; const wrapper = shallow(buildApp({}, {...props})); - const op = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); + const op = wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp'); assert.isFalse(op.isEnabled()); assert.strictEqual(op.getMessage(), 'Pull request head repository does not exist'); }); @@ -159,7 +159,7 @@ describe('IssueishDetailController', function() { remotes, })); - const op = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); + const op = wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp'); assert.isFalse(op.isEnabled()); assert.strictEqual(op.getMessage(), 'Current'); }); @@ -185,7 +185,7 @@ describe('IssueishDetailController', function() { remotes, })); - const op = wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp'); + const op = wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp'); assert.isFalse(op.isEnabled()); assert.strictEqual(op.getMessage(), 'Current'); }); @@ -217,7 +217,7 @@ describe('IssueishDetailController', function() { })); sinon.spy(reporterProxy, 'incrementCounter'); - await wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp').run(); + await wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp').run(); assert.isTrue(addRemote.calledWith('ccc', 'git@github.com:ccc/ddd.git')); assert.isTrue(fetch.calledWith('refs/heads/feature', {remoteName: 'ccc'})); @@ -255,7 +255,7 @@ describe('IssueishDetailController', function() { })); sinon.spy(reporterProxy, 'incrementCounter'); - await wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp').run(); + await wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp').run(); assert.isTrue(fetch.calledWith('refs/heads/clever-name', {remoteName: 'existing'})); assert.isTrue(checkout.calledWith('pr-789/ccc/clever-name', { @@ -298,7 +298,7 @@ describe('IssueishDetailController', function() { })); sinon.spy(reporterProxy, 'incrementCounter'); - await wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp').run(); + await wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp').run(); assert.isTrue(checkout.calledWith('existing')); assert.isTrue(pull.calledWith('refs/heads/yes', {remoteName: 'upstream', ffOnly: true})); @@ -310,7 +310,7 @@ describe('IssueishDetailController', function() { const wrapper = shallow(buildApp({}, {addRemote})); // Should not throw - await wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp').run(); + await wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp').run(); assert.isTrue(addRemote.called); }); @@ -319,7 +319,7 @@ describe('IssueishDetailController', function() { const wrapper = shallow(buildApp({}, {addRemote})); await assert.isRejected( - wrapper.find('Relay(BarePullRequestDetailView)').prop('checkoutOp').run(), + wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('checkoutOp').run(), /not handled by the pipeline/, ); assert.isTrue(addRemote.called); @@ -329,7 +329,7 @@ describe('IssueishDetailController', function() { describe('openCommit', function() { it('opens a CommitDetailItem in the workspace', async function() { const wrapper = shallow(buildApp({}, {workdirPath: __dirname})); - await wrapper.find('Relay(BarePullRequestDetailView)').prop('openCommit')({sha: '1234'}); + await wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('openCommit')({sha: '1234'}); assert.include( atomEnv.workspace.getPaneItems().map(item => item.getURI()), @@ -341,7 +341,7 @@ describe('IssueishDetailController', function() { sinon.stub(reporterProxy, 'addEvent'); const wrapper = shallow(buildApp({}, {workdirPath: __dirname})); - await wrapper.find('Relay(BarePullRequestDetailView)').prop('openCommit')({sha: '1234'}); + await wrapper.find('ForwardRef(Relay(BarePullRequestDetailView))').prop('openCommit')({sha: '1234'}); assert.isTrue( reporterProxy.addEvent.calledWith( diff --git a/test/views/issue-detail-view.test.js b/test/views/issue-detail-view.test.js index ecf93724e1..9417b269c6 100644 --- a/test/views/issue-detail-view.test.js +++ b/test/views/issue-detail-view.test.js @@ -34,7 +34,7 @@ describe('IssueDetailView', function() { assert.strictEqual(link.text(), 'user1/repo#200'); assert.strictEqual(link.prop('href'), 'https://github.com/user1/repo/issues/200'); - assert.isFalse(wrapper.find('Relay(PrStatuses)').exists()); + assert.isFalse(wrapper.find('ForwardRef(Relay(PrStatuses))').exists()); assert.isFalse(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); const avatarLink = wrapper.find('.github-IssueishDetailView-avatar'); @@ -49,8 +49,8 @@ describe('IssueDetailView', function() { assert.lengthOf(wrapper.find(EmojiReactionsView), 1); - assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); - assert.notOk(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); + assert.isNotNull(wrapper.find('ForwardRef(Relay(IssueishTimelineView))').prop('issue')); + assert.notOk(wrapper.find('ForwardRef(Relay(IssueishTimelineView))').prop('pullRequest')); }); it('renders a placeholder issue body', function() { diff --git a/test/views/issueish-timeline-view.test.js b/test/views/issueish-timeline-view.test.js index 9f3479bbe7..4135f9961c 100644 --- a/test/views/issueish-timeline-view.test.js +++ b/test/views/issueish-timeline-view.test.js @@ -71,19 +71,19 @@ describe('IssueishTimelineView', function() { ], })); - const commitGroup0 = wrapper.find('Relay(BareCommitsView)').filterWhere(c => c.prop('nodes').length === 2); + const commitGroup0 = wrapper.find('ForwardRef(Relay(BareCommitsView))').filterWhere(c => c.prop('nodes').length === 2); assert.deepEqual(commitGroup0.prop('nodes').map(n => n.id), [0, 1]); - const commentGroup0 = wrapper.find('Grouped(Relay(BareIssueCommentView))').filterWhere(c => c.prop('nodes').length === 1); + const commentGroup0 = wrapper.find('Grouped(ForwardRef(Relay(BareIssueCommentView)))').filterWhere(c => c.prop('nodes').length === 1); assert.deepEqual(commentGroup0.prop('nodes').map(n => n.id), [2]); - const mergedGroup = wrapper.find('Grouped(Relay(BareMergedEventView))').filterWhere(c => c.prop('nodes').length === 1); + const mergedGroup = wrapper.find('Grouped(ForwardRef(Relay(BareMergedEventView)))').filterWhere(c => c.prop('nodes').length === 1); assert.deepEqual(mergedGroup.prop('nodes').map(n => n.id), [3]); - const commitGroup1 = wrapper.find('Relay(BareCommitsView)').filterWhere(c => c.prop('nodes').length === 4); + const commitGroup1 = wrapper.find('ForwardRef(Relay(BareCommitsView))').filterWhere(c => c.prop('nodes').length === 4); assert.deepEqual(commitGroup1.prop('nodes').map(n => n.id), [4, 5, 6, 7]); - const commentGroup1 = wrapper.find('Grouped(Relay(BareIssueCommentView))').filterWhere(c => c.prop('nodes').length === 2); + const commentGroup1 = wrapper.find('Grouped(ForwardRef(Relay(BareIssueCommentView)))').filterWhere(c => c.prop('nodes').length === 2); assert.deepEqual(commentGroup1.prop('nodes').map(n => n.id), [8, 9]); }); diff --git a/test/views/pr-detail-view.test.js b/test/views/pr-detail-view.test.js index 33f3a0b55e..62364fe02d 100644 --- a/test/views/pr-detail-view.test.js +++ b/test/views/pr-detail-view.test.js @@ -42,7 +42,7 @@ describe('PullRequestDetailView', function() { assert.isTrue(wrapper.find('.github-IssueishDetailView-checkoutButton').exists()); - assert.isDefined(wrapper.find('Relay(BarePrStatusesView)[displayType="check"]').prop('pullRequest')); + assert.isDefined(wrapper.find('ForwardRef(Relay(BarePrStatusesView))[displayType="check"]').prop('pullRequest')); const avatarLink = wrapper.find('.github-IssueishDetailView-avatar'); assert.strictEqual(avatarLink.prop('href'), 'https://github.com/author0'); @@ -56,9 +56,9 @@ describe('PullRequestDetailView', function() { assert.lengthOf(wrapper.find(EmojiReactionsView), 1); - assert.notOk(wrapper.find('Relay(IssueishTimelineView)').prop('issue')); - assert.isNotNull(wrapper.find('Relay(IssueishTimelineView)').prop('pullRequest')); - assert.isNotNull(wrapper.find('Relay(BarePrStatusesView)[displayType="full"]').prop('pullRequest')); + assert.notOk(wrapper.find('ForwardRef(Relay(IssueishTimelineView))').prop('issue')); + assert.isNotNull(wrapper.find('ForwardRef(Relay(IssueishTimelineView))').prop('pullRequest')); + assert.isNotNull(wrapper.find('ForwardRef(Relay(BarePrStatusesView))[displayType="full"]').prop('pullRequest')); assert.strictEqual(wrapper.find('.github-IssueishDetailView-baseRefName').text(), baseRefName); assert.strictEqual(wrapper.find('.github-IssueishDetailView-headRefName').text(), headRefName); @@ -104,28 +104,29 @@ describe('PullRequestDetailView', function() { checkoutOp: new EnableableOperation(() => {}).disable(checkoutStates.CURRENT), })); - assert.isTrue(wrapper.find('Relay(IssueishTimelineView)').prop('onBranch')); - assert.isTrue(wrapper.find('Relay(PrCommitsView)').prop('onBranch')); + assert.isTrue(wrapper.find('ForwardRef(Relay(IssueishTimelineView))').prop('onBranch')); + assert.isTrue(wrapper.find('ForwardRef(Relay(IssueishTimelineView))').prop('onBranch')); + assert.isTrue(wrapper.find('ForwardRef(Relay(PrCommitsView))').prop('onBranch')); }); it('tells its tabs when the pull request is not checked out', function() { const checkoutOp = new EnableableOperation(() => {}); const wrapper = shallow(buildApp({}, {checkoutOp})); - assert.isFalse(wrapper.find('Relay(IssueishTimelineView)').prop('onBranch')); - assert.isFalse(wrapper.find('Relay(PrCommitsView)').prop('onBranch')); + assert.isFalse(wrapper.find('ForwardRef(Relay(IssueishTimelineView))').prop('onBranch')); + assert.isFalse(wrapper.find('ForwardRef(Relay(PrCommitsView))').prop('onBranch')); wrapper.setProps({checkoutOp: checkoutOp.disable(checkoutStates.HIDDEN, 'message')}); - assert.isFalse(wrapper.find('Relay(IssueishTimelineView)').prop('onBranch')); - assert.isFalse(wrapper.find('Relay(PrCommitsView)').prop('onBranch')); + assert.isFalse(wrapper.find('ForwardRef(Relay(IssueishTimelineView))').prop('onBranch')); + assert.isFalse(wrapper.find('ForwardRef(Relay(PrCommitsView))').prop('onBranch')); wrapper.setProps({checkoutOp: checkoutOp.disable(checkoutStates.DISABLED, 'message')}); - assert.isFalse(wrapper.find('Relay(IssueishTimelineView)').prop('onBranch')); - assert.isFalse(wrapper.find('Relay(PrCommitsView)').prop('onBranch')); + assert.isFalse(wrapper.find('ForwardRef(Relay(IssueishTimelineView))').prop('onBranch')); + assert.isFalse(wrapper.find('ForwardRef(Relay(PrCommitsView))').prop('onBranch')); wrapper.setProps({checkoutOp: checkoutOp.disable(checkoutStates.BUSY, 'message')}); - assert.isFalse(wrapper.find('Relay(IssueishTimelineView)').prop('onBranch')); - assert.isFalse(wrapper.find('Relay(PrCommitsView)').prop('onBranch')); + assert.isFalse(wrapper.find('ForwardRef(Relay(IssueishTimelineView))').prop('onBranch')); + assert.isFalse(wrapper.find('ForwardRef(Relay(PrCommitsView))').prop('onBranch')); }); it('renders pull request information for cross repository PR', function() { diff --git a/test/views/pr-statuses-view.test.js b/test/views/pr-statuses-view.test.js index 65dc150fad..5b6077291f 100644 --- a/test/views/pr-statuses-view.test.js +++ b/test/views/pr-statuses-view.test.js @@ -109,7 +109,7 @@ describe('PrStatusesView', function() { it('renders a context view for each status context', function() { const wrapper = shallow(buildApp({summaryState: 'FAILURE', states: ['SUCCESS', 'FAILURE', 'ERROR']})); - const contextViews = wrapper.find('Relay(BarePrStatusContextView)'); + const contextViews = wrapper.find('ForwardRef(Relay(BarePrStatusContextView))'); assert.deepEqual(contextViews.map(v => v.prop('context').state), ['SUCCESS', 'FAILURE', 'ERROR']); }); diff --git a/test/views/timeline-items/commit-comment-thread-view.test.js b/test/views/timeline-items/commit-comment-thread-view.test.js index b80c1942a4..7c1248288e 100644 --- a/test/views/timeline-items/commit-comment-thread-view.test.js +++ b/test/views/timeline-items/commit-comment-thread-view.test.js @@ -24,7 +24,7 @@ describe('CommitCommentThreadView', function() { ], })); - const commentViews = wrapper.find('Relay(BareCommitCommentView)'); + const commentViews = wrapper.find('ForwardRef(Relay(BareCommitCommentView))'); assert.deepEqual(commentViews.map(c => c.prop('item').author.login), ['user0', 'user1', 'user2']); diff --git a/test/views/timeline-items/cross-referenced-events-view.test.js b/test/views/timeline-items/cross-referenced-events-view.test.js index 4834306516..ed678e91e0 100644 --- a/test/views/timeline-items/cross-referenced-events-view.test.js +++ b/test/views/timeline-items/cross-referenced-events-view.test.js @@ -15,7 +15,7 @@ describe('CrossReferencedEventsView', function() { it('renders a child component for each grouped child event', function() { const wrapper = shallow(buildApp({nodeOpts: [{}, {}, {}]})); - assert.lengthOf(wrapper.find('Relay(BareCrossReferencedEventView)'), 3); + assert.lengthOf(wrapper.find('ForwardRef(Relay(BareCrossReferencedEventView))'), 3); }); it('generates a summary based on a single pull request cross-reference', function() { From 313363ca77f93d3bb408d6f2a9b7f344cbb49189 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 8 Jan 2019 12:33:57 -0800 Subject: [PATCH 1731/4053] test for replying to an outdated comment --- test/controllers/pr-reviews-controller.test.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index 682550dc2b..eee6efb1e6 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -220,5 +220,22 @@ describe('PullRequestReviewsController', function() { assert.strictEqual(threadedComments[1].body, 'reply to OG comment'); }); + it('comments with a replyTo id that does not point to an existing comment are threaded separately', function() { + const outdatedCommentId = 1; + const replyToOutdatedCommentId = 2; + const review = reviewBuilder() + .id(2) + .submittedAt('2018-12-28T20:40:55Z') + .addComment(c => c.id(replyToOutdatedCommentId).path('file0.txt').replyTo(outdatedCommentId).body('reply to outdated comment')) + .build(); + + const wrapper = shallow(buildApp({reviewSpecs: [review]})); + wrapper.instance().collectComments({reviewId: review.id, submittedAt: review.submittedAt, comments: review.comments, fetchingMoreComments: false}); + + const comments = wrapper.instance().state[replyToOutdatedCommentId]; + assert.lengthOf(comments, 1); + assert.strictEqual(comments[0].body, 'reply to outdated comment'); + }); + }); }); From f07d2511f48569b1ce25b4462114668025670e41 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 8 Jan 2019 12:35:56 -0800 Subject: [PATCH 1732/4053] add a const for originalCommentId hopefully this makes the tests easier to understand? IDK. YOLO. --- test/controllers/pr-reviews-controller.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index eee6efb1e6..e3eceb8df3 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -168,16 +168,17 @@ describe('PullRequestReviewsController', function() { describe('grouping and ordering comments', function() { it('groups the comments into threads based on replyId', function() { + const originalCommentId = 1; const review1 = reviewBuilder() .id(0) .submittedAt('2018-12-27T20:40:55Z') - .addComment(c => c.id(1).path('file0.txt').body('OG comment')) + .addComment(c => c.id(originalCommentId).path('file0.txt').body('OG comment')) .build(); const review2 = reviewBuilder() .id(1) .submittedAt('2018-12-28T20:40:55Z') - .addComment(c => c.id(2).path('file0.txt').replyTo(1).body('reply to OG comment')) + .addComment(c => c.id(2).path('file0.txt').replyTo(originalCommentId).body('reply to OG comment')) .build(); const reviewSpecs = [review1, review2]; From 86e8f2badc5b4c971159a0fd8b441094e9d64070 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 8 Jan 2019 12:56:31 -0800 Subject: [PATCH 1733/4053] :art: extra pedantic comment update --- lib/controllers/pr-reviews-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 140586d79f..428bb38b7b 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -74,8 +74,8 @@ export default class PullRequestReviewsController extends React.Component { * Threads can have comments belonging to multiple reviews. * We need a nested pagination container to fetch comment pages. * Upon fetching new comments, the `collectComments` method is called with all comments fetched for that review. - * Ultimately we want to organize comments based on the root comment they are replies to. - * So `renderCommentFetchingContainers` simply fetches data and doesn't render any DOM elements. + * Ultimately we want to group comments based on the root comment they are replies to. + * `renderCommentFetchingContainers` only fetches data and doesn't render any user visible DOM elements. * `PullRequestReviewCommentsView` renders the aggregated comment thread data. * */ return ( From db38949196b79531f19ad0fd2209575159b24f5c Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 8 Jan 2019 13:01:44 -0800 Subject: [PATCH 1734/4053] don't test sorting comments by date we don't actually sort the comments by date. They come in in order and then we reverse them for some reason. --- .../controllers/pr-reviews-controller.test.js | 35 ++++--------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index e3eceb8df3..d99ded32b1 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -167,8 +167,9 @@ describe('PullRequestReviewsController', function() { }); describe('grouping and ordering comments', function() { - it('groups the comments into threads based on replyId', function() { + it.only('groups the comments into threads based on replyId', function() { const originalCommentId = 1; + const singleCommentId = 5; const review1 = reviewBuilder() .id(0) .submittedAt('2018-12-27T20:40:55Z') @@ -179,6 +180,7 @@ describe('PullRequestReviewsController', function() { .id(1) .submittedAt('2018-12-28T20:40:55Z') .addComment(c => c.id(2).path('file0.txt').replyTo(originalCommentId).body('reply to OG comment')) + .addComment(c => c.id(singleCommentId).path('file0.txt').body('I am single and free')) .build(); const reviewSpecs = [review1, review2]; @@ -189,36 +191,13 @@ describe('PullRequestReviewsController', function() { wrapper.instance().reviewsById.set(review1.id, {submittedAt: review1.submittedAt, comments: review1.comments, fetchingMoreComments: false}); wrapper.instance().collectComments({reviewId: review2.id, submittedAt: review2.submittedAt, comments: review2.comments, fetchingMoreComments: false}); - const threadedComments = wrapper.instance().state[1]; + const threadedComments = wrapper.instance().state[originalCommentId]; assert.lengthOf(threadedComments, 2); assert.strictEqual(threadedComments[0].body, 'OG comment'); assert.strictEqual(threadedComments[1].body, 'reply to OG comment'); - }); - it('sorts replies based on date', function() { - const review1 = reviewBuilder() - .id(0) - .submittedAt('2018-12-27T20:40:55Z') - .addComment(c => c.id(1).path('file0.txt').body('OG comment')) - .build(); - const review2 = reviewBuilder() - .id(1) - .submittedAt('2018-12-28T20:40:55Z') - .addComment(c => c.id(2).path('file0.txt').replyTo(1).body('reply to OG comment')) - .build(); - - const reviewSpecs = [review1, review2]; - - const wrapper = shallow(buildApp({reviewSpecs})); - - // adding this manually to reviewsById because the last time you call collectComments it groups them, and we don't want to do that just yet. - wrapper.instance().reviewsById.set(review1.id, {submittedAt: review1.submittedAt, comments: review1.comments, fetchingMoreComments: false}); - - wrapper.instance().collectComments({reviewId: review2.id, submittedAt: review2.submittedAt, comments: review2.comments, fetchingMoreComments: false}); - const threadedComments = wrapper.instance().state[1]; - assert.lengthOf(threadedComments, 2); - assert.strictEqual(threadedComments[0].body, 'OG comment'); - assert.strictEqual(threadedComments[1].body, 'reply to OG comment'); + const singleComment = wrapper.instance().state[singleCommentId]; + assert.strictEqual(singleComment[0].body, 'I am single and free'); }); it('comments with a replyTo id that does not point to an existing comment are threaded separately', function() { @@ -227,7 +206,7 @@ describe('PullRequestReviewsController', function() { const review = reviewBuilder() .id(2) .submittedAt('2018-12-28T20:40:55Z') - .addComment(c => c.id(replyToOutdatedCommentId).path('file0.txt').replyTo(outdatedCommentId).body('reply to outdated comment')) + .addComment(c => c.id(replyToOutdatedCommentId).path('file0.txt').replyTo(outdatedCommentId).body('reply to outdated comment')) .build(); const wrapper = shallow(buildApp({reviewSpecs: [review]})); From f1557b71a444626829f526e44d848381c686199d Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 8 Jan 2019 13:15:51 -0800 Subject: [PATCH 1735/4053] make review builder actually return `submittedAt` --- test/builder/pr.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/builder/pr.js b/test/builder/pr.js index e7b78ba2db..3616872b77 100644 --- a/test/builder/pr.js +++ b/test/builder/pr.js @@ -108,6 +108,7 @@ class ReviewBuilder { }); return { id: this._id, + submittedAt: this._submittedAt, comments: {edges: comments}, }; } From 26fa6126b20298a99af74b70cdb1f9a72ce7b1d9 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 8 Jan 2019 13:22:05 -0800 Subject: [PATCH 1736/4053] test that comments show up grouped by review submittedAt date --- .../controllers/pr-reviews-controller.test.js | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index d99ded32b1..8392258ad7 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -167,7 +167,7 @@ describe('PullRequestReviewsController', function() { }); describe('grouping and ordering comments', function() { - it.only('groups the comments into threads based on replyId', function() { + it('groups the comments into threads based on replyId', function() { const originalCommentId = 1; const singleCommentId = 5; const review1 = reviewBuilder() @@ -183,9 +183,7 @@ describe('PullRequestReviewsController', function() { .addComment(c => c.id(singleCommentId).path('file0.txt').body('I am single and free')) .build(); - const reviewSpecs = [review1, review2]; - - const wrapper = shallow(buildApp({reviewSpecs})); + const wrapper = shallow(buildApp({reviewSpecs: [review1, review2]})); // adding this manually to reviewsById because the last time you call collectComments it groups them, and we don't want to do that just yet. wrapper.instance().reviewsById.set(review1.id, {submittedAt: review1.submittedAt, comments: review1.comments, fetchingMoreComments: false}); @@ -200,6 +198,42 @@ describe('PullRequestReviewsController', function() { assert.strictEqual(singleComment[0].body, 'I am single and free'); }); + it('comments are ordered based on the order in which their reviews were submitted', function() { + const originalCommentId = 1; + const review1 = reviewBuilder() + .id(0) + .submittedAt('2018-12-20T20:40:55Z') + .addComment(c => c.id(originalCommentId).path('file0.txt').body('OG comment')) + .build(); + + const review2 = reviewBuilder() + .id(1) + .submittedAt('2018-12-22T20:40:55Z') + .addComment(c => c.id(2).path('file0.txt').replyTo(originalCommentId).body('first reply to OG comment')) + .build(); + + const review3 = reviewBuilder() + .id(2) + .submittedAt('2018-12-25T20:40:55Z') + .addComment(c => c.id(3).path('file0.txt').replyTo(originalCommentId).body('second reply to OG comment')) + .build(); + + const wrapper = shallow(buildApp({reviewSpecs: [review1, review2, review3]})); + + // adding this manually to reviewsById because the last time you call collectComments it groups them, and we don't want to do that just yet. + wrapper.instance().reviewsById.set(review2.id, {submittedAt: review2.submittedAt, comments: review2.comments, fetchingMoreComments: false}); + wrapper.instance().reviewsById.set(review1.id, {submittedAt: review1.submittedAt, comments: review1.comments, fetchingMoreComments: false}); + + wrapper.instance().collectComments({reviewId: review3.id, submittedAt: review3.submittedAt, comments: review3.comments, fetchingMoreComments: false}); + const threadedComments = wrapper.instance().state[originalCommentId]; + assert.lengthOf(threadedComments, 3); + + assert.strictEqual(threadedComments[0].body, 'OG comment'); + assert.strictEqual(threadedComments[1].body, 'first reply to OG comment'); + assert.strictEqual(threadedComments[2].body, 'second reply to OG comment'); + }); + + it('comments with a replyTo id that does not point to an existing comment are threaded separately', function() { const outdatedCommentId = 1; const replyToOutdatedCommentId = 2; From bc87b98247c4d70867143e3a4173eec267ceaf53 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Tue, 8 Jan 2019 18:03:26 -0800 Subject: [PATCH 1737/4053] update react component atlas --- docs/react-component-atlas.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md index 383e3b78eb..596b3acbb4 100644 --- a/docs/react-component-atlas.md +++ b/docs/react-component-atlas.md @@ -124,6 +124,13 @@ This is a high-level overview of the structure of the React component tree that > > > > > [``](/lib/containers/pr-changed-files-container.js) > > > +> > > Fetch all reviews and comments for a pull request, group comments, and render them. +> > > [``](/lib/containers/pr-reviews-container.js) +> > > [``](/lib/containers/pr-review-comments-container.js) +> > > [``](lib/controllers/pr-reviews-controller.js) +> > > [``](lib/views/pr-review-comments-view.js) +> > > [``](lib/views/pr-review-comments-view.js) +> > > > > > Show all the changes, separated by files, introduced in a pull request. > > > > > > > [``](/lib/controllers/multi-file-patch-controller.js) From fbb72f488244d1fe9a4c4d8e47c15f5c9207be0a Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 9 Jan 2019 14:46:07 +0100 Subject: [PATCH 1738/4053] relay changed where the displayname is stored --- lib/views/issueish-timeline-view.js | 2 +- test/views/issueish-timeline-view.test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/views/issueish-timeline-view.js b/lib/views/issueish-timeline-view.js index 923f919abc..dcedf15d46 100644 --- a/lib/views/issueish-timeline-view.js +++ b/lib/views/issueish-timeline-view.js @@ -13,7 +13,7 @@ import CommitCommentThreadView from './timeline-items/commit-comment-thread-view export function collectionRenderer(Component, styleAsTimelineItem = true) { return class GroupedComponent extends React.Component { - static displayName = `Grouped(${Component.displayName})` + static displayName = `Grouped(${Component.render.displayName})` static propTypes = { nodes: PropTypes.array.isRequired, diff --git a/test/views/issueish-timeline-view.test.js b/test/views/issueish-timeline-view.test.js index 4135f9961c..90dfbe8e72 100644 --- a/test/views/issueish-timeline-view.test.js +++ b/test/views/issueish-timeline-view.test.js @@ -74,16 +74,16 @@ describe('IssueishTimelineView', function() { const commitGroup0 = wrapper.find('ForwardRef(Relay(BareCommitsView))').filterWhere(c => c.prop('nodes').length === 2); assert.deepEqual(commitGroup0.prop('nodes').map(n => n.id), [0, 1]); - const commentGroup0 = wrapper.find('Grouped(ForwardRef(Relay(BareIssueCommentView)))').filterWhere(c => c.prop('nodes').length === 1); + const commentGroup0 = wrapper.find('Grouped(Relay(BareIssueCommentView))').filterWhere(c => c.prop('nodes').length === 1); assert.deepEqual(commentGroup0.prop('nodes').map(n => n.id), [2]); - const mergedGroup = wrapper.find('Grouped(ForwardRef(Relay(BareMergedEventView)))').filterWhere(c => c.prop('nodes').length === 1); + const mergedGroup = wrapper.find('Grouped(Relay(BareMergedEventView))').filterWhere(c => c.prop('nodes').length === 1); assert.deepEqual(mergedGroup.prop('nodes').map(n => n.id), [3]); const commitGroup1 = wrapper.find('ForwardRef(Relay(BareCommitsView))').filterWhere(c => c.prop('nodes').length === 4); assert.deepEqual(commitGroup1.prop('nodes').map(n => n.id), [4, 5, 6, 7]); - const commentGroup1 = wrapper.find('Grouped(ForwardRef(Relay(BareIssueCommentView)))').filterWhere(c => c.prop('nodes').length === 2); + const commentGroup1 = wrapper.find('Grouped(Relay(BareIssueCommentView))').filterWhere(c => c.prop('nodes').length === 2); assert.deepEqual(commentGroup1.prop('nodes').map(n => n.id), [8, 9]); }); From 8699262ef71b6cc218cee3e913caba8908b17970 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 9 Jan 2019 16:34:04 +0100 Subject: [PATCH 1739/4053] ok this should do it --- lib/views/issueish-timeline-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/issueish-timeline-view.js b/lib/views/issueish-timeline-view.js index dcedf15d46..286d3a5b7e 100644 --- a/lib/views/issueish-timeline-view.js +++ b/lib/views/issueish-timeline-view.js @@ -13,7 +13,7 @@ import CommitCommentThreadView from './timeline-items/commit-comment-thread-view export function collectionRenderer(Component, styleAsTimelineItem = true) { return class GroupedComponent extends React.Component { - static displayName = `Grouped(${Component.render.displayName})` + static displayName = `Grouped(${Component.render ? Component.render.displayName : Component.displayName})` static propTypes = { nodes: PropTypes.array.isRequired, From 5379d104f78040ee9b67b3afdfb7e2d611b62949 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 3 Oct 2018 14:18:38 +0000 Subject: [PATCH 1740/4053] fix(package): update keytar to version 4.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61d7625d5a..02c41a62aa 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "event-kit": "2.5.3", "fs-extra": "4.0.3", "graphql": "0.13.2", - "keytar": "4.2.1", + "keytar": "4.3.0", "lodash.memoize": "4.1.2", "moment": "2.22.2", "node-emoji": "^1.8.1", From d06eb9f21ea41295523f9127223e9f5a2a9ec441 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 3 Oct 2018 14:18:42 +0000 Subject: [PATCH 1741/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 130 +++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 70 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a11d91370..c5a71bba9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -358,9 +358,9 @@ "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" }, "are-we-there-yet": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -1773,23 +1773,23 @@ } }, "buffer-alloc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.1.0.tgz", - "integrity": "sha1-BVFNM78WVtNUDGhPZbEgLpDsowM=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "requires": { - "buffer-alloc-unsafe": "^0.1.0", - "buffer-fill": "^0.1.0" + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" } }, "buffer-alloc-unsafe": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-0.1.1.tgz", - "integrity": "sha1-/+H2dVHdBVc33iUzN7/oU9+rGmo=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" }, "buffer-fill": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-0.1.1.tgz", - "integrity": "sha1-dtglxNblDga3ox61IMBNCMwjUHE=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" }, "buffer-from": { "version": "1.1.1", @@ -2285,9 +2285,9 @@ "dev": true }, "deep-extend": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", - "integrity": "sha1-uJSp3ZDTAj+/HFWjlPuFjrIGbx8=" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "deep-is": { "version": "0.1.3", @@ -3267,9 +3267,9 @@ } }, "expand-template": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.0.tgz", - "integrity": "sha1-4J77qXe/mPnuDtJavQxpLgKuw/w=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz", + "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==" }, "extend": { "version": "3.0.1", @@ -4604,12 +4604,12 @@ "dev": true }, "keytar": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-4.2.1.tgz", - "integrity": "sha1-igamV3/fY3PgqmsRInfmPex3/RI=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-4.3.0.tgz", + "integrity": "sha512-pd++/v+fS0LQKmzWlW6R1lziTXFqhfGeS6sYLfuTIqEy2pDzAbjutbSW8f9tnJdEEMn/9XhAQlT34VAtl9h4MQ==", "requires": { "nan": "2.8.0", - "prebuild-install": "^2.4.1" + "prebuild-install": "^5.0.0" } }, "kind-of": { @@ -5042,9 +5042,9 @@ "dev": true }, "mimic-response": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", - "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, "min-document": { "version": "2.19.0", @@ -5281,6 +5281,11 @@ } } }, + "napi-build-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.1.tgz", + "integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==" + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5334,9 +5339,9 @@ } }, "node-abi": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.0.tgz", - "integrity": "sha1-PCdRXLhC9bvBMqMSVPnx4cVce4M=", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.5.tgz", + "integrity": "sha512-aa/UC6Nr3+tqhHGRsAuw/edz7/q9nnetBrKWxj6rpTtm+0X9T1qU7lIEHMS3yN9JwAbRiKUbRRFy1PLz/y3aaA==", "requires": { "semver": "^5.4.1" } @@ -6925,21 +6930,22 @@ "dev": true }, "prebuild-install": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.3.tgz", - "integrity": "sha1-n2XyQngtNwKWNTcQ6byENJDBn2k=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.2.0.tgz", + "integrity": "sha512-cpuyMS8y30Df0bnN+I8pdmpwtZbm8fj9cQADOhSH/qnS1exb80elZ707FTMohFBJax4NyWjJVSg0chRQXzHSvg==", "requires": { "detect-libc": "^1.0.3", "expand-template": "^1.0.2", "github-from-package": "0.0.0", "minimist": "^1.2.0", "mkdirp": "^0.5.1", + "napi-build-utils": "^1.0.1", "node-abi": "^2.2.0", "noop-logger": "^0.1.1", "npmlog": "^4.0.1", "os-homedir": "^1.0.1", "pump": "^2.0.1", - "rc": "^1.1.6", + "rc": "^1.2.7", "simple-get": "^2.7.0", "tar-fs": "^1.13.0", "tunnel-agent": "^0.6.0", @@ -7073,11 +7079,11 @@ } }, "rc": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.7.tgz", - "integrity": "sha1-ihDKMNWI0ARkNgNyuJDQbazQIpc=", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "requires": { - "deep-extend": "^0.5.1", + "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" @@ -8113,7 +8119,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -8122,20 +8127,17 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -8275,9 +8277,9 @@ } }, "tar-fs": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.2.tgz", - "integrity": "sha1-F+Ujl0fjmffnc0T19TNl8Er1NXc=", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", + "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", "requires": { "chownr": "^1.0.1", "mkdirp": "^0.5.1", @@ -8288,7 +8290,7 @@ "pump": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha1-Xf6DEcM7v2/BgmH580cCxHwIqVQ=", + "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -8297,16 +8299,16 @@ } }, "tar-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.0.tgz", - "integrity": "sha1-pQ76p7F3YLgsJ7PK5KMBqCVKVxU=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "requires": { "bl": "^1.0.0", - "buffer-alloc": "^1.1.0", + "buffer-alloc": "^1.2.0", "end-of-stream": "^1.0.0", "fs-constants": "^1.0.0", - "readable-stream": "^2.0.0", - "to-buffer": "^1.1.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", "xtend": "^4.0.0" } }, @@ -8922,23 +8924,11 @@ "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" }, "wide-align": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "requires": { - "string-width": "^1.0.2" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } + "string-width": "^1.0.2 || 2" } }, "wordwrap": { From bf675047374570e22f3c897381743d874ad54f26 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 9 Jan 2019 11:21:10 -0500 Subject: [PATCH 1742/4053] :arrow_up: moment --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a11d91370..3d7f6b35cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5225,9 +5225,9 @@ "dev": true }, "moment": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz", + "integrity": "sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA==" }, "moo": { "version": "0.4.3", diff --git a/package.json b/package.json index 61d7625d5a..d07d37caeb 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "graphql": "0.13.2", "keytar": "4.2.1", "lodash.memoize": "4.1.2", - "moment": "2.22.2", + "moment": "2.23.0", "node-emoji": "^1.8.1", "prop-types": "15.6.2", "react": "16.7.0", From 95c7d6715aeed2d787fed6b8abec7dec1aea6625 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 9 Jan 2019 09:30:21 -0800 Subject: [PATCH 1743/4053] fussing with the test formatting --- test/controllers/pr-reviews-controller.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index 8392258ad7..1fef175c8d 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -220,7 +220,7 @@ describe('PullRequestReviewsController', function() { const wrapper = shallow(buildApp({reviewSpecs: [review1, review2, review3]})); - // adding this manually to reviewsById because the last time you call collectComments it groups them, and we don't want to do that just yet. + // adding these manually to reviewsById because the last time you call collectComments it groups them, and we don't want to do that just yet. wrapper.instance().reviewsById.set(review2.id, {submittedAt: review2.submittedAt, comments: review2.comments, fetchingMoreComments: false}); wrapper.instance().reviewsById.set(review1.id, {submittedAt: review1.submittedAt, comments: review1.comments, fetchingMoreComments: false}); @@ -233,7 +233,6 @@ describe('PullRequestReviewsController', function() { assert.strictEqual(threadedComments[2].body, 'second reply to OG comment'); }); - it('comments with a replyTo id that does not point to an existing comment are threaded separately', function() { const outdatedCommentId = 1; const replyToOutdatedCommentId = 2; @@ -250,6 +249,5 @@ describe('PullRequestReviewsController', function() { assert.lengthOf(comments, 1); assert.strictEqual(comments[0].body, 'reply to outdated comment'); }); - }); }); From 890b74f153d6d37d7f67170fd9d5c2a3a3ccbe18 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 9 Jan 2019 11:18:00 -0500 Subject: [PATCH 1744/4053] :arrow_up: dugite --- package-lock.json | 224 ++++++++++++++++++++++++++-------------------- package.json | 2 +- 2 files changed, 130 insertions(+), 96 deletions(-) diff --git a/package-lock.json b/package-lock.json index 887c84455a..654cd01565 100644 --- a/package-lock.json +++ b/package-lock.json @@ -208,7 +208,7 @@ "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha1-UkryQNGjYFJ7cwR17PoTRKpUDd4=", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", "dev": true, "requires": { "call-me-maybe": "^1.0.1", @@ -253,7 +253,7 @@ "@smashwilson/atom-mocha-test-runner": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@smashwilson/atom-mocha-test-runner/-/atom-mocha-test-runner-1.4.0.tgz", - "integrity": "sha1-AjOAreJPt5xrC7TlXdTuc7LFdfo=", + "integrity": "sha512-Zp50XTy2QZEk53PUxXQ1kLTAkSwEuM2X7JXtMGLRWuU68piFghkXGaopTrjXK3CwgzmmFi26m65sTCrXg3zqbg==", "dev": true, "requires": { "diff": "3.5.0", @@ -329,6 +329,7 @@ "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, "requires": { "co": "^4.6.0", "fast-deep-equal": "^1.0.0", @@ -339,7 +340,7 @@ "ansi-escapes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha1-9zIHu4EgfXX9bIPxJa8m7qN4yjA=", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, "ansi-regex": { @@ -369,7 +370,7 @@ "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "requires": { "sprintf-js": "~1.0.2" } @@ -464,7 +465,7 @@ "array.prototype.flat": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz", - "integrity": "sha1-gS248CytJNP6tl3WfqvjuJA0lKQ=", + "integrity": "sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==", "dev": true, "requires": { "define-properties": "^1.1.2", @@ -496,7 +497,7 @@ "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha1-5gtrDo8wG9l+U3UhW9pAbIURjAs=", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, "assign-symbols": { @@ -547,7 +548,7 @@ "atom-babel6-transpiler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/atom-babel6-transpiler/-/atom-babel6-transpiler-1.2.0.tgz", - "integrity": "sha1-OcgHq8H9WqZDqvCut8DqE3j+Y1Y=", + "integrity": "sha512-lZucrjVyRtPAPPJxvICCEBsAC1qn48wUHaIlieriWCXTXLqtLC2PvkQU7vNvU2w1eZ7tw9m0lojZ8PbpVyWTvg==", "requires": { "babel-core": "6.x" } @@ -1459,7 +1460,7 @@ "babel-preset-fbjs": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.2.0.tgz", - "integrity": "sha1-wluHmpFP7v2WQFKxvOTJDukVAjo=", + "integrity": "sha512-jj0KFJDioYZMtPtZf77dQuU+Ad/1BtN0UnAYlHDa8J8f4tGXr3YrPoJImD5MdueaOPeN/jUdrCgu330EfXr0XQ==", "dev": true, "requires": { "babel-plugin-check-es2015-constants": "^6.8.0", @@ -1635,7 +1636,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -1644,7 +1645,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -1653,7 +1654,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -1987,7 +1988,7 @@ "circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY=", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, "class-utils": { @@ -2016,7 +2017,7 @@ "classnames": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", - "integrity": "sha1-Q5Nb/90pHzJtrQogUwmzjQD2UM4=" + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" }, "cli-cursor": { "version": "2.1.0", @@ -2066,7 +2067,8 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true }, "code-point-at": { "version": "1.1.0", @@ -2115,7 +2117,7 @@ "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha1-30boZ9D8Kuxmo0ZitAapzK//Ww8=", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", "dev": true }, "compare-sets": { @@ -2211,7 +2213,7 @@ "cross-env": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", - "integrity": "sha1-bs1MAV1Xc+YUA57lKQdmabnRJvI=", + "integrity": "sha512-jtdNFfFW1hB7sMhr/H6rW1Z45LFqyI431m3qU6bFXcQ3Eh7LtBuG3h74o7ohHZ3crrRkkqHlo4jYHFPcjroANg==", "dev": true, "requires": { "cross-spawn": "^6.0.5", @@ -2340,7 +2342,7 @@ "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { "type-detect": "^4.0.0" @@ -2396,7 +2398,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -2405,7 +2407,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -2414,7 +2416,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -2450,7 +2452,7 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "dir-glob": { @@ -2537,7 +2539,7 @@ "domhandler": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha1-iAUJfpM9ZehVRvcm1g9euItE+AM=", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", "dev": true, "requires": { "domelementtype": "1" @@ -2554,18 +2556,29 @@ } }, "dugite": { - "version": "1.79.0", - "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.79.0.tgz", - "integrity": "sha512-1iohG+Yj+7wwVNUv+HCWaK5ZeAbqNyxHZf96B65KojBVcvMT29i8Tnh/Ta/KHI7LcI0dQqSqsKJdZozpWjXWKw==", + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.81.0.tgz", + "integrity": "sha512-aH1cVzbEXOHqpiub9PWJUN+R2p7H+tvN+VqyAYHR9Tj/axLDccWJk5aKDN1/US82DkaIYWUZz8x0lAbjfqrq4Q==", "requires": { "checksum": "^0.1.1", "mkdirp": "^0.5.1", - "progress": "^2.0.0", + "progress": "^2.0.3", "request": "^2.88.0", "rimraf": "^2.5.4", - "tar": "^4.4.6" + "tar": "^4.4.7" }, "dependencies": { + "ajv": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", @@ -2576,15 +2589,25 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "^5.3.0", + "ajv": "^6.5.5", "har-schema": "^2.0.0" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "mime-db": { "version": "1.37.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", @@ -2603,6 +2626,11 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -2668,7 +2696,7 @@ "electron-devtools-installer": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/electron-devtools-installer/-/electron-devtools-installer-2.2.4.tgz", - "integrity": "sha1-JhpQM343Eh0zi5ZvB5IutJOah2M=", + "integrity": "sha512-b5kcM3hmUqn64+RUcHjjr8ZMpHS2WJ5YO0pnG9+P/RTdx46of/JrEjuciHWux6pE+On6ynWhHJF53j/EDJN0PA==", "dev": true, "requires": { "7zip": "0.0.6", @@ -2756,7 +2784,7 @@ "emoji-regex": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", - "integrity": "sha1-m66pKbFVVlwR6kHGYm6qZc75ksI=", + "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==", "dev": true }, "encoding": { @@ -2874,7 +2902,7 @@ "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "requires": { "prr": "~1.0.1" @@ -3195,7 +3223,7 @@ "eslint-scope": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha1-UL8wcekzi83EMzF5Sgy1M/ATYXI=", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -3211,7 +3239,7 @@ "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha1-PzGA+y4pEBdxastMnW1bXDSmqB0=", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", "dev": true }, "espree": { @@ -3233,7 +3261,7 @@ "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -3242,7 +3270,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -3480,7 +3508,8 @@ "fast-deep-equal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true }, "fast-future": { "version": "1.0.2", @@ -3665,7 +3694,7 @@ "fs-extra": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha1-DYUhIuW8W+tFP7Ao6cDJvzY0DJQ=", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -3675,7 +3704,7 @@ "fs-minipass": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha1-BsJ3IYRU7CiN93raVKA7hwKqy50=", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "requires": { "minipass": "^2.2.1" } @@ -3688,13 +3717,13 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "function.prototype.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.0.tgz", - "integrity": "sha1-i9djzAr4YKhZzF1JOE10uTLNIyc=", + "integrity": "sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==", "dev": true, "requires": { "define-properties": "^1.1.2", @@ -3889,7 +3918,7 @@ "grim": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/grim/-/grim-2.0.2.tgz", - "integrity": "sha1-52CinKe4NDsMH/r2ziDyGkbuiu0=", + "integrity": "sha512-Qj7hTJRfd87E/gUgfvM0YIH/g2UA2SV6niv6BYXk1o6w4mhgv+QyYM1EjOJQljvzgEj4SqSsRWldXIeKHz3e3Q==", "dev": true, "requires": { "event-kit": "^2.0.0" @@ -3898,7 +3927,7 @@ "growl": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha1-8nNdwig2dPpnR4sQGBBZNVw2nl4=", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, "har-schema": { @@ -3991,7 +4020,7 @@ "hock": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/hock/-/hock-1.3.3.tgz", - "integrity": "sha1-aWHj3wUpsu08vBPkuNgfvYi5xg0=", + "integrity": "sha512-bEX7KH/KSv2Q5zA+o1EdyeH52+gD2cfpYyAsHMEwjb9txXWttityKVf7cG0y3UVA4D8bxKDzH8LVXCQIr9rClg==", "dev": true, "requires": { "deep-equal": "0.2.1", @@ -4337,7 +4366,7 @@ "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", @@ -4348,7 +4377,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true } } @@ -4536,7 +4565,7 @@ "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha1-mA9vcthSEaU0fGsrwYxbhMPrR+8=", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { "encoding": "^0.1.11", "is-stream": "^1.0.1" @@ -4613,7 +4642,8 @@ "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -4689,7 +4719,7 @@ "less": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/less/-/less-3.8.1.tgz", - "integrity": "sha1-8xdYWY71oZMN1MrvqeQ0BkHnHh0=", + "integrity": "sha512-8HFGuWmL3FhQR0aH89escFNBQH/nEiYPP2ltDFdQw2chE28Yx2E3lhAIq9Y2saYwLSwa699s4dBVEfCY8Drf7Q==", "dev": true, "requires": { "clone": "^2.1.2", @@ -4706,7 +4736,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true } @@ -5077,7 +5107,7 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, "optional": true }, @@ -5144,9 +5174,9 @@ } }, "minizlib": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.1.tgz", - "integrity": "sha512-TrfjCjk4jLhcJyGMYymBH6oTXcWjYbUAXTHDbtnWHjZC25h0cdajHuPE1zxb4DVmu8crfh+HwH/WMuyLG0nHBg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "requires": { "minipass": "^2.2.1" } @@ -5190,7 +5220,7 @@ "mocha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha1-bYrlCPWRZ/lA8rWzxKYSrlDJCuY=", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, "requires": { "browser-stdout": "1.3.1", @@ -5209,7 +5239,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" @@ -5218,7 +5248,7 @@ "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -5291,7 +5321,7 @@ "moo": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", - "integrity": "sha1-P4R6JvMc9iWpVqh/KxD7wBO/0Q4=", + "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==", "dev": true }, "ms": { @@ -5354,7 +5384,7 @@ "nearley": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.15.1.tgz", - "integrity": "sha1-ll5Obsnta4D8gUU+Fh77zrs20kc=", + "integrity": "sha512-8IUY/rUrKz2mIynUGh8k+tul1awMKEjeHHC5G3FHvvyAW6oq4mQfNp2c0BMea+sYZJvYcrrM6GmZVIle/GRXGw==", "dev": true, "requires": { "moo": "^0.4.3", @@ -5408,7 +5438,7 @@ "node-emoji": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.8.1.tgz", - "integrity": "sha1-buxr+wdCHiFIx1xrunJCH4UwqCY=", + "integrity": "sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg==", "requires": { "lodash.toarray": "^4.4.0" } @@ -6685,7 +6715,7 @@ "object-inspect": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha1-xwtsv3LydKq0w0wMgvUWe/gs8Vs=", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", "dev": true }, "object-is": { @@ -6873,7 +6903,7 @@ "parse5": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", - "integrity": "sha1-BC95L/3TaFFVHPTp4Gazh0q0W1w=", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", "dev": true, "requires": { "@types/node": "*" @@ -6978,7 +7008,7 @@ "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c=", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, "posix-character-classes": { @@ -7040,12 +7070,13 @@ "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=" + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true }, "progress-stream": { "version": "1.2.0", @@ -7068,7 +7099,7 @@ "prop-types": { "version": "15.6.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha1-BdXKd7RFPphdYPx/+MhZCUpJcQI=", + "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", "requires": { "loose-envify": "^1.3.1", "object-assign": "^4.1.1" @@ -7087,9 +7118,9 @@ "dev": true }, "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha1-YPWA02AXC7cip5fMcEQR5tqFDGc=" + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" }, "pump": { "version": "2.0.1", @@ -7129,7 +7160,7 @@ "randexp": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha1-6YatXl4x2uE93W97MBmqfIf2DKM=", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", "dev": true, "requires": { "discontinuous-range": "1.0.0", @@ -7658,7 +7689,7 @@ "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "requires": { "glob": "^7.0.5" } @@ -7884,7 +7915,7 @@ "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { "base": "^0.11.1", @@ -7940,7 +7971,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -7949,7 +7980,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -7958,7 +7989,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -8023,7 +8054,7 @@ "spdx-correct": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha1-BaW01xU6GVvJLDxCW2nzsqlSTII=", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -8033,13 +8064,13 @@ "spdx-exceptions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha1-LHrmEFbHFKW5ubKyr30xHvXHj+k=", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", "dev": true }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -8273,19 +8304,24 @@ } }, "tar": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.6.tgz", - "integrity": "sha1-YxEPCcALTmCsi8/hvzyGYCNfvJs=", + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "requires": { - "chownr": "^1.0.1", + "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.3", - "minizlib": "^1.1.0", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", "yallist": "^3.0.2" }, "dependencies": { + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -8679,7 +8715,7 @@ "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw=", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true }, "typedarray": { @@ -8702,7 +8738,7 @@ "underscore-plus": { "version": "1.6.8", "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.6.8.tgz", - "integrity": "sha1-iUtRMnY+nlzp1Q8mh7aNumCC3iI=", + "integrity": "sha512-88PrCeMKeAAC1L4xjSiiZ3Fg6kZOYrLpLGVPPeqKq/662DfQe/KTSKdSR/Q/tucKNnfW2MNAUGSCkDf8HmXC5Q==", "requires": { "underscore": "~1.8.3" }, @@ -8803,8 +8839,7 @@ "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", - "dev": true, + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "requires": { "punycode": "^2.1.0" }, @@ -8812,8 +8847,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, @@ -9020,9 +9054,9 @@ "dev": true }, "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" }, "yargs": { "version": "9.0.1", diff --git a/package.json b/package.json index afe9dd6e37..3fdc822d05 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "bytes": "^3.0.0", "classnames": "2.2.6", "compare-sets": "1.0.1", - "dugite": "^1.79.0", + "dugite": "^1.81.0", "event-kit": "2.5.3", "fs-extra": "4.0.3", "graphql": "0.13.2", From e980dbf29f676f41286f6cc7dcdcff4661fc01b2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 9 Jan 2019 14:09:23 -0500 Subject: [PATCH 1745/4053] :arrow_up: globby --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 654cd01565..1efc2263cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3863,13 +3863,13 @@ "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=" }, "globby": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", - "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", + "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", "dev": true, "requires": { "array-union": "^1.0.1", - "dir-glob": "^2.0.0", + "dir-glob": "2.0.0", "fast-glob": "^2.0.2", "glob": "^7.1.2", "ignore": "^3.3.5", diff --git a/package.json b/package.json index 3fdc822d05..9dffa87e11 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "eslint": "5.12.0", "eslint-config-fbjs-opensource": "1.0.0", "eslint-plugin-jsx-a11y": "6.1.2", - "globby": "8.0.1", + "globby": "8.0.2", "hock": "1.3.3", "lodash.isequalwith": "4.4.0", "mkdirp": "0.5.1", From 3ff9662eb05a10bd6babd8889ba714996f877b72 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 9 Jan 2019 15:59:19 -0500 Subject: [PATCH 1746/4053] Document using a "PullRequestReviewsItem" for review navigation. Co-Authored-By: Vanessa Yuen Co-Authored-By: Katrina Uychaco --- .../003-pull-request-review.md | 64 +++++++++++-------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index a045631c5a..e7c7a45d44 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -26,7 +26,7 @@ Peer review is also a critical part of the path to acceptance for pull requests ![center pane](https://user-images.githubusercontent.com/378023/46985265-75c9fe00-d124-11e8-9b34-572cd1aaf701.png) * Clicking a pull request in the list opens a `PullRequestDetailItem` in the workspace center. - +* Clicking the "Reviews" label or progress bar opens a `PullRequestReviewsItem` in the right dock. ### PullRequestDetailItem @@ -38,14 +38,15 @@ At the top of each `PullRequestDetailItem` is a summary about the pull request, - Overview - Files (**new**) -- Reviews (**new**) - Commits - Build Status -Below the tabs is a "tools bar" for controls to toggle review comments or collapse files. +Below the tabs is a "tools bar" with controls to toggle review comments or collapse files. #### Footer +> TODO: Add "open" button + ![reviews panel](https://user-images.githubusercontent.com/378023/46536010-17ad4780-c8e8-11e8-8338-338bb592efc5.png) A panel at the bottom of the pane shows the progress for resolved review comments. It also has a "Review Changes" button to create a new review. This panel is persistent throughout all sub-views. It allows creating new reviews no matter where you are. Below examples with the existing sub-views: @@ -54,6 +55,7 @@ Overview | Commits | Build Status --- | --- | --- ![overview](https://user-images.githubusercontent.com/378023/46535907-ca30da80-c8e7-11e8-9401-2b8660d56c25.png) | ![commits](https://user-images.githubusercontent.com/378023/46535908-ca30da80-c8e7-11e8-87ca-01637f2554b6.png) | ![build status](https://user-images.githubusercontent.com/378023/46535909-cac97100-c8e7-11e8-8813-47fdaa3ece57.png) +When the pull request is checked out, an "open" button is shown in the review footer. Clicking "open" opens a `PullRequestReviewsItem` for this pull request's review comments as an item in the right workspace dock. ### Files (tab) @@ -70,26 +72,6 @@ Uncollapsed (default) | Collapsed * For large diffs, the files can be collapsed to get a better overview. - -### Reviews (tab) - -Clicking on the "Reviews" tab shows all reviews of a pull request. This is akin to the review summaries that appear on the "Conversation" tab on dotcom. In addition, each review also includes review comments and their diff. - -![reviews](https://user-images.githubusercontent.com/378023/46535563-c81a4c00-c8e6-11e8-9c0b-6ea575556101.png) - -Uncollapsed (default) | Collapsed ---- | --- -![reviews](https://user-images.githubusercontent.com/378023/46535563-c81a4c00-c8e6-11e8-9c0b-6ea575556101.png) | ![collapsed reviews](https://user-images.githubusercontent.com/378023/46926357-62a72780-d06b-11e8-9344-23389d1c727c.png) - -* Comments can be collapsed to get a better overview. -* Reviews get sorted by "urgency". Showing reviews that still need to get adressed at the top: - 1. "recommended" changes - 2. "commented" changes - 3. "no review" (when a reviewer only leaves review comments, but no summary) - 4. "approved" changes - 5. "previous" reviews (when a reviewer made an earlier review and it's now out-dated) -* Within each group, sorting is done by "newest first". - #### Create a new review ##### `+` Button @@ -131,7 +113,39 @@ Clicking "Finish your review" from a comment or clicking "Review Changes" in the * Review comments can be resolved by clicking on the "Resolve conversation" buttons. * If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. -#### Context and navigation +### PullRequestReviewsItem + +This item is opened in the workspace's right dock when the user: + +* Clicks the review progress bar in the GitHub tab. +* Clicks the "open" button on the review summary footer of a `PullRequestDetailItem`. +* Clicks the "<>" button on a review comment in the "Files" tab of a `PullRequestDetailItem`. + +It shows a scrollable view of all of the reviews and comments associated with a specific pull request, + +> TODO: Illustration for the PullRequestReviewsItem + +Reviews are sorted by "urgency," showing reviews that still need to be addressed at the top. Within each group, sorting is done by "newest first". + +1. "recommended" changes +2. "commented" changes +3. "no review" (when a reviewer only leaves review comments, but no summary) +4. "approved" changes +5. "previous" reviews (when a reviewer made an earlier review and it's now out-dated) + +Clicking on a review summary comment expands or collapses the associated review comments. + +Clicking on a review comment opens a `TextEditor` on the corresponding position of the file under review. The clicked review comment is highlighted as the "current" one. + +#### Within an open TextEditor + +If an open `TextEditor` corresponds to a file that has one or more review comments in an open `PullRequestReviewsItem`, gutter and line decorations are added to the lines that match those review comment positions. The "current" one is styled differently to stand out. + +> TODO: Illustrate the "review comment here" gutter and line decorations + +Clicking on the gutter icon reveals the `PullRequestReviewsItem` and highlights that review comment as the "current" one, scrolling to it and expanding its review if necessary. + +### Context and navigation Review comments are shown in 3 different places. The comments themselves have the same functionality, but allow the comment to be seen in a different context, depending on different use cases. For example "reviewing a pull request", "addressing feedback", "editing the entire file". @@ -145,7 +159,7 @@ In order to navigate between comments or switch context, each comment has the fo * Clicking on the `<>` button in a review comment shows the comment in the entire file. If possible, the scroll-position is retained. This allows to quickly get more context about the code. * If the current pull request is not checked out, the `<>` button is disabled, and a tooltip prompts the user to check out the pull request to edit the source. -* Clicking on the "sandwich" button shows the comment under the "Reviews" tab. +* Clicking on the "sandwich" button shows the comment in the corresponding `PullRequestReviewsItem`. * Clicking on the "file-+" button (not shown in above screenshot) shows the comment under the "Files" tab. * The up and down arrow buttons navigate to the next and previous unresolved review comments. * Reaction emoji may be added to each comment with the "emoji" button. Existing emoji reaction tallies are included beneath each comment. From 3bc411e730e7677d9ceacbce20b9ca9834f8c749 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 9 Jan 2019 16:00:17 -0500 Subject: [PATCH 1747/4053] We actually called it "Files Changed," not "Files" --- docs/feature-requests/003-pull-request-review.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index e7c7a45d44..fdc7654fec 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -57,9 +57,9 @@ Overview | Commits | Build Status When the pull request is checked out, an "open" button is shown in the review footer. Clicking "open" opens a `PullRequestReviewsItem` for this pull request's review comments as an item in the right workspace dock. -### Files (tab) +### Files Changed (tab) -Clicking on the "Files" tab displays the full, multi-file diff associated with the pull request. This is akin to the "Files changed" tab on dotcom. +Clicking on the "Files Changed" tab displays the full, multi-file diff associated with the pull request. This is akin to the "Files changed" tab on dotcom. ![files](https://user-images.githubusercontent.com/378023/46536560-d3bb4200-c8e9-11e8-9764-dca0b84245cf.png) @@ -119,7 +119,7 @@ This item is opened in the workspace's right dock when the user: * Clicks the review progress bar in the GitHub tab. * Clicks the "open" button on the review summary footer of a `PullRequestDetailItem`. -* Clicks the "<>" button on a review comment in the "Files" tab of a `PullRequestDetailItem`. +* Clicks the "<>" button on a review comment in the "Files Changed" tab of a `PullRequestDetailItem`. It shows a scrollable view of all of the reviews and comments associated with a specific pull request, @@ -160,7 +160,7 @@ In order to navigate between comments or switch context, each comment has the fo * Clicking on the `<>` button in a review comment shows the comment in the entire file. If possible, the scroll-position is retained. This allows to quickly get more context about the code. * If the current pull request is not checked out, the `<>` button is disabled, and a tooltip prompts the user to check out the pull request to edit the source. * Clicking on the "sandwich" button shows the comment in the corresponding `PullRequestReviewsItem`. -* Clicking on the "file-+" button (not shown in above screenshot) shows the comment under the "Files" tab. +* Clicking on the "file-+" button (not shown in above screenshot) shows the comment under the "Files Changed" tab. * The up and down arrow buttons navigate to the next and previous unresolved review comments. * Reaction emoji may be added to each comment with the "emoji" button. Existing emoji reaction tallies are included beneath each comment. From 56f0eae8e422f6a32086cb04d4b0087af5ea408e Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 9 Jan 2019 13:06:30 -0800 Subject: [PATCH 1748/4053] explicitly test date comparator --- .../controllers/pr-reviews-controller.test.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index 1fef175c8d..4edf917316 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -250,4 +250,23 @@ describe('PullRequestReviewsController', function() { assert.strictEqual(comments[0].body, 'reply to outdated comment'); }); }); + describe('compareReviewsByDate', function() { + let wrapper; + const reviewA = reviewBuilder().submittedAt('2018-12-28T20:40:55Z').build(); + const reviewB = reviewBuilder().submittedAt('2018-12-27T20:40:55Z').build(); + + beforeEach(function() { + wrapper = shallow(buildApp()); + }); + + it('returns 1 if reviewA is older', function() { + assert.strictEqual(wrapper.instance().compareReviewsByDate(reviewA, reviewB), 1); + }); + it('return -1 if reviewB is older', function() { + assert.strictEqual(wrapper.instance().compareReviewsByDate(reviewB, reviewA), -1); + }); + it('returns 0 if reviews have the same date', function() { + assert.strictEqual(wrapper.instance().compareReviewsByDate(reviewA, reviewA), 0); + }); + }); }); From 11fb55898d9adf0f48b6121fadfa3a9d0dbd6a0d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 9 Jan 2019 16:07:48 -0500 Subject: [PATCH 1749/4053] Describe the review summary panel within the "Files Changed" tab Co-Authored-By: Vanessa Yuen Co-Authored-By: Katrina Uychaco --- docs/feature-requests/003-pull-request-review.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index fdc7654fec..23244c0a45 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -61,17 +61,24 @@ When the pull request is checked out, an "open" button is shown in the review fo Clicking on the "Files Changed" tab displays the full, multi-file diff associated with the pull request. This is akin to the "Files changed" tab on dotcom. +> TODO: Change "Show review comments" checkbox to an expand/collapse review summaries control + ![files](https://user-images.githubusercontent.com/378023/46536560-d3bb4200-c8e9-11e8-9764-dca0b84245cf.png) -* Diffs are editable. -* Editing the diff is _only_ possible if the pull request branch is checked out and the local branch history has not diverged from the remote branch history. +Clicking on the "Expand review summaries" control in the filter bar reveals an inline panel that displays the summary of each review created on this pull request, including the review's author, the review's current state, its summary comment, and a progress bar showing how many of the review comments associated with this review have been marked as resolved. + +> TODO: Illustrate the "review summary" list panel + +Clicking the checkbox within each review summary block hides or reveals the review summary comments associated with that review in diff on this tab. Clicking the "Collapse review summaries" control conceals the review summary panel again. + +Beneath the review summary panel is the pull request's combined diff. Diffs are editable, but _only_ if the pull request branch is checked out and the local branch history has not diverged incompatibly from the remote branch history. + +For large diffs, the files can be collapsed to get a better overview. Uncollapsed (default) | Collapsed --- | --- ![files](https://user-images.githubusercontent.com/378023/46536560-d3bb4200-c8e9-11e8-9764-dca0b84245cf.png) | ![collapsed files](https://user-images.githubusercontent.com/378023/46931273-7069a680-d085-11e8-9ea7-c96a1772fe27.png) -* For large diffs, the files can be collapsed to get a better overview. - #### Create a new review ##### `+` Button From 4696ec6fff3c236da0101784be4e3f5f6cd5ed34 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 9 Jan 2019 16:45:16 -0500 Subject: [PATCH 1750/4053] "Review summary list panel" concept --- docs/feature-requests/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 23244c0a45..2eaf6dd336 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -67,7 +67,7 @@ Clicking on the "Files Changed" tab displays the full, multi-file diff associate Clicking on the "Expand review summaries" control in the filter bar reveals an inline panel that displays the summary of each review created on this pull request, including the review's author, the review's current state, its summary comment, and a progress bar showing how many of the review comments associated with this review have been marked as resolved. -> TODO: Illustrate the "review summary" list panel +![review summary list panel](https://user-images.githubusercontent.com/17565/50930369-e172ed00-142d-11e9-8ae4-00106dde80f5.png) Clicking the checkbox within each review summary block hides or reveals the review summary comments associated with that review in diff on this tab. Clicking the "Collapse review summaries" control conceals the review summary panel again. From 7ff898fc09625abb84bd0d70fb5176379e6e582a Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 9 Jan 2019 13:49:17 -0800 Subject: [PATCH 1751/4053] t e s t c o v e r a g e --- test/controllers/pr-reviews-controller.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index 4edf917316..11a1ff9729 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -250,6 +250,18 @@ describe('PullRequestReviewsController', function() { assert.strictEqual(comments[0].body, 'reply to outdated comment'); }); }); + + describe('handleError', function() { + it('attempts to load more reviews', function() { + const wrapper = shallow(buildApp()); + + const loadMoreStub = sinon.stub(wrapper.instance(), '_attemptToLoadMoreReviews'); + wrapper.instance().handleError(); + + assert.strictEqual(loadMoreStub.callCount, 1); + }); + }); + describe('compareReviewsByDate', function() { let wrapper; const reviewA = reviewBuilder().submittedAt('2018-12-28T20:40:55Z').build(); From 3aeb59e0ca3feb8dce1b32e8f78911dd9a7469d8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 9 Jan 2019 17:01:29 -0500 Subject: [PATCH 1752/4053] What I'm picturing for the PullRequestReviewsItem --- docs/feature-requests/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 2eaf6dd336..2fc225648d 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -130,7 +130,7 @@ This item is opened in the workspace's right dock when the user: It shows a scrollable view of all of the reviews and comments associated with a specific pull request, -> TODO: Illustration for the PullRequestReviewsItem +![pull request reviews item](https://user-images.githubusercontent.com/17565/50931285-213ad400-1430-11e9-8dd2-bd0cc98216fa.png) Reviews are sorted by "urgency," showing reviews that still need to be addressed at the top. Within each group, sorting is done by "newest first". From f805399bf505e5b304fead82cc5a26202a9325da Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 9 Jan 2019 16:27:30 -0800 Subject: [PATCH 1753/4053] add ugly ForwardRef thing to fix tests --- test/controllers/pr-reviews-controller.test.js | 2 +- test/views/multi-file-patch-view.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index 11a1ff9729..d6210d07bc 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -64,7 +64,7 @@ describe('PullRequestReviewsController', function() { const reviewSpecs = [review1, review2]; const wrapper = shallow(buildApp({reviewSpecs})); - const containers = wrapper.find('Relay(BarePullRequestReviewCommentsContainer)'); + const containers = wrapper.find('ForwardRef(Relay(BarePullRequestReviewCommentsContainer))'); assert.strictEqual(containers.length, 2); assert.strictEqual(containers.at(0).prop('review').id, review1.id); diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 813c206af9..d3436debf5 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -137,7 +137,7 @@ describe('MultiFilePatchView', function() { it('renders a PullRequestsReviewsContainer if itemType is IssueishDetailItem', function() { const wrapper = shallow(buildApp({itemType: IssueishDetailItem})); - assert.lengthOf(wrapper.find('Relay(PullRequestReviewsController)'), 1); + assert.lengthOf(wrapper.find('ForwardRef(Relay(PullRequestReviewsController))'), 1); }); it('renders the file patch within an editor', function() { From 9e6e82e7a4adbf82a76fc589aa506b628fe75dd5 Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 10 Jan 2019 16:15:05 +0900 Subject: [PATCH 1754/4053] Add screenshot/gif section --- PULL_REQUEST_TEMPLATE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index c6d8eb658e..09e071659a 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -13,6 +13,10 @@ We must be able to understand the design of your change from this description. I --> +### Screenshot/Gif + + + ### Alternate Designs From a8bd864bcf1b75aa8dfba29e053be35c630f531e Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 10 Jan 2019 16:24:57 +0900 Subject: [PATCH 1755/4053] Add a comma --- PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 09e071659a..7e2f5af7de 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -15,7 +15,7 @@ We must be able to understand the design of your change from this description. I ### Screenshot/Gif - + ### Alternate Designs From a6149058a12f9feec97bbd13a825f0acf35c7721 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 11:27:44 -0500 Subject: [PATCH 1756/4053] Ignore conditionals that are too much of a pain to test --- lib/git-shell-out-strategy.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index d8ad54d16a..2df45a5051 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -119,6 +119,7 @@ export default class GitShellOutStrategy { // Attempt to collect the --exec-path from a native git installation. execPathPromise = new Promise((resolve, reject) => { childProcess.exec('git --exec-path', (error, stdout, stderr) => { + /* istanbul ignore if */ if (error) { // Oh well resolve(null); @@ -202,6 +203,7 @@ export default class GitShellOutStrategy { env.ATOM_GITHUB_GPG_PROMPT = 'true'; } + /* istanbul ignore if */ if (diagnosticsEnabled) { env.GIT_TRACE = 'true'; env.GIT_TRACE_CURL = 'true'; @@ -214,9 +216,11 @@ export default class GitShellOutStrategy { opts.stdinEncoding = 'utf8'; } + /* istanbul ignore if */ if (process.env.PRINT_GIT_TIMES) { console.time(`git:${formattedArgs}`); } + return new Promise(async (resolve, reject) => { if (options.beforeRun) { const newArgsOpts = await options.beforeRun({args, opts}); @@ -236,6 +240,7 @@ export default class GitShellOutStrategy { // chance to fall back to GIT_ASKPASS from the credential handler. await new Promise((resolveKill, rejectKill) => { require('tree-kill')(handlerPid, 'SIGTERM', err => { + /* istanbul ignore if */ if (err) { rejectKill(err); } else { resolveKill(); } }); }); @@ -258,14 +263,18 @@ export default class GitShellOutStrategy { timingMarker.mark('ipc', now - ipcTime); } timingMarker.finalize(); + + /* istanbul ignore if */ if (process.env.PRINT_GIT_TIMES) { console.timeEnd(`git:${formattedArgs}`); } + if (gitPromptServer) { gitPromptServer.terminate(); } subscriptions.dispose(); + /* istanbul ignore if */ if (diagnosticsEnabled) { const exposeControlCharacters = raw => { if (!raw) { return ''; } @@ -374,6 +383,7 @@ export default class GitShellOutStrategy { options.processCallback = child => { childPid = child.pid; + /* istanbul ignore next */ child.stdin.on('error', err => { throw new Error( `Error writing to stdin: git ${args.join(' ')} in ${this.workingDir}\n${options.stdin}\n${err}`); @@ -385,12 +395,14 @@ export default class GitShellOutStrategy { return { promise, cancel: () => { + /* istanbul ignore if */ if (!childPid) { return Promise.resolve(); } return new Promise((resolve, reject) => { require('tree-kill')(childPid, 'SIGTERM', err => { + /* istanbul ignore if */ if (err) { reject(err); } else { resolve(); } }); }); From 3b5e0b2fb046b383dbfb52d634d6ceca422806d2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 11:29:39 -0500 Subject: [PATCH 1757/4053] git rev-parse --resolve-git-dir will always return an absolute path --- lib/git-shell-out-strategy.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 2df45a5051..323a11fecf 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -423,11 +423,7 @@ export default class GitShellOutStrategy { await fs.stat(this.workingDir); // fails if folder doesn't exist const output = await this.exec(['rev-parse', '--resolve-git-dir', path.join(this.workingDir, '.git')]); const dotGitDir = output.trim(); - if (path.isAbsolute(dotGitDir)) { - return toNativePathSep(dotGitDir); - } else { - return toNativePathSep(path.resolve(path.join(this.workingDir, dotGitDir))); - } + return toNativePathSep(dotGitDir); } catch (e) { return null; } From 3313d22bde5fc1d457c78b0b34359dfd06f7e5be Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 11:30:09 -0500 Subject: [PATCH 1758/4053] "unfold" parameter is never used --- lib/git-shell-out-strategy.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 323a11fecf..68ac46c68e 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -815,11 +815,8 @@ export default class GitShellOutStrategy { } } - mergeTrailers(commitMessage, trailers, unfold) { + mergeTrailers(commitMessage, trailers) { const args = ['interpret-trailers']; - if (unfold) { - args.push('--unfold'); - } for (const trailer of trailers) { args.push('--trailer', `${trailer.token}=${trailer.value}`); } From df55ac0ddec0ec9122409ad1d73f7296a2c82b95 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 11:32:05 -0500 Subject: [PATCH 1759/4053] Cover throwing LargeRepoError from getStatusBundle() --- test/git-strategies.test.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 6dbd031fdf..8aa15ed933 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -8,7 +8,7 @@ import hock from 'hock'; import {GitProcess} from 'dugite'; import CompositeGitStrategy from '../lib/composite-git-strategy'; -import GitShellOutStrategy from '../lib/git-shell-out-strategy'; +import GitShellOutStrategy, {LargeRepoError} from '../lib/git-shell-out-strategy'; import WorkerManager from '../lib/worker-manager'; import {cloneRepository, initRepository, assertDeepPropertyVals, setUpLocalAndRemoteRepositories} from './helpers'; @@ -119,8 +119,9 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); }); - if (process.platform === 'win32') { - describe('getStatusBundle()', function() { + + describe('getStatusBundle()', function() { + if (process.platform === 'win32') { it('normalizes the path separator on Windows', async function() { const workingDir = await cloneRepository('three-files'); const git = createTestStrategy(workingDir); @@ -135,8 +136,17 @@ import * as reporterProxy from '../lib/reporter-proxy'; const changedPaths = changedEntries.map(entry => entry.filePath); assert.deepEqual(changedPaths, [relPathA, relPathB]); }); + } + + it('throws a LargeRepoError when the status output is too large', async function() { + const workingDir = await cloneRepository('three-files'); + const git = createTestStrategy(workingDir); + + sinon.stub(git, 'exec').resolves({length: 1024 * 1024 * 10 + 1}); + + await assert.isRejected(git.getStatusBundle(), LargeRepoError); }); - } + }); describe('getHeadCommit()', function() { it('gets the SHA and message of the most recent commit', async function() { From 3c2b06f3bc5edd62766bf98beb59212fc52fd571 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 11:32:51 -0500 Subject: [PATCH 1760/4053] Cover codepaths in exec() --- test/git-strategies.test.js | 59 ++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 8aa15ed933..eb123cc51e 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -29,6 +29,48 @@ import * as reporterProxy from '../lib/reporter-proxy'; }; describe(`Git commands for CompositeGitStrategy made of [${strategies.map(s => s.name).join(', ')}]`, function() { + describe('exec', function() { + let git, incrementCounterStub; + + beforeEach(async function() { + const workingDir = await cloneRepository(); + git = createTestStrategy(workingDir); + incrementCounterStub = sinon.stub(reporterProxy, 'incrementCounter'); + }); + + describe('when the WorkerManager is not ready or disabled', function() { + beforeEach(function() { + sinon.stub(WorkerManager.getInstance(), 'isReady').returns(false); + }); + + it('kills the git process when cancel is triggered by the prompt server', async function() { + const promptStub = sinon.stub().rejects(); + git.setPromptCallback(promptStub); + + const stdin = dedent` + host=noway.com + username=me + + `; + await git.exec(['credential', 'fill'], {useGitPromptServer: true, stdin}); + + assert.isTrue(promptStub.called); + }); + }); + + it('does not call incrementCounter when git command is on the ignore list', async function() { + await git.exec(['status']); + assert.equal(incrementCounterStub.callCount, 0); + }); + + it('does call incrementCounter when git command is NOT on the ignore list', async function() { + await git.exec(['commit', '--allow-empty', '-m', 'make an empty commit']); + + assert.equal(incrementCounterStub.callCount, 1); + assert.deepEqual(incrementCounterStub.lastCall.args, ['commit']); + }); + }); + // https://github.com/atom/github/issues/1051 // https://github.com/atom/github/issues/898 it('passes all environment variables to spawned git process', async function() { @@ -1534,24 +1576,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); }); }); - describe('exec', function() { - let workingDirPath, git, incrementCounterStub; - beforeEach(async function() { - workingDirPath = await cloneRepository('three-files'); - git = createTestStrategy(workingDirPath); - incrementCounterStub = sinon.stub(reporterProxy, 'incrementCounter'); - }); - it('does not call incrementCounter when git command is on the ignore list', async function() { - await git.exec(['status']); - assert.equal(incrementCounterStub.callCount, 0); - }); - it('does call incrementCounter when git command is NOT on the ignore list', async function() { - await git.exec(['commit', '--allow-empty', '-m', 'make an empty commit']); - assert.equal(incrementCounterStub.callCount, 1); - assert.deepEqual(incrementCounterStub.lastCall.args, ['commit']); - }); - }); describe('executeGitCommand', function() { it('shells out in process until WorkerManager instance is ready', async function() { if (process.env.ATOM_GITHUB_INLINE_GIT_EXEC) { From 240ccdf0afac8353cf59f27021ed0289cd031266 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 11:33:24 -0500 Subject: [PATCH 1761/4053] Cover fetchCommitMessageTemplate() --- test/git-strategies.test.js | 45 ++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index eb123cc51e..f713da487b 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1,6 +1,7 @@ import fs from 'fs-extra'; import path from 'path'; import http from 'http'; +import os from 'os'; import mkdirp from 'mkdirp'; import dedent from 'dedent-js'; @@ -128,11 +129,15 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); describe('fetchCommitMessageTemplate', function() { - it('gets commit message from template', async function() { - const workingDirPath = await cloneRepository('three-files'); - const git = createTestStrategy(workingDirPath); - const templateText = 'some commit message'; + let git, workingDirPath, templateText; + + beforeEach(async function() { + workingDirPath = await cloneRepository('three-files'); + git = createTestStrategy(workingDirPath); + templateText = 'some commit message'; + }); + it('gets commit message from template', async function() { const commitMsgTemplatePath = path.join(workingDirPath, '.gitmessage'); await fs.writeFile(commitMsgTemplatePath, templateText, {encoding: 'utf8'}); @@ -141,17 +146,11 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); it('if config is not set return null', async function() { - const workingDirPath = await cloneRepository('three-files'); - const git = createTestStrategy(workingDirPath); - assert.isNotOk(await git.getConfig('commit.template')); // falsy value of null or '' assert.isNull(await git.fetchCommitMessageTemplate()); }); it('if config is set but file does not exist throw an error', async function() { - const workingDirPath = await cloneRepository('three-files'); - const git = createTestStrategy(workingDirPath); - const nonExistentCommitTemplatePath = path.join(workingDirPath, 'file-that-doesnt-exist'); await git.setConfig('commit.template', nonExistentCommitTemplatePath); await assert.isRejected( @@ -159,6 +158,32 @@ import * as reporterProxy from '../lib/reporter-proxy'; `Invalid commit template path set in Git config: ${nonExistentCommitTemplatePath}`, ); }); + + it('replaces ~ with your home directory', async function() { + await git.setConfig('commit.template', '~/does-not-exist.txt'); + await assert.isRejected( + git.fetchCommitMessageTemplate(), + `Invalid commit template path set in Git config: ${path.join(os.homedir(), 'does-not-exist.txt')}`, + ); + }); + + it("replaces ~user with user's home directory", async function() { + const expectedFullPath = path.join(path.dirname(os.homedir()), 'nope/does-not-exist.txt'); + await git.setConfig('commit.template', '~nope/does-not-exist.txt'); + await assert.isRejected( + git.fetchCommitMessageTemplate(), + `Invalid commit template path set in Git config: ${expectedFullPath}`, + ); + }); + + it('interprets relative paths local to the working directory', async function() { + const subDir = path.join(workingDirPath, 'abc/def/ghi'); + const subPath = path.join(subDir, 'template.txt'); + await fs.mkdirs(subDir); + await fs.writeFile(subPath, templateText, {encoding: 'utf8'}); + await git.setConfig('commit.template', path.join('abc/def/ghi/template.txt')); + assert.strictEqual(await git.fetchCommitMessageTemplate(), templateText); + }); }); From 0b454873767d356403d75920216eeb2506079797 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 11:34:47 -0500 Subject: [PATCH 1762/4053] Cover handling of unexpected git errors --- test/git-strategies.test.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index f713da487b..bb8d9456c3 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -503,6 +503,14 @@ import * as reporterProxy from '../lib/reporter-proxy'; const authors = await git.getAuthors({max: 1}); assert.deepEqual(authors, []); }); + + it('propagates other git errors', async function() { + const workingDirPath = await cloneRepository('multiple-commits'); + const git = createTestStrategy(workingDirPath); + sinon.stub(git, 'exec').rejects(new Error('oh no')); + + await assert.isRejected(git.getAuthors(), /oh no/); + }); }); describe('diffFileStatus', function() { @@ -1059,6 +1067,14 @@ import * as reporterProxy from '../lib/reporter-proxy'; await git.setConfig('awesome.devs', 'BinaryMuse,kuychaco,smashwilson'); assert.equal('BinaryMuse,kuychaco,smashwilson', await git.getConfig('awesome.devs')); }); + + it('propagates unexpected git errors', async function() { + const workingDirPath = await cloneRepository('three-files'); + const git = createTestStrategy(workingDirPath); + sinon.stub(git, 'exec').rejects(new Error('AHHHH')); + + await assert.isRejected(git.getConfig('some.key'), /AHHHH/); + }); }); describe('commit(message, options)', function() { @@ -1447,6 +1463,14 @@ import * as reporterProxy from '../lib/reporter-proxy'; const contents = await git.exec(['cat-file', '-p', sha]); assert.equal(contents, 'foo\n'); }); + + it('propagates unexpected git errors from hash-object', async function() { + const workingDirPath = await cloneRepository(); + const git = createTestStrategy(workingDirPath); + sinon.stub(git, 'exec').rejects(new Error('shiiiit')); + + await assert.isRejected(git.createBlob({filePath: 'a.txt'}), /shiiiit/); + }); }); describe('expandBlobToFile(absFilePath, sha)', function() { @@ -1542,6 +1566,14 @@ import * as reporterProxy from '../lib/reporter-proxy'; assert.isTrue(contents.includes('<<<<<<<')); assert.isTrue(contents.includes('>>>>>>>')); }); + + it('propagates unexpected git errors', async function() { + const workingDirPath = await cloneRepository('three-files'); + const git = createTestStrategy(workingDirPath); + sinon.stub(git, 'exec').rejects(new Error('ouch')); + + await assert.isRejected(git.mergeFile('a.txt', 'b.txt', 'c.txt', 'result.txt'), /ouch/); + }); }); describe('updateIndex(filePath, commonBaseSha, oursSha, theirsSha)', function() { From 24da545a9180ebbb271fc1f1260baefb6820e311 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 11:35:34 -0500 Subject: [PATCH 1763/4053] Cover unexpected git diff output --- test/git-strategies.test.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index bb8d9456c3..852064efd1 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -609,6 +609,42 @@ import * as reporterProxy from '../lib/reporter-proxy'; assert.isDefined(diffOutput); }); + it('rejects if an unexpected number of diffs is returned', async function() { + const workingDirPath = await cloneRepository(); + const git = createTestStrategy(workingDirPath); + sinon.stub(git, 'exec').resolves(dedent` + diff --git aaa.txt aaa.txt + index df565d30..244a7225 100644 + --- aaa.txt + +++ aaa.txt + @@ -100,3 +100,3 @@ + 000 + -001 + +002 + 003 + diff --git aaa.txt aaa.txt + index df565d30..244a7225 100644 + --- aaa.txt + +++ aaa.txt + @@ -100,3 +100,3 @@ + 000 + -001 + +002 + 003 + diff --git aaa.txt aaa.txt + index df565d30..244a7225 100644 + --- aaa.txt + +++ aaa.txt + @@ -100,3 +100,3 @@ + 000 + -001 + +002 + 003 + `); + + await assert.isRejected(git.getDiffsForFilePath('aaa.txt'), /Expected between 0 and 2 diffs/); + }); + describe('when the file is unstaged', function() { it('returns a diff comparing the working directory copy of the file and the version on the index', async function() { const workingDirPath = await cloneRepository('three-files'); From 97200a16e93c7c858ca4c4f8a253045df7962e44 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 11:35:51 -0500 Subject: [PATCH 1764/4053] Input argument validation tests --- test/git-strategies.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 852064efd1..378f24abf0 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -971,6 +971,12 @@ import * as reporterProxy from '../lib/reporter-proxy'; assert.deepEqual(stagedChange.hunks[0].lines, [' foo', '+bar']); }); }); + + it('fails when an invalid type is passed', async function() { + const workingDirPath = await cloneRepository('three-files'); + const git = createTestStrategy(workingDirPath); + assert.throws(() => git.reset('scrambled'), /Invalid type scrambled/); + }); }); describe('deleteRef()', function() { @@ -1507,6 +1513,12 @@ import * as reporterProxy from '../lib/reporter-proxy'; await assert.isRejected(git.createBlob({filePath: 'a.txt'}), /shiiiit/); }); + + it('rejects if neither file path or stdin are provided', async function() { + const workingDirPath = await cloneRepository(); + const git = createTestStrategy(workingDirPath); + await assert.isRejected(git.createBlob(), /Must supply file path or stdin/); + }); }); describe('expandBlobToFile(absFilePath, sha)', function() { From 8bf9385ac242da5f4849da714933d4983d2ff68c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 11:36:10 -0500 Subject: [PATCH 1765/4053] Fail correctly when amending an unborn commit --- test/git-strategies.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 378f24abf0..9cb81fb169 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1198,6 +1198,13 @@ import * as reporterProxy from '../lib/reporter-proxy'; assert.strictEqual(amendedCommit.messageSubject, 'first'); assert.strictEqual(amendedCommit.messageBody, 'second\n\nthird'); }); + + it('attempts to amend an unborn commit', async function() { + const workingDirPath = await initRepository(); + const git = createTestStrategy(workingDirPath); + + await assert.isRejected(git.commit('', {amend: true, allowEmpty: true}), /You have nothing to amend/); + }); }); }); From 4860e97ddfc46d5b3a128ef3daa76ec1036dfb28 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 11:36:27 -0500 Subject: [PATCH 1766/4053] Test degenerate case of checkoutSide() --- test/git-strategies.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 9cb81fb169..aa9ade3c8a 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1302,6 +1302,17 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); }); + describe('checkoutSide', function() { + it('is a no-op when no paths are provided', async function() { + const workdir = await cloneRepository(); + const git = await createTestStrategy(workdir); + sinon.spy(git, 'exec'); + + await git.checkoutSide('ours', []); + assert.isFalse(git.exec.called); + }); + }); + // Only needs to be tested on strategies that actually implement gpgExec describe('GPG signing', function() { let git; From 6539e2b2ad7767ef16ed6a37686f2dcd75cb8e90 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 13:19:09 -0500 Subject: [PATCH 1767/4053] Cover git process spawn rejection --- test/git-strategies.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index aa9ade3c8a..8620ea1010 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -59,6 +59,11 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); }); + it('rejects if the process fails to spawn for an unexpected reason', async function() { + sinon.stub(git, 'executeGitCommand').returns({promise: Promise.reject(new Error('wat'))}); + await assert.isRejected(git.exec(['version']), /wat/); + }); + it('does not call incrementCounter when git command is on the ignore list', async function() { await git.exec(['status']); assert.equal(incrementCounterStub.callCount, 0); From f99e1e22e09e81a22c3fe0cb6520db0a565fa71e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 13:38:50 -0500 Subject: [PATCH 1768/4053] Use correct path separators in fake template paths --- test/git-strategies.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 8620ea1010..d782777f19 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -165,7 +165,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); it('replaces ~ with your home directory', async function() { - await git.setConfig('commit.template', '~/does-not-exist.txt'); + await git.setConfig('commit.template', path.join('~/does-not-exist.txt')); await assert.isRejected( git.fetchCommitMessageTemplate(), `Invalid commit template path set in Git config: ${path.join(os.homedir(), 'does-not-exist.txt')}`, @@ -174,7 +174,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; it("replaces ~user with user's home directory", async function() { const expectedFullPath = path.join(path.dirname(os.homedir()), 'nope/does-not-exist.txt'); - await git.setConfig('commit.template', '~nope/does-not-exist.txt'); + await git.setConfig('commit.template', path.join('~nope/does-not-exist.txt')); await assert.isRejected( git.fetchCommitMessageTemplate(), `Invalid commit template path set in Git config: ${expectedFullPath}`, From 73a6354530563d574f3bc9394e78605c1b3c2a14 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 14:10:18 -0500 Subject: [PATCH 1769/4053] Handle either path separator in EXPAND_TILDE_REGEX --- lib/git-shell-out-strategy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 68ac46c68e..a8cde05c5c 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -61,9 +61,9 @@ const DISABLE_COLOR_FLAGS = [ * Ex: on Mac ~kuychaco/ is expanded to the specified user’s home directory (/Users/kuychaco) * Regex translation: * ^~ line starts with tilde - * ([^/]*)/ captures non-forwardslash characters before first slash + * ([^/]*)[\\/] captures non-forwardslash characters before first slash */ -const EXPAND_TILDE_REGEX = new RegExp('^~([^/]*)/'); +const EXPAND_TILDE_REGEX = new RegExp('^~([^/]*)[\\/]'); export default class GitShellOutStrategy { static defaultExecArgs = { From 9658e1eb10974c19bf2dc4837cc476e32943b1df Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 14:27:04 -0500 Subject: [PATCH 1770/4053] Test coverage for GitTempDir --- test/git-temp-dir.test.js | 71 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 test/git-temp-dir.test.js diff --git a/test/git-temp-dir.test.js b/test/git-temp-dir.test.js new file mode 100644 index 0000000000..f5746c0375 --- /dev/null +++ b/test/git-temp-dir.test.js @@ -0,0 +1,71 @@ +import fs from 'fs-extra'; +import path from 'path'; + +import GitTempDir, {BIN_SCRIPTS} from '../lib/git-temp-dir'; + +describe('GitTempDir', function() { + it('ensures that a temporary directory is populated', async function() { + const tempDir = new GitTempDir(); + await tempDir.ensure(); + + const root = tempDir.getRootPath(); + for (const scriptName in BIN_SCRIPTS) { + const script = BIN_SCRIPTS[scriptName]; + const stat = await fs.stat(path.join(root, script)); + assert.isTrue(stat.isFile()); + if (script.endsWith('.sh') && process.platform !== 'win32') { + // eslint-disable-next-line no-bitwise + assert.isTrue((stat.mode & fs.constants.S_IXUSR) === fs.constants.S_IXUSR); + } + } + + await tempDir.ensure(); + assert.strictEqual(root, tempDir.getRootPath()); + }); + + it('generates getters for script paths', async function() { + const tempDir = new GitTempDir(); + await tempDir.ensure(); + + const scriptPath = tempDir.getScriptPath('git-credential-atom.js'); + assert.isTrue(scriptPath.startsWith(tempDir.getRootPath())); + assert.isTrue(scriptPath.endsWith('git-credential-atom.js')); + + assert.strictEqual(tempDir.getCredentialHelperJs(), tempDir.getScriptPath('git-credential-atom.js')); + assert.strictEqual(tempDir.getCredentialHelperSh(), tempDir.getScriptPath('git-credential-atom.sh')); + assert.strictEqual(tempDir.getAskPassJs(), tempDir.getScriptPath('git-askpass-atom.js')); + }); + + it('fails when the temp dir is not yet created', function() { + const tempDir = new GitTempDir(); + assert.throws(() => tempDir.getAskPassJs(), /uninitialized GitTempDir/); + }); + + if (process.platform === 'win32') { + it('generates a valid named pipe path for its socket', async function() { + const tempDir = new GitTempDir(); + await tempDir.ensure(); + + assert.match(tempDir.getSocketPath(), /^\\\\\?\\pipe\\/); + }); + } else { + it('generates a socket path within the directory', async function() { + const tempDir = new GitTempDir(); + await tempDir.ensure(); + + assert.isTrue(tempDir.getSocketPath().startsWith(tempDir.getRootPath())); + }); + } + + it('deletes the directory on dispose', async function() { + const tempDir = new GitTempDir(); + await tempDir.ensure(); + + const beforeStat = await fs.stat(tempDir.getRootPath()); + assert.isTrue(beforeStat.isDirectory()); + + await tempDir.dispose(); + + await assert.isRejected(fs.stat(tempDir.getRootPath()), /ENOENT/); + }); +}); From 3d34f7e008245a5a2152fc0a858b17acf9ce6013 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 14:50:42 -0500 Subject: [PATCH 1771/4053] path.join() looks like it's resolving the path on Windows? --- test/git-strategies.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index d782777f19..56aaf34248 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -165,7 +165,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); it('replaces ~ with your home directory', async function() { - await git.setConfig('commit.template', path.join('~/does-not-exist.txt')); + await git.setConfig('commit.template', `~${path.sep}does-not-exist.txt`); await assert.isRejected( git.fetchCommitMessageTemplate(), `Invalid commit template path set in Git config: ${path.join(os.homedir(), 'does-not-exist.txt')}`, @@ -174,7 +174,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; it("replaces ~user with user's home directory", async function() { const expectedFullPath = path.join(path.dirname(os.homedir()), 'nope/does-not-exist.txt'); - await git.setConfig('commit.template', path.join('~nope/does-not-exist.txt')); + await git.setConfig('commit.template', `~nope${path.sep}does-not-exist.txt`); await assert.isRejected( git.fetchCommitMessageTemplate(), `Invalid commit template path set in Git config: ${expectedFullPath}`, From efb6ddcecda891f2d3018a1894df792bb0504d30 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 15:36:15 -0500 Subject: [PATCH 1772/4053] More Regexp tries --- lib/git-shell-out-strategy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index a8cde05c5c..36dd15fbaf 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -61,9 +61,9 @@ const DISABLE_COLOR_FLAGS = [ * Ex: on Mac ~kuychaco/ is expanded to the specified user’s home directory (/Users/kuychaco) * Regex translation: * ^~ line starts with tilde - * ([^/]*)[\\/] captures non-forwardslash characters before first slash + * ([^\\/]*)[\\/] captures non-slash characters before first slash */ -const EXPAND_TILDE_REGEX = new RegExp('^~([^/]*)[\\/]'); +const EXPAND_TILDE_REGEX = new RegExp('^~([^\\/]*)[\\/]'); export default class GitShellOutStrategy { static defaultExecArgs = { From 89be380251dba8a6951d22df806e828fd8651f98 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 10 Jan 2019 16:12:50 -0500 Subject: [PATCH 1773/4053] Grrr --- lib/git-shell-out-strategy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 36dd15fbaf..dd4280aeba 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -61,9 +61,9 @@ const DISABLE_COLOR_FLAGS = [ * Ex: on Mac ~kuychaco/ is expanded to the specified user’s home directory (/Users/kuychaco) * Regex translation: * ^~ line starts with tilde - * ([^\\/]*)[\\/] captures non-slash characters before first slash + * ([^\\\\/]*)[\\\\/] captures non-slash characters before first slash */ -const EXPAND_TILDE_REGEX = new RegExp('^~([^\\/]*)[\\/]'); +const EXPAND_TILDE_REGEX = new RegExp('^~([^\\\\/]*)[\\\\/]'); export default class GitShellOutStrategy { static defaultExecArgs = { From 5428bb21f51a4f9ea68590e49520244df6c5eee6 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 10 Jan 2019 13:19:22 -0800 Subject: [PATCH 1774/4053] use default git cleanup configuration if commit template exists. in https://github.com/atom/github/issues/1817, there is a bug where we are not stripping commented lines if a git message template exists. The `verbatim` flag is passed if the commit is made from a mini editor. The mini editor is meant to be analagous of `git commit -m`, a quick and dirty sort of affair. As much as I don't want to add special cases here, it seemed like the cleanest to ignore `verbatim` if you have a template set. --- lib/git-shell-out-strategy.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index d8ad54d16a..40cade6d87 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -506,8 +506,12 @@ export default class GitShellOutStrategy { msg = rawMessage; } + // if commit template is used, that will not play nicely with `verbatim` + // because we want to strip commented lines from templates. + const template = await this.fetchCommitMessageTemplate(); + // Determine the cleanup mode. - if (verbatim) { + if (verbatim && !template) { args.push('--cleanup=verbatim'); } else { const configured = await this.getConfig('commit.cleanup'); From 5cf450782a87f19aca3da1c8ecb474b46deebd49 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 10 Jan 2019 13:39:43 -0800 Subject: [PATCH 1775/4053] add unit test to ensure that comments are stripped if commit message is used --- test/git-strategies.test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 6dbd031fdf..9d9583b481 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1036,6 +1036,24 @@ import * as reporterProxy from '../lib/reporter-proxy'; 'and things', ].join('\n')); }); + it('ignores verbatim flag if commit template is used', async function() { + const workingDirPath = await cloneRepository('three-files'); + const git = createTestStrategy(workingDirPath); + const templateText = '# this line should be stripped'; + + const commitMsgTemplatePath = path.join(workingDirPath, '.gitmessage'); + await fs.writeFile(commitMsgTemplatePath, templateText, {encoding: 'utf8'}); + + await git.setConfig('commit.template', commitMsgTemplatePath); + await git.setConfig('commit.cleanup', 'default'); + const commitMessage = ['this line should not be stripped', '', 'neither should this one', templateText].join('\n'); + await git.commit(commitMessage, {allowEmpty: true, verbatim: true}); + + const lastCommit = await git.getHeadCommit(); + assert.strictEqual(lastCommit.messageSubject, 'this line should not be stripped'); + // message body should not contain the template text + assert.strictEqual(lastCommit.messageBody, 'neither should this one'); + }); }); describe('when amend option is true', function() { From a9a7ecd6c347d205f59318c03f99d3c031a5b7b7 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 10 Jan 2019 14:50:07 -0800 Subject: [PATCH 1776/4053] move clock inside the only test that needs it it's cleaner, and the tests were stalling when run all together (the individual tests worked fine) --- test/controllers/pr-reviews-controller.test.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index d6210d07bc..eaba38f15c 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -108,15 +108,6 @@ describe('PullRequestReviewsController', function() { }); describe('attemptToLoadMoreReviews', function() { - let clock; - beforeEach(function() { - clock = sinon.useFakeTimers(); - }); - - afterEach(function() { - clock = sinon.restore(); - }); - it('does not call loadMore if hasMore is false', function() { const relayLoadMoreStub = sinon.stub(); const wrapper = shallow(buildApp({relayLoadMore: relayLoadMoreStub})); @@ -138,6 +129,7 @@ describe('PullRequestReviewsController', function() { }); it('calls loadMore after a timeout if hasMore is true and isLoading is true', function() { + const clock = sinon.useFakeTimers(); const relayLoadMoreStub = sinon.stub(); const relayHasMore = () => { return true; }; const relayIsLoading = () => { return true; }; @@ -153,6 +145,7 @@ describe('PullRequestReviewsController', function() { clock.tick(PAGINATION_WAIT_TIME_MS); assert.strictEqual(relayLoadMoreStub.callCount, 1); assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleError]); + sinon.restore(); }); }); From 888979372d6c6e27026d29caf2e4405bec6f6a73 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 10 Jan 2019 14:51:18 -0800 Subject: [PATCH 1777/4053] `accumulate` is totes a better name for those functions Co-Authored-By: Ash Wilson --- lib/containers/pr-review-comments-container.js | 6 +++--- lib/controllers/pr-reviews-controller.js | 4 ++-- test/containers/pr-review-comments-container.test.js | 10 +++++----- test/controllers/pr-reviews-controller.test.js | 10 +++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index c514c9f313..9ab65f7e10 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -21,10 +21,10 @@ export class BarePullRequestReviewCommentsContainer extends React.Component { } componentDidMount() { - this.handleComments(); + this.accumulateComments(); } - handleComments = error => { + accumulateComments = error => { /* istanbul ignore if */ if (error) { // eslint-disable-next-line no-console @@ -58,7 +58,7 @@ export class BarePullRequestReviewCommentsContainer extends React.Component { _loadMoreComments = () => { this.props.relay.loadMore( PAGE_SIZE, - this.handleComments, + this.accumulateComments, ); } diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 428bb38b7b..d7c3acd62c 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -43,7 +43,7 @@ export default class PullRequestReviewsController extends React.Component { } } - handleError = error => { + accumulateReviews = error => { /* istanbul ignore if */ if (error) { // eslint-disable-next-line no-console @@ -54,7 +54,7 @@ export default class PullRequestReviewsController extends React.Component { } _loadMoreReviews = () => { - this.props.relay.loadMore(PAGE_SIZE, this.handleError); + this.props.relay.loadMore(PAGE_SIZE, this.accumulateReviews); } render() { diff --git a/test/containers/pr-review-comments-container.test.js b/test/containers/pr-review-comments-container.test.js index 8eb7916221..164124640b 100644 --- a/test/containers/pr-review-comments-container.test.js +++ b/test/containers/pr-review-comments-container.test.js @@ -60,18 +60,18 @@ describe('PullRequestReviewCommentsContainer', function() { const wrapper = shallow(buildApp({relayLoadMore: relayLoadMoreStub})); wrapper.instance()._loadMoreComments(); - assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleComments]); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().accumulateComments]); }); }); - describe('handleComments', function() { + describe('accumulateComments', function() { it('collects comments and attempts to load more comments', function() { const collectCommentsStub = sinon.stub(); const wrapper = shallow(buildApp({}, {collectComments: collectCommentsStub})); // collect comments is called when mounted, we don't care about that in this test so reset the count collectCommentsStub.reset(); sinon.stub(wrapper.instance(), '_attemptToLoadMoreComments'); - wrapper.instance().handleComments(); + wrapper.instance().accumulateComments(); assert.strictEqual(collectCommentsStub.callCount, 1); @@ -112,7 +112,7 @@ describe('PullRequestReviewCommentsContainer', function() { wrapper.instance()._attemptToLoadMoreComments(); assert.strictEqual(relayLoadMoreStub.callCount, 1); - assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleComments]); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().accumulateComments]); }); it('calls loadMore after a timeout if hasMore is true and isLoading is true', function() { @@ -130,7 +130,7 @@ describe('PullRequestReviewCommentsContainer', function() { clock.tick(PAGINATION_WAIT_TIME_MS); assert.strictEqual(relayLoadMoreStub.callCount, 1); - assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleComments]); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().accumulateComments]); }); }); }); diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index eaba38f15c..83cfa8eadd 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -125,7 +125,7 @@ describe('PullRequestReviewsController', function() { wrapper.instance()._attemptToLoadMoreReviews(); assert.strictEqual(relayLoadMoreStub.callCount, 1); - assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleError]); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().accumulateReviews]); }); it('calls loadMore after a timeout if hasMore is true and isLoading is true', function() { @@ -144,7 +144,7 @@ describe('PullRequestReviewsController', function() { clock.tick(PAGINATION_WAIT_TIME_MS); assert.strictEqual(relayLoadMoreStub.callCount, 1); - assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleError]); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().accumulateReviews]); sinon.restore(); }); }); @@ -155,7 +155,7 @@ describe('PullRequestReviewsController', function() { const wrapper = shallow(buildApp({relayLoadMore: relayLoadMoreStub})); wrapper.instance()._loadMoreReviews(); - assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().handleError]); + assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().accumulateReviews]); }); }); @@ -244,12 +244,12 @@ describe('PullRequestReviewsController', function() { }); }); - describe('handleError', function() { + describe('accumulateReviews', function() { it('attempts to load more reviews', function() { const wrapper = shallow(buildApp()); const loadMoreStub = sinon.stub(wrapper.instance(), '_attemptToLoadMoreReviews'); - wrapper.instance().handleError(); + wrapper.instance().accumulateReviews(); assert.strictEqual(loadMoreStub.callCount, 1); }); From 7155d25a71f0523c2bb26891ab753447f3089a84 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 10 Jan 2019 14:53:35 -0800 Subject: [PATCH 1778/4053] move other fake :clock: inside test too --- .../containers/pr-review-comments-container.test.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/containers/pr-review-comments-container.test.js b/test/containers/pr-review-comments-container.test.js index 164124640b..df3d9ea318 100644 --- a/test/containers/pr-review-comments-container.test.js +++ b/test/containers/pr-review-comments-container.test.js @@ -86,15 +86,6 @@ describe('PullRequestReviewCommentsContainer', function() { }); describe('attemptToLoadMoreComments', function() { - let clock; - beforeEach(function() { - clock = sinon.useFakeTimers(); - }); - - afterEach(function() { - clock = sinon.restore(); - }); - it('does not call loadMore if hasMore is false', function() { const relayLoadMoreStub = sinon.stub(); const wrapper = shallow(buildApp({relayLoadMore: relayLoadMoreStub})); @@ -116,6 +107,7 @@ describe('PullRequestReviewCommentsContainer', function() { }); it('calls loadMore after a timeout if hasMore is true and isLoading is true', function() { + const clock = sinon.useFakeTimers(); const relayLoadMoreStub = sinon.stub(); const relayHasMore = () => { return true; }; const relayIsLoading = () => { return true; }; @@ -131,6 +123,9 @@ describe('PullRequestReviewCommentsContainer', function() { clock.tick(PAGINATION_WAIT_TIME_MS); assert.strictEqual(relayLoadMoreStub.callCount, 1); assert.deepEqual(relayLoadMoreStub.lastCall.args, [PAGE_SIZE, wrapper.instance().accumulateComments]); + + // buybye fake timer it was nice knowing you + sinon.restore(); }); }); }); From 615df63c31fd0432b5245e95f48ae4c6cd40451d Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 10 Jan 2019 16:50:02 -0800 Subject: [PATCH 1779/4053] deal with minimized comments you can mark comments as spammy, or even abusive, so I'm really glad we caught this before shipping. --- .../issueishDetailContainerQuery.graphql.js | 12 ++++- .../prReviewCommentsContainerQuery.graphql.js | 12 ++++- ...rReviewCommentsContainer_review.graphql.js | 10 +++- .../prReviewsContainerQuery.graphql.js | 12 ++++- .../pr-review-comments-container.js | 1 + .../prDetailViewRefetchQuery.graphql.js | 12 ++++- lib/views/pr-review-comments-view.js | 46 ++++++++++++------- styles/pr-comment.less | 8 ++++ 8 files changed, 88 insertions(+), 25 deletions(-) diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 08c8d4132a..2ec6a9595d 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash b2844afc76c2e9c390039931a9af7d40 + * @relayHash aa9982b03c50cc4fd52b07a4206b1eb0 */ /* eslint-disable */ @@ -559,6 +559,7 @@ fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { } } bodyHTML + isMinimized path position replyTo { @@ -1207,7 +1208,7 @@ return { "operationKind": "query", "name": "issueishDetailContainerQuery", "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_3D8CP9\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_3D8CP9 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_3D8CP9\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_3D8CP9 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1647,6 +1648,13 @@ return { v2, v26, v12, + { + "kind": "ScalarField", + "alias": null, + "name": "isMinimized", + "args": null, + "storageKey": null + }, v39, v40, { diff --git a/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js index 0904ea42ba..6fa92aa46b 100644 --- a/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 5e0b465f16f55322d70100cd893a1c04 + * @relayHash d372fbac67c0b387985686b68db27130 */ /* eslint-disable */ @@ -63,6 +63,7 @@ fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { } } bodyHTML + isMinimized path position replyTo { @@ -139,7 +140,7 @@ return { "operationKind": "query", "name": "prReviewCommentsContainerQuery", "id": null, - "text": "query prReviewCommentsContainerQuery(\n $commentCount: Int!\n $commentCursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on PullRequestReview {\n ...prReviewCommentsContainer_review_1VbUmL\n }\n id\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "text": "query prReviewCommentsContainerQuery(\n $commentCount: Int!\n $commentCursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on PullRequestReview {\n ...prReviewCommentsContainer_review_1VbUmL\n }\n id\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -306,6 +307,13 @@ return { "args": null, "storageKey": null }, + { + "kind": "ScalarField", + "alias": null, + "name": "isMinimized", + "args": null, + "storageKey": null + }, { "kind": "ScalarField", "alias": null, diff --git a/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js b/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js index aec8134452..ebe2215fea 100644 --- a/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js +++ b/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js @@ -27,6 +27,7 @@ export type prReviewCommentsContainer_review = {| +login: string, |}, +bodyHTML: any, + +isMinimized: boolean, +path: string, +position: ?number, +replyTo: ?{| @@ -181,6 +182,13 @@ return { "args": null, "storageKey": null }, + { + "kind": "ScalarField", + "alias": null, + "name": "isMinimized", + "args": null, + "storageKey": null + }, { "kind": "ScalarField", "alias": null, @@ -238,5 +246,5 @@ return { }; })(); // prettier-ignore -(node/*: any*/).hash = '63492273ddd049ed59809581c7795811'; +(node/*: any*/).hash = 'ccea6475d7b22690b4aa61757b705968'; module.exports = node; diff --git a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js index 7e64650ea5..19537cca71 100644 --- a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 455845bc2fe2987a2c32d72dbc9247b6 + * @relayHash a3845eb13d831f5f140cc6adc98de1df */ /* eslint-disable */ @@ -108,6 +108,7 @@ fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { } } bodyHTML + isMinimized path position replyTo { @@ -263,7 +264,7 @@ return { "operationKind": "query", "name": "prReviewsContainerQuery", "id": null, - "text": "query prReviewsContainerQuery(\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_y4qc0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "text": "query prReviewsContainerQuery(\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_y4qc0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -492,6 +493,13 @@ return { "args": null, "storageKey": null }, + { + "kind": "ScalarField", + "alias": null, + "name": "isMinimized", + "args": null, + "storageKey": null + }, { "kind": "ScalarField", "alias": null, diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 9ab65f7e10..16051026bd 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -94,6 +94,7 @@ export default createPaginationContainer(BarePullRequestReviewCommentsContainer, login } bodyHTML + isMinimized path position replyTo { diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index 8b5d424baa..42e336a55a 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 631a28049723f0b6b8a3f8eb25a4f955 + * @relayHash c9c759b66c17ed333ce2946f8675ee19 */ /* eslint-disable */ @@ -475,6 +475,7 @@ fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { } } bodyHTML + isMinimized path position replyTo { @@ -885,7 +886,7 @@ return { "operationKind": "query", "name": "prDetailViewRefetchQuery", "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {}, "fragment": { "kind": "Fragment", @@ -1133,6 +1134,13 @@ return { v6, v24, v25, + { + "kind": "ScalarField", + "alias": null, + "name": "isMinimized", + "args": null, + "storageKey": null + }, v26, v27, { diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index 09f8bc9bf9..80e89af272 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -5,6 +5,8 @@ import {Point, Range} from 'atom'; import {toNativePathSep} from '../helpers'; import Marker from '../atom/marker'; import Decoration from '../atom/decoration'; +import Octicon from '../atom/octicon'; + import GithubDotcomMarkdown from './github-dotcom-markdown'; import Timeago from './timeago'; @@ -65,25 +67,37 @@ export class PullRequestCommentView extends React.Component { bodyHTML: PropTypes.string, url: PropTypes.string, createdAt: PropTypes.string.isRequired, + isMinimized: PropTypes.bool.isRequired, }).isRequired, } render() { - const author = this.props.comment.author; - const login = author ? author.login : 'someone'; - return ( -
    -
    - {login} - {login} commented{' '} - - - -
    -
    - -
    -
    - ); + if (this.props.comment.isMinimized) { + return ( +
    + + + This comment was hidden + +
    + ); + } else { + const author = this.props.comment.author; + const login = author ? author.login : 'someone'; + return ( +
    +
    + {login} + {login} commented{' '} + + + +
    +
    + +
    +
    + ); + } } } diff --git a/styles/pr-comment.less b/styles/pr-comment.less index f2ff654985..f71f9f097f 100644 --- a/styles/pr-comment.less +++ b/styles/pr-comment.less @@ -50,4 +50,12 @@ margin-left: @avatar-size + @avatar-spacing; // avatar + margin } + &-hidden { + font-size: 14px; + } + + &-icon { + vertical-align: middle; + } + } From b346a451b0e637f9969203e02a10dbffc36e0e86 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 11 Jan 2019 09:02:25 -0500 Subject: [PATCH 1780/4053] Normalize commit template paths on Windows --- lib/git-shell-out-strategy.js | 1 + test/git-strategies.test.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index dd4280aeba..f431a44961 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -454,6 +454,7 @@ export default class GitShellOutStrategy { // if no user is specified, fall back to using the home directory. return `${user ? path.join(path.dirname(homeDir), user) : homeDir}/`; }); + templatePath = toNativePathSep(templatePath); if (!path.isAbsolute(templatePath)) { templatePath = path.join(this.workingDir, templatePath); diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 56aaf34248..7ed32ece11 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -165,7 +165,8 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); it('replaces ~ with your home directory', async function() { - await git.setConfig('commit.template', `~${path.sep}does-not-exist.txt`); + // Fun fact: even on Windows, git does not accept "~\does-not-exist.txt" + await git.setConfig('commit.template', '~/does-not-exist.txt'); await assert.isRejected( git.fetchCommitMessageTemplate(), `Invalid commit template path set in Git config: ${path.join(os.homedir(), 'does-not-exist.txt')}`, @@ -174,7 +175,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; it("replaces ~user with user's home directory", async function() { const expectedFullPath = path.join(path.dirname(os.homedir()), 'nope/does-not-exist.txt'); - await git.setConfig('commit.template', `~nope${path.sep}does-not-exist.txt`); + await git.setConfig('commit.template', '~nope/does-not-exist.txt'); await assert.isRejected( git.fetchCommitMessageTemplate(), `Invalid commit template path set in Git config: ${expectedFullPath}`, From adf996ec3bae29e53253b1f540d1e311e1cebf0d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 11 Jan 2019 09:37:21 -0500 Subject: [PATCH 1781/4053] Handle path separator normalization on Windows --- lib/containers/pr-changed-files-container.js | 9 +++++---- lib/models/patch/multi-file-patch.js | 1 - test/containers/pr-changed-files-container.test.js | 8 ++++++++ test/fixtures/diffs/raw-diff.js | 6 +++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/containers/pr-changed-files-container.js b/lib/containers/pr-changed-files-container.js index beb3c5d38b..539c7a3b82 100644 --- a/lib/containers/pr-changed-files-container.js +++ b/lib/containers/pr-changed-files-container.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import {parse as parseDiff} from 'what-the-diff'; import {ItemTypePropType, EndpointPropType} from '../prop-types'; +import {toNativePathSep} from '../helpers'; import MultiFilePatchController from '../controllers/multi-file-patch-controller'; import LoadingView from '../views/loading-view'; import ErrorView from '../views/error-view'; @@ -60,13 +61,13 @@ export default class PullRequestChangedFilesContainer extends React.Component { buildPatch(rawDiff) { const diffs = parseDiff(rawDiff).map(diff => { - // diff coming from API will have the defaul git diff prefixes a/ and b/ - // e.g. a/file1.js and b/file2.js + // diff coming from API will have the defaul git diff prefixes a/ and b/ and use *nix-style / path separators. + // e.g. a/dir/file1.js and b/dir/file2.js // see https://git-scm.com/docs/git-diff#_generating_patches_with_p return { ...diff, - newPath: diff.newPath ? diff.newPath.replace(/^[a|b]\//, '') : diff.newPath, - oldPath: diff.oldPath ? diff.oldPath.replace(/^[a|b]\//, '') : diff.oldPath, + newPath: diff.newPath ? toNativePathSep(diff.newPath.replace(/^[a|b]\//, '')) : diff.newPath, + oldPath: diff.oldPath ? toNativePathSep(diff.oldPath.replace(/^[a|b]\//, '')) : diff.oldPath, }; }); return buildMultiFilePatch(diffs); diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index e04a498416..e21ae6158f 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -340,7 +340,6 @@ export default class MultiFilePatch { } getBufferRowForDiffPosition = (fileName, diffRow) => { - // TODO verify that this works on Windows const {startBufferRow, index} = this.diffRowOffsetIndices.get(fileName); const {offset} = index.lowerBound({diffRow}).data(); return startBufferRow + diffRow - offset; diff --git a/test/containers/pr-changed-files-container.test.js b/test/containers/pr-changed-files-container.test.js index af252cb56c..47896dcfb4 100644 --- a/test/containers/pr-changed-files-container.test.js +++ b/test/containers/pr-changed-files-container.test.js @@ -1,6 +1,7 @@ import React from 'react'; import {shallow} from 'enzyme'; import {parse as parseDiff} from 'what-the-diff'; +import path from 'path'; import {rawDiff, rawDiffWithPathPrefix} from '../fixtures/diffs/raw-diff'; import {buildMultiFilePatch} from '../../lib/models/patch'; @@ -79,6 +80,13 @@ describe('PullRequestChangedFilesContainer', function() { assert.notMatch(filePatches[0].oldFile.path, /^[a|b]\//); }); + it('converts file paths to use native path separators', function() { + const wrapper = shallow(buildApp()); + const {filePatches} = wrapper.instance().buildPatch(rawDiffWithPathPrefix); + assert.strictEqual(filePatches[0].newFile.path, path.join('bad/path.txt')); + assert.strictEqual(filePatches[0].oldFile.path, path.join('bad/path.txt')); + }); + it('passes loaded diff data through to the controller', async function() { const wrapper = shallow(buildApp({ token: '4321', diff --git a/test/fixtures/diffs/raw-diff.js b/test/fixtures/diffs/raw-diff.js index 0d57194fec..473df75cb5 100644 --- a/test/fixtures/diffs/raw-diff.js +++ b/test/fixtures/diffs/raw-diff.js @@ -12,10 +12,10 @@ const rawDiff = dedent` line3 `; const rawDiffWithPathPrefix = dedent` - diff --git a/badpath.txt b/badpath.txt + diff --git a/bad/path.txt b/bad/path.txt index af607bb..cfac420 100644 - --- a/badpath.txt - +++ b/badpath.txt + --- a/bad/path.txt + +++ b/bad/path.txt @@ -1,2 +1,3 @@ line0 -line1 From 185d096e010f00947bbecd766bb9e985a43b7881 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 11 Jan 2019 10:30:35 -0500 Subject: [PATCH 1782/4053] Cover uncovered lines in Side --- lib/models/conflicts/banner.js | 1 + lib/models/conflicts/side.js | 1 + .../single-2way-diff-empty.txt | 8 + test/models/conflicts/conflict.test.js | 369 +++++++++++++++++- 4 files changed, 370 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/conflict-marker-examples/single-2way-diff-empty.txt diff --git a/lib/models/conflicts/banner.js b/lib/models/conflicts/banner.js index 47080ca2ce..25630fe329 100644 --- a/lib/models/conflicts/banner.js +++ b/lib/models/conflicts/banner.js @@ -26,6 +26,7 @@ export default class Banner { revert() { const range = this.getMarker().getBufferRange(); this.editor.setTextInBufferRange(range, this.originalText); + this.getMarker().setBufferRange(range); } delete() { diff --git a/lib/models/conflicts/side.js b/lib/models/conflicts/side.js index e99174c89a..17585511a0 100644 --- a/lib/models/conflicts/side.js +++ b/lib/models/conflicts/side.js @@ -98,6 +98,7 @@ export default class Side { revert() { const range = this.getMarker().getBufferRange(); this.editor.setTextInBufferRange(range, this.originalText); + this.getMarker().setBufferRange(range); } deleteBanner() { diff --git a/test/fixtures/conflict-marker-examples/single-2way-diff-empty.txt b/test/fixtures/conflict-marker-examples/single-2way-diff-empty.txt new file mode 100644 index 0000000000..802eb878f7 --- /dev/null +++ b/test/fixtures/conflict-marker-examples/single-2way-diff-empty.txt @@ -0,0 +1,8 @@ +Before the start + +<<<<<<< HEAD +These are my changes +======= +>>>>>>> master + +Past the end diff --git a/test/models/conflicts/conflict.test.js b/test/models/conflicts/conflict.test.js index f8ac15018d..8a1c877a8a 100644 --- a/test/models/conflicts/conflict.test.js +++ b/test/models/conflicts/conflict.test.js @@ -21,10 +21,10 @@ describe('Conflict', function() { return atomEnv.workspace.open(fullPath); }; - const assertConflictOnRows = function(conflict, description, message) { + const assertConflictOnRows = function(conflict, description) { const isRangeOnRows = function(range, startRow, endRow, rangeName) { - assert( - range.start.row === startRow && range.start.column === 0 && range.end.row === endRow && range.end.column === 0, + assert.isTrue( + range.start.row === startRow && range.end.row === endRow, `expected conflict's ${rangeName} range to cover rows ${startRow} to ${endRow}, but it was ${range}`, ); }; @@ -33,27 +33,37 @@ describe('Conflict', function() { return isRangeOnRows(range, row, row + 1, rangeName); }; - const ourBannerRange = conflict.getSide(OURS).banner.marker.getBufferRange(); + const isPointOnRow = function(range, row, rangeName) { + return isRangeOnRows(range, row, row, rangeName); + }; + + const ourBannerRange = conflict.getSide(OURS).getBannerMarker().getBufferRange(); isRangeOnRow(ourBannerRange, description.ourBannerRow, '"ours" banner'); - const ourSideRange = conflict.getSide(OURS).marker.getBufferRange(); + const ourSideRange = conflict.getSide(OURS).getMarker().getBufferRange(); isRangeOnRows(ourSideRange, description.ourSideRows[0], description.ourSideRows[1], '"ours"'); assert.strictEqual(conflict.getSide(OURS).position, description.ourPosition || TOP, '"ours" in expected position'); - const theirBannerRange = conflict.getSide(THEIRS).banner.marker.getBufferRange(); + const ourBlockRange = conflict.getSide(OURS).getBlockMarker().getBufferRange(); + isPointOnRow(ourBlockRange, description.ourBannerRow, '"ours" block range'); + + const theirBannerRange = conflict.getSide(THEIRS).getBannerMarker().getBufferRange(); isRangeOnRow(theirBannerRange, description.theirBannerRow, '"theirs" banner'); - const theirSideRange = conflict.getSide(THEIRS).marker.getBufferRange(); + const theirSideRange = conflict.getSide(THEIRS).getMarker().getBufferRange(); isRangeOnRows(theirSideRange, description.theirSideRows[0], description.theirSideRows[1], '"theirs"'); assert.strictEqual(conflict.getSide(THEIRS).position, description.theirPosition || BOTTOM, '"theirs" in expected position'); + const theirBlockRange = conflict.getSide(THEIRS).getBlockMarker().getBufferRange(); + isPointOnRow(theirBlockRange, description.theirBannerRow, '"theirs" block range'); + if (description.baseBannerRow || description.baseSideRows) { assert.isNotNull(conflict.getSide(BASE), "expected conflict's base side to be non-null"); - const baseBannerRange = conflict.getSide(BASE).banner.marker.getBufferRange(); + const baseBannerRange = conflict.getSide(BASE).getBannerMarker().getBufferRange(); isRangeOnRow(baseBannerRange, description.baseBannerRow, '"base" banner'); - const baseSideRange = conflict.getSide(BASE).marker.getBufferRange(); + const baseSideRange = conflict.getSide(BASE).getMarker().getBufferRange(); isRangeOnRows(baseSideRange, description.baseSideRows[0], description.baseSideRows[1], '"base"'); assert.strictEqual(conflict.getSide(BASE).position, MIDDLE, '"base" in MIDDLE position'); } else { @@ -277,4 +287,345 @@ end assert.isTrue(conflict.getSeparator().isModified()); }); }); + + describe('contextual block position and CSS class generation', function() { + let editor, conflict; + + describe('from a merge', function() { + beforeEach(async function() { + editor = await editorOnFixture('single-3way-diff.txt'); + conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0]; + }); + + it('accesses the block decoration position', function() { + assert.strictEqual(conflict.getSide(OURS).getBlockPosition(), 'before'); + assert.strictEqual(conflict.getSide(BASE).getBlockPosition(), 'before'); + assert.strictEqual(conflict.getSide(THEIRS).getBlockPosition(), 'after'); + }); + + it('accesses the line decoration CSS class', function() { + assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictOurs'); + assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictBase'); + assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictTheirs'); + }); + + it('accesses the line decoration CSS class when modified', function() { + for (const position of [[5, 1], [3, 1], [1, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictModified'); + assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictModified'); + assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictModified'); + }); + + it('accesses the line decoration CSS class when the banner is modified', function() { + for (const position of [[6, 1], [2, 1], [0, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictModified'); + assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictModified'); + assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictModified'); + }); + + it('accesses the banner CSS class', function() { + assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictOursBanner'); + assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictBaseBanner'); + assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictTheirsBanner'); + }); + + it('accesses the banner CSS class when modified', function() { + for (const position of [[5, 1], [3, 1], [1, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + }); + + it('accesses the banner CSS class when the banner is modified', function() { + for (const position of [[6, 1], [2, 1], [0, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + }); + + it('accesses the block CSS classes', function() { + assert.strictEqual( + conflict.getSide(OURS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictOursBlock github-ConflictTopBlock', + ); + assert.strictEqual( + conflict.getSide(BASE).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock', + ); + assert.strictEqual( + conflict.getSide(THEIRS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictBottomBlock', + ); + }); + + it('accesses the block CSS classes when modified', function() { + for (const position of [[5, 1], [3, 1], [1, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual( + conflict.getSide(OURS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictOursBlock github-ConflictTopBlock github-ConflictModifiedBlock', + ); + assert.strictEqual( + conflict.getSide(BASE).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock github-ConflictModifiedBlock', + ); + assert.strictEqual( + conflict.getSide(THEIRS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictBottomBlock github-ConflictModifiedBlock', + ); + }); + + it('accesses the block CSS classes when the banner is modified', function() { + for (const position of [[6, 1], [2, 1], [0, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual( + conflict.getSide(OURS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictOursBlock github-ConflictTopBlock github-ConflictModifiedBlock', + ); + assert.strictEqual( + conflict.getSide(BASE).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock github-ConflictModifiedBlock', + ); + assert.strictEqual( + conflict.getSide(THEIRS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictBottomBlock github-ConflictModifiedBlock', + ); + }); + }); + + describe('from a rebase', function() { + beforeEach(async function() { + editor = await editorOnFixture('single-3way-diff.txt'); + conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), true)[0]; + }); + + it('accesses the block decoration position', function() { + assert.strictEqual(conflict.getSide(THEIRS).getBlockPosition(), 'before'); + assert.strictEqual(conflict.getSide(BASE).getBlockPosition(), 'before'); + assert.strictEqual(conflict.getSide(OURS).getBlockPosition(), 'after'); + }); + + it('accesses the line decoration CSS class', function() { + assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictTheirs'); + assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictBase'); + assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictOurs'); + }); + + it('accesses the line decoration CSS class when modified', function() { + for (const position of [[5, 1], [3, 1], [1, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictModified'); + assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictModified'); + assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictModified'); + }); + + it('accesses the line decoration CSS class when the banner is modified', function() { + for (const position of [[6, 1], [2, 1], [0, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual(conflict.getSide(THEIRS).getLineCSSClass(), 'github-ConflictModified'); + assert.strictEqual(conflict.getSide(BASE).getLineCSSClass(), 'github-ConflictModified'); + assert.strictEqual(conflict.getSide(OURS).getLineCSSClass(), 'github-ConflictModified'); + }); + + it('accesses the banner CSS class', function() { + assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictTheirsBanner'); + assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictBaseBanner'); + assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictOursBanner'); + }); + + it('accesses the banner CSS class when modified', function() { + for (const position of [[5, 1], [3, 1], [1, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + }); + + it('accesses the banner CSS class when the banner is modified', function() { + for (const position of [[6, 1], [2, 1], [0, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual(conflict.getSide(THEIRS).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + assert.strictEqual(conflict.getSide(BASE).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + assert.strictEqual(conflict.getSide(OURS).getBannerCSSClass(), 'github-ConflictModifiedBanner'); + }); + + it('accesses the block CSS classes', function() { + assert.strictEqual( + conflict.getSide(THEIRS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictTopBlock', + ); + assert.strictEqual( + conflict.getSide(BASE).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock', + ); + assert.strictEqual( + conflict.getSide(OURS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictOursBlock github-ConflictBottomBlock', + ); + }); + + it('accesses the block CSS classes when modified', function() { + for (const position of [[5, 1], [3, 1], [1, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual( + conflict.getSide(THEIRS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictTopBlock github-ConflictModifiedBlock', + ); + assert.strictEqual( + conflict.getSide(BASE).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock github-ConflictModifiedBlock', + ); + assert.strictEqual( + conflict.getSide(OURS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictOursBlock github-ConflictBottomBlock github-ConflictModifiedBlock', + ); + }); + + it('accesses the block CSS classes when the banner is modified', function() { + for (const position of [[6, 1], [2, 1], [0, 1]]) { + editor.setCursorBufferPosition(position); + editor.insertText('change'); + } + + assert.strictEqual( + conflict.getSide(THEIRS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictTheirsBlock github-ConflictTopBlock github-ConflictModifiedBlock', + ); + assert.strictEqual( + conflict.getSide(BASE).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictBaseBlock github-ConflictMiddleBlock github-ConflictModifiedBlock', + ); + assert.strictEqual( + conflict.getSide(OURS).getBlockCSSClasses(), + 'github-ConflictBlock github-ConflictOursBlock github-ConflictBottomBlock github-ConflictModifiedBlock', + ); + }); + }); + }); + + it('accesses a side range that encompasses the banner and content', async function() { + const editor = await editorOnFixture('single-3way-diff.txt'); + const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0]; + + assert.deepEqual(conflict.getSide(OURS).getRange().serialize(), [[0, 0], [2, 0]]); + assert.deepEqual(conflict.getSide(BASE).getRange().serialize(), [[2, 0], [4, 0]]); + assert.deepEqual(conflict.getSide(THEIRS).getRange().serialize(), [[5, 0], [7, 0]]); + }); + + it('determines the inclusion of points', async function() { + const editor = await editorOnFixture('single-3way-diff.txt'); + const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0]; + + assert.isTrue(conflict.getSide(OURS).includesPoint([0, 1])); + assert.isTrue(conflict.getSide(OURS).includesPoint([1, 3])); + assert.isFalse(conflict.getSide(OURS).includesPoint([2, 1])); + }); + + it('detects when a side is empty', async function() { + const editor = await editorOnFixture('single-2way-diff-empty.txt'); + const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0]; + + assert.isFalse(conflict.getSide(OURS).isEmpty()); + assert.isTrue(conflict.getSide(THEIRS).isEmpty()); + }); + + it('reverts a modified Side', async function() { + const editor = await editorOnFixture('single-3way-diff.txt'); + const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0]; + + editor.setCursorBufferPosition([5, 10]); + editor.insertText('MY-CHANGE'); + + assert.isTrue(conflict.getSide(THEIRS).isModified()); + assert.match(editor.getText(), /MY-CHANGE/); + + conflict.getSide(THEIRS).revert(); + + assert.isFalse(conflict.getSide(THEIRS).isModified()); + assert.notMatch(editor.getText(), /MY-CHANGE/); + }); + + it('reverts a modified Side banner', async function() { + const editor = await editorOnFixture('single-3way-diff.txt'); + const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0]; + + editor.setCursorBufferPosition([6, 4]); + editor.insertText('MY-CHANGE'); + + assert.isTrue(conflict.getSide(THEIRS).isBannerModified()); + assert.match(editor.getText(), /MY-CHANGE/); + + conflict.getSide(THEIRS).revertBanner(); + + assert.isFalse(conflict.getSide(THEIRS).isBannerModified()); + assert.notMatch(editor.getText(), /MY-CHANGE/); + }); + + it('deletes a banner', async function() { + const editor = await editorOnFixture('single-3way-diff.txt'); + const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0]; + + assert.match(editor.getText(), /<<<<<<< HEAD/); + conflict.getSide(OURS).deleteBanner(); + assert.notMatch(editor.getText(), /<<<<<<< HEAD/); + }); + + it('deletes a side', async function() { + const editor = await editorOnFixture('single-3way-diff.txt'); + const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0]; + + assert.match(editor.getText(), /your/); + conflict.getSide(THEIRS).delete(); + assert.notMatch(editor.getText(), /your/); + }); + + it('appends text to a side', async function() { + const editor = await editorOnFixture('single-3way-diff.txt'); + const conflict = Conflict.allFromEditor(editor, editor.getDefaultMarkerLayer(), false)[0]; + + assert.notMatch(editor.getText(), /APPENDED/); + conflict.getSide(THEIRS).appendText('APPENDED\n'); + assert.match(editor.getText(), /APPENDED/); + + assert.isTrue(conflict.getSide(THEIRS).isModified()); + assert.strictEqual(conflict.getSide(THEIRS).getText(), 'These are your changes\nAPPENDED\n'); + assert.isFalse(conflict.getSide(THEIRS).isBannerModified()); + }); }); From 00ff5d0838b38a549f81761e05b56f301e10ff27 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 11 Jan 2019 16:36:12 +0100 Subject: [PATCH 1783/4053] add hidden comment test --- test/views/pr-comments-view.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index 3a87f20737..15e2562626 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -118,4 +118,10 @@ describe('PullRequestCommentView', function() { const wrapper = shallow(buildApp({author: null})); assert.isTrue(wrapper.text().includes('someone commented')); }); + + it('hides minimized comment', function() { + const wrapper = shallow(buildApp({isMinimized: true})); + assert.isTrue(wrapper.find('.github-PrComment-hidden').exists()); + assert.isFalse(wrapper.find('.github-PrComment-header').exists()); + }); }); From 94cb39ccb623bbb4d7f23f4f009f5e02c6bb8076 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 11 Jan 2019 11:32:59 -0500 Subject: [PATCH 1784/4053] Full coverage for Decoration --- lib/atom/decoration.js | 10 +++--- test/atom/decoration.test.js | 63 ++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/lib/atom/decoration.js b/lib/atom/decoration.js index cd3cbead27..9da22e42ac 100644 --- a/lib/atom/decoration.js +++ b/lib/atom/decoration.js @@ -64,12 +64,12 @@ class BareDecoration extends React.Component { componentDidUpdate(prevProps) { if (this.props.editorHolder !== prevProps.editorHolder) { this.editorSub.dispose(); - this.editorSub = this.state.editorHolder.observe(this.observeParents); + this.editorSub = this.props.editorHolder.observe(this.observeParents); } if (this.props.decorableHolder !== prevProps.decorableHolder) { this.decorableSub.dispose(); - this.decorableSub = this.state.decorableHolder.observe(this.observeParents); + this.decorableSub = this.props.decorableHolder.observe(this.observeParents); } if ( @@ -95,10 +95,10 @@ class BareDecoration extends React.Component { this.decorationHolder.map(decoration => decoration.destroy()); const editorValid = this.props.editorHolder.map(editor => !editor.isDestroyed()).getOr(false); - const markableValid = this.props.decorableHolder.map(decorable => !decorable.isDestroyed()).getOr(false); + const decorableValid = this.props.decorableHolder.map(decorable => !decorable.isDestroyed()).getOr(false); // Ensure the Marker or MarkerLayer corresponds to the context's TextEditor - const markableMatches = this.props.decorableHolder.map(decorable => this.props.editorHolder.map(editor => { + const decorableMatches = this.props.decorableHolder.map(decorable => this.props.editorHolder.map(editor => { const layer = decorable.layer || decorable; const displayLayer = editor.getMarkerLayer(layer.id); if (!displayLayer) { @@ -110,7 +110,7 @@ class BareDecoration extends React.Component { return true; }).getOr(false)).getOr(false); - if (!editorValid || !markableValid || !markableMatches) { + if (!editorValid || !decorableValid || !decorableMatches) { return; } diff --git a/test/atom/decoration.test.js b/test/atom/decoration.test.js index 2e9fd4fdcd..db187dfdcb 100644 --- a/test/atom/decoration.test.js +++ b/test/atom/decoration.test.js @@ -99,6 +99,69 @@ describe('Decoration', function() { }); }); + describe('when called with changed props', function() { + let wrapper, originalDecoration; + + beforeEach(function() { + const app = ( + + ); + wrapper = mount(app); + + originalDecoration = editor.getLineDecorations({position: 'head', class: 'something'})[0]; + }); + + it('redecorates when a new Editor and Marker are provided', async function() { + const newEditor = await workspace.open(require.resolve('../../package.json')); + const newMarker = newEditor.markBufferRange([[0, 0], [2, 0]]); + + wrapper.setProps({editor: newEditor, decorable: newMarker}); + + assert.isTrue(originalDecoration.isDestroyed()); + assert.lengthOf(editor.getLineDecorations({position: 'head', class: 'something'}), 0); + assert.lengthOf(newEditor.getLineDecorations({position: 'head', class: 'something'}), 1); + }); + + it('redecorates when a new MarkerLayer is provided', function() { + const newLayer = editor.addMarkerLayer(); + + wrapper.setProps({decorable: newLayer, decorateMethod: 'decorateMarkerLayer'}); + + assert.isTrue(originalDecoration.isDestroyed()); + assert.lengthOf(editor.getLineDecorations({position: 'head', class: 'something'}), 0); + + // Turns out Atom doesn't provide any public way to query the markers on a layer. + assert.lengthOf( + Array.from(editor.decorationManager.layerDecorationsByMarkerLayer.get(newLayer)), + 1, + ); + }); + + it('updates decoration properties', function() { + wrapper.setProps({className: 'different'}); + + assert.lengthOf(editor.getLineDecorations({position: 'head', class: 'something'}), 0); + assert.lengthOf(editor.getLineDecorations({position: 'head', class: 'different'}), 1); + assert.isFalse(originalDecoration.isDestroyed()); + assert.strictEqual(originalDecoration.getProperties().class, 'different'); + }); + + it('does not redecorate when the decorable is on the wrong TextEditor', async function() { + const newEditor = await workspace.open(require.resolve('../../package.json')); + + wrapper.setProps({editor: newEditor}); + + assert.isTrue(originalDecoration.isDestroyed()); + assert.lengthOf(editor.getLineDecorations({}), 0); + }); + }); + it('destroys its decoration on unmount', function() { const app = ( Date: Fri, 11 Jan 2019 13:59:25 -0500 Subject: [PATCH 1785/4053] Cover EditorConflictController --- lib/controllers/editor-conflict-controller.js | 9 +-- .../editor-conflict-controller.test.js | 71 +++++++++++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/lib/controllers/editor-conflict-controller.js b/lib/controllers/editor-conflict-controller.js index 728cfc5021..79f7309512 100644 --- a/lib/controllers/editor-conflict-controller.js +++ b/lib/controllers/editor-conflict-controller.js @@ -18,11 +18,7 @@ export default class EditorConflictController extends React.Component { commandRegistry: PropTypes.object.isRequired, resolutionProgress: PropTypes.object.isRequired, isRebase: PropTypes.bool.isRequired, - refreshResolutionProgress: PropTypes.func, - } - - static defaultProps = { - refreshResolutionProgress: () => {}, + refreshResolutionProgress: PropTypes.func.isRequired, } constructor(props, context) { @@ -49,7 +45,6 @@ export default class EditorConflictController extends React.Component { const buffer = this.props.editor.getBuffer(); this.subscriptions.add( - this.props.editor.onDidStopChanging(() => this.forceUpdate()), this.props.editor.onDidDestroy(() => this.props.refreshResolutionProgress(this.props.editor.getPath())), buffer.onDidReload(() => this.reparseConflicts()), ); @@ -171,7 +166,7 @@ export default class EditorConflictController extends React.Component { } dismissConflicts(conflicts) { - this.setState((prevState, props) => { + this.setState(prevState => { const {added} = compareSets(new Set(conflicts), prevState.conflicts); return {conflicts: added}; }); diff --git a/test/controllers/editor-conflict-controller.test.js b/test/controllers/editor-conflict-controller.test.js index 61ef4b376c..872acb70f2 100644 --- a/test/controllers/editor-conflict-controller.test.js +++ b/test/controllers/editor-conflict-controller.test.js @@ -166,6 +166,49 @@ describe('EditorConflictController', function() { assert.strictEqual(conflicts[2].getChosenSide(), conflicts[2].getSide(THEIRS)); }); + it('resolves multiple conflicts as "ours"', function() { + assert.isFalse(conflicts[0].isResolved()); + assert.isFalse(conflicts[1].isResolved()); + assert.isFalse(conflicts[2].isResolved()); + + editor.setCursorBufferPosition([8, 3]); // On "Your changes" + editor.addCursorAtBufferPosition([11, 2]); // On "Text in between 0 and 1." + editor.addCursorAtBufferPosition([14, 5]); // On "My middle changes" + editor.addCursorAtBufferPosition([15, 0]); // On "=======" + commandRegistry.dispatch(editorView, 'github:resolve-as-ours'); + + assert.isTrue(conflicts[0].isResolved()); + assert.strictEqual(conflicts[0].getChosenSide(), conflicts[0].getSide(OURS)); + assert.deepEqual(conflicts[0].getUnchosenSides(), [conflicts[0].getSide(THEIRS)]); + + assert.isTrue(conflicts[1].isResolved()); + assert.strictEqual(conflicts[1].getChosenSide(), conflicts[1].getSide(OURS)); + assert.deepEqual(conflicts[1].getUnchosenSides(), [conflicts[1].getSide(THEIRS)]); + + assert.isFalse(conflicts[2].isResolved()); + }); + + it('resolves multiple conflicts as "theirs"', function() { + assert.isFalse(conflicts[0].isResolved()); + assert.isFalse(conflicts[1].isResolved()); + assert.isFalse(conflicts[2].isResolved()); + + editor.setCursorBufferPosition([8, 3]); // On "Your changes" + editor.addCursorAtBufferPosition([11, 2]); // On "Text in between 0 and 1." + editor.addCursorAtBufferPosition([22, 5]); // On "More of my changes" + commandRegistry.dispatch(editorView, 'github:resolve-as-theirs'); + + assert.isTrue(conflicts[0].isResolved()); + assert.strictEqual(conflicts[0].getChosenSide(), conflicts[0].getSide(THEIRS)); + assert.deepEqual(conflicts[0].getUnchosenSides(), [conflicts[0].getSide(OURS)]); + + assert.isFalse(conflicts[1].isResolved()); + + assert.isTrue(conflicts[2].isResolved()); + assert.strictEqual(conflicts[2].getChosenSide(), conflicts[2].getSide(THEIRS)); + assert.deepEqual(conflicts[2].getUnchosenSides(), [conflicts[2].getSide(OURS)]); + }); + it('disregards conflicts with cursors on both sides', function() { editor.setCursorBufferPosition([6, 3]); // On "Multi-line even" editor.addCursorAtBufferPosition([14, 1]); // On "My middle changes" @@ -316,6 +359,26 @@ describe('EditorConflictController', function() { await assert.async.isTrue(refreshResolutionProgress.calledWith(fixtureFile)); }); + + it('performs a resolution from the context menu', function() { + const conflict = conflicts[1]; + assert.isFalse(conflict.isResolved()); + + wrapper.find('ConflictController').at(1).prop('resolveAsSequence')([OURS]); + + assert.isTrue(conflict.isResolved()); + assert.strictEqual(conflict.getChosenSide(), conflict.getSide(OURS)); + }); + + it('dismisses a conflict from the context menu', function() { + const conflict = conflicts[2]; + + wrapper.find('ConflictController').at(2).prop('dismiss')(); + wrapper.update(); + + assert.lengthOf(wrapper.find(ConflictController), 2); + assert.isFalse(wrapper.find(ConflictController).someWhere(cc => cc.prop('conflict') === conflict)); + }); }); describe('on a file with 3-way diff markers', function() { @@ -442,4 +505,12 @@ describe('EditorConflictController', function() { assert.equal(editor.getText(), 'These are my changes\nThese are your changes\n\nPast the end\n'); }); }); + + it('cleans up its subscriptions when unmounting', async function() { + await useFixture('triple-2way-diff.txt'); + wrapper.unmount(); + + editor.destroy(); + assert.isFalse(refreshResolutionProgress.called); + }); }); From 9f5c577d57b9bac3b0f61af7b205653241a4eedf Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 11 Jan 2019 11:50:59 -0800 Subject: [PATCH 1786/4053] :art: comment. --- lib/controllers/pr-reviews-controller.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index d7c3acd62c..d10839eb69 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -122,12 +122,15 @@ export default class PullRequestReviewsController extends React.Component { if (!comment.replyTo) { state[comment.id] = [comment]; } else { - // TODO: look at this more closely... - // When comment being replied to is outdated...?? Not 100% sure... - // Why would we even get an outdated comment or a response to one here? // Ran into this error when viewing files for https://github.com/numpy/numpy/pull/9998 // for comment MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE1MzA1NTUzMw, - // who's replyTo comment is an outdated comment + // who's replyTo comment does not exist. + // Not sure how we'd get into this state -- tried replying to outdated, + // hidden, deleted, and resolved comments but none of those conditions + // got us here. + // It may be that this only affects older pull requests, before something + // changed with oudated comment behavior. + // anyhow, do this check and move on with our lives. if (!state[comment.replyTo.id]) { state[comment.id] = [comment]; } else { From a8a5d6168d03fb091f977f3eff569c889613314b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 11 Jan 2019 14:55:03 -0500 Subject: [PATCH 1787/4053] Coverage for the Present state --- lib/models/repository-states/present.js | 10 +-- test/models/repository.test.js | 81 +++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 830c18280e..8d93ffb7b8 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -610,9 +610,6 @@ export default class Present extends State { const bundle = await this.git().getStatusBundle(); const results = await this.formatChangedFiles(bundle); results.branch = bundle.branch; - if (!results.branch.aheadBehind) { - results.branch.aheadBehind = {ahead: null, behind: null}; - } return results; } catch (err) { if (err instanceof LargeRepoError) { @@ -855,7 +852,7 @@ export default class Present extends State { // Direct blob access getBlobContents(sha) { - return this.cache.getOrSet(Keys.blob(sha), () => { + return this.cache.getOrSet(Keys.blob.oneWith(sha), () => { return this.git().getBlobContents(sha); }); } @@ -876,6 +873,7 @@ export default class Present extends State { // Cache + /* istanbul ignore next */ getCache() { return this.cache; } @@ -974,6 +972,7 @@ class Cache { this.didUpdate(); } + /* istanbul ignore next */ [Symbol.iterator]() { return this.storage[Symbol.iterator](); } @@ -988,6 +987,7 @@ class Cache { this.emitter.emit('did-update'); } + /* istanbul ignore next */ onDidUpdate(callback) { return this.emitter.on('did-update', callback); } @@ -1025,6 +1025,7 @@ class CacheKey { } } + /* istanbul ignore next */ toString() { return `CacheKey(${this.primary})`; } @@ -1041,6 +1042,7 @@ class GroupKey { } } + /* istanbul ignore next */ toString() { return `GroupKey(${this.group})`; } diff --git a/test/models/repository.test.js b/test/models/repository.test.js index cfbf9df9b0..e802d8cdd8 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -7,6 +7,7 @@ import isEqualWith from 'lodash.isequalwith'; import Repository from '../../lib/models/repository'; import CompositeGitStrategy from '../../lib/composite-git-strategy'; +import {LargeRepoError} from '../../lib/git-shell-out-strategy'; import {nullCommit} from '../../lib/models/commit'; import {nullOperationStates} from '../../lib/models/operation-states'; import Author from '../../lib/models/author'; @@ -444,6 +445,60 @@ describe('Repository', function() { }); }); + describe('getStatusBundle', function() { + it('transitions to the TooLarge state and returns empty status when too large', async function() { + const workdir = await cloneRepository(); + const repository = new Repository(workdir); + await repository.getLoadPromise(); + + sinon.stub(repository.git, 'getStatusBundle').rejects(new LargeRepoError()); + + const result = await repository.getStatusBundle(); + + assert.isTrue(repository.isInState('TooLarge')); + assert.deepEqual(result.branch, {}); + assert.deepEqual(result.stagedFiles, {}); + assert.deepEqual(result.unstagedFiles, {}); + assert.deepEqual(result.mergeConflictFiles, {}); + }); + + it('propagates unrecognized git errors', async function() { + const workdir = await cloneRepository(); + const repository = new Repository(workdir); + await repository.getLoadPromise(); + + sinon.stub(repository.git, 'getStatusBundle').rejects(new Error('oh no')); + + await assert.isRejected(repository.getStatusBundle(), /oh no/); + }); + + it('post-processes renamed files to an addition and a deletion', async function() { + const workdir = await cloneRepository(); + const repository = new Repository(workdir); + await repository.getLoadPromise(); + + sinon.stub(repository.git, 'getStatusBundle').resolves({ + changedEntries: [], + untrackedEntries: [], + renamedEntries: [ + {stagedStatus: 'R', origFilePath: 'from0.txt', filePath: 'to0.txt'}, + {unstagedStatus: 'R', origFilePath: 'from1.txt', filePath: 'to1.txt'}, + {stagedStatus: 'C', filePath: 'c2.txt'}, + {unstagedStatus: 'C', filePath: 'c3.txt'}, + ], + unmergedEntries: [], + }); + + const result = await repository.getStatusBundle(); + assert.strictEqual(result.stagedFiles['from0.txt'], 'deleted'); + assert.strictEqual(result.stagedFiles['to0.txt'], 'added'); + assert.strictEqual(result.unstagedFiles['from1.txt'], 'deleted'); + assert.strictEqual(result.unstagedFiles['to1.txt'], 'added'); + assert.strictEqual(result.stagedFiles['c2.txt'], 'added'); + assert.strictEqual(result.unstagedFiles['c3.txt'], 'added'); + }); + }); + describe('getFilePatchForPath', function() { it('returns cached MultiFilePatch objects if they exist', async function() { const workingDirPath = await cloneRepository('multiple-commits'); @@ -931,6 +986,20 @@ describe('Repository', function() { }); }); + describe('unsetConfig', function() { + it('unsets a git config option', async function() { + const workingDirPath = await cloneRepository('three-files'); + const repository = new Repository(workingDirPath); + await repository.getLoadPromise(); + + await repository.setConfig('some.key', 'value'); + assert.strictEqual(await repository.getConfig('some.key'), 'value'); + + await repository.unsetConfig('some.key'); + assert.isNull(await repository.getConfig('some.key')); + }); + }); + describe('getCommitter', function() { it('returns user name and email if they exist', async function() { const workingDirPath = await cloneRepository('three-files'); @@ -1435,6 +1504,18 @@ describe('Repository', function() { }); }); + describe('getBlobContents(sha)', function() { + it('returns blob contents for sha', async function() { + const workingDirPath = await cloneRepository('three-files'); + const repository = new Repository(workingDirPath); + await repository.getLoadPromise(); + + const sha = await repository.createBlob({stdin: 'aa\nbb\ncc\n'}); + const contents = await repository.getBlobContents(sha); + assert.strictEqual(contents, 'aa\nbb\ncc\n'); + }); + }); + describe('discardWorkDirChangesForPaths()', function() { it('can discard working directory changes in modified files', async function() { const workingDirPath = await cloneRepository('three-files'); From 0838da6ce1332af8f9ca531e5b26a3b1e8f89bd8 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 11 Jan 2019 13:15:48 -0800 Subject: [PATCH 1788/4053] strip commented lines instead of using the verbatim flag --- lib/git-shell-out-strategy.js | 9 ++++++--- test/git-strategies.test.js | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 40cade6d87..df4b160685 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -506,12 +506,15 @@ export default class GitShellOutStrategy { msg = rawMessage; } - // if commit template is used, that will not play nicely with `verbatim` - // because we want to strip commented lines from templates. + // if commit template is used, strip commented lines from commit + // to be consistent with command line git. const template = await this.fetchCommitMessageTemplate(); + if (template) { + msg = msg.split('\n').filter(line => !line.startsWith('#')).join('\n'); + } // Determine the cleanup mode. - if (verbatim && !template) { + if (verbatim) { args.push('--cleanup=verbatim'); } else { const configured = await this.getConfig('commit.cleanup'); diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 9d9583b481..c1634d6bf1 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1036,7 +1036,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; 'and things', ].join('\n')); }); - it('ignores verbatim flag if commit template is used', async function() { + it('strips commented lines if commit template is used', async function() { const workingDirPath = await cloneRepository('three-files'); const git = createTestStrategy(workingDirPath); const templateText = '# this line should be stripped'; From 3cde57a89051170d831f56c948561ef6f37e5f73 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 11 Jan 2019 15:19:13 -0800 Subject: [PATCH 1789/4053] fix GPG signing tests --- test/git-strategies.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index c1634d6bf1..f5e6dcf894 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1186,6 +1186,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; beforeEach(async function() { const workingDirPath = await cloneRepository('multiple-commits'); git = createTestStrategy(workingDirPath); + sinon.stub(git, 'fetchCommitMessageTemplate').returns(null); }); const operations = [ From 85e502ea53bc8238cce04647ac91fe3904f904bb Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 11 Jan 2019 16:32:10 -0800 Subject: [PATCH 1790/4053] fetch commit author name from git --- lib/git-shell-out-strategy.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index d8ad54d16a..5f6a1d9ce8 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -700,12 +700,13 @@ export default class GitShellOutStrategy { // %x00 - null byte // %H - commit SHA // %ae - author email + // %an = author full name // %at - timestamp, UNIX timestamp // %s - subject // %b - body const args = [ 'log', - '--pretty=format:%H%x00%ae%x00%at%x00%s%x00%b%x00', + '--pretty=format:%H%x00%ae%x00%an%x00%at%x00%s%x00%b%x00', '--no-abbrev-commit', '--no-prefix', '--no-ext-diff', @@ -733,13 +734,14 @@ export default class GitShellOutStrategy { } const fields = output.trim().split('\0'); + console.log('fields', fields) const commits = []; - for (let i = 0; i < fields.length; i += 6) { - const body = fields[i + 4].trim(); + for (let i = 0; i < fields.length; i += 7) { + const body = fields[i + 5].trim(); let patch = []; if (includePatch) { - const diffs = fields[i + 5]; + const diffs = fields[i + 6]; patch = parseDiff(diffs.trim()); } @@ -748,8 +750,9 @@ export default class GitShellOutStrategy { commits.push({ sha: fields[i] && fields[i].trim(), authorEmail: fields[i + 1] && fields[i + 1].trim(), - authorDate: parseInt(fields[i + 2], 10), - messageSubject: fields[i + 3], + authorName: fields[i + 2] && fields[i + 2].trim(), + authorDate: parseInt(fields[i + 3], 10), + messageSubject: fields[i + 4], messageBody, coAuthors, unbornRef: false, From da316e835017855f415ad701c457a3920135bb66 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 11 Jan 2019 16:32:37 -0800 Subject: [PATCH 1791/4053] render author name in `CommitDetailView` --- lib/views/commit-detail-view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index 304efe8672..d0fc77b578 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -56,6 +56,7 @@ export default class CommitDetailView extends React.Component {

    {emojify(commit.getMessageSubject())}

    +
    {this.props.commit.authorName}
    {this.renderAuthors()} From b30ca92e57081d5c7e9b09b15e91f540f3ef3f25 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 11 Jan 2019 16:33:38 -0800 Subject: [PATCH 1792/4053] models and builders, oh my! --- lib/models/commit.js | 7 ++++++- test/builder/commit.js | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/models/commit.js b/lib/models/commit.js index 281d909367..b0edb038d8 100644 --- a/lib/models/commit.js +++ b/lib/models/commit.js @@ -16,9 +16,10 @@ export default class Commit { return new Commit({unbornRef: UNBORN}); } - constructor({sha, authorEmail, coAuthors, authorDate, messageSubject, messageBody, unbornRef, patch}) { + constructor({sha, authorEmail, authorName, coAuthors, authorDate, messageSubject, messageBody, unbornRef, patch}) { this.sha = sha; this.authorEmail = authorEmail; + this.authorName = authorName; this.coAuthors = coAuthors || []; this.authorDate = authorDate; this.messageSubject = messageSubject; @@ -36,6 +37,10 @@ export default class Commit { return this.authorEmail; } + getAuthorName() { + return this.authorName; + } + getAuthorDate() { return this.authorDate; } diff --git a/test/builder/commit.js b/test/builder/commit.js index d9dd960bb7..aaf0b78093 100644 --- a/test/builder/commit.js +++ b/test/builder/commit.js @@ -7,6 +7,7 @@ class CommitBuilder { constructor() { this._sha = '0123456789abcdefghij0123456789abcdefghij'; this._authorEmail = 'default@email.com'; + this._authorName = 'Tilde Ann Thurium' this._authorDate = moment('2018-11-28T12:00:00', moment.ISO_8601).unix(); this._coAuthors = []; this._messageSubject = 'subject'; @@ -25,6 +26,11 @@ class CommitBuilder { return this; } + authorName(newName) { + this._authorName = newName; + return this; + } + authorDate(timestamp) { this._authorDate = timestamp; return this; @@ -56,6 +62,7 @@ class CommitBuilder { const commit = new Commit({ sha: this._sha, authorEmail: this._authorEmail, + authorName: thus._authorName, authorDate: this._authorDate, coAuthors: this._coAuthors, messageSubject: this._messageSubject, From c27ec98a6da1e8ec947fcb976231b3130e7d17bf Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 11 Jan 2019 16:36:12 -0800 Subject: [PATCH 1793/4053] :shirt: --- lib/git-shell-out-strategy.js | 1 - test/builder/commit.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 5f6a1d9ce8..4f18e1b5fa 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -734,7 +734,6 @@ export default class GitShellOutStrategy { } const fields = output.trim().split('\0'); - console.log('fields', fields) const commits = []; for (let i = 0; i < fields.length; i += 7) { diff --git a/test/builder/commit.js b/test/builder/commit.js index aaf0b78093..43898bf6fc 100644 --- a/test/builder/commit.js +++ b/test/builder/commit.js @@ -7,7 +7,7 @@ class CommitBuilder { constructor() { this._sha = '0123456789abcdefghij0123456789abcdefghij'; this._authorEmail = 'default@email.com'; - this._authorName = 'Tilde Ann Thurium' + this._authorName = 'Tilde Ann Thurium'; this._authorDate = moment('2018-11-28T12:00:00', moment.ISO_8601).unix(); this._coAuthors = []; this._messageSubject = 'subject'; @@ -62,7 +62,7 @@ class CommitBuilder { const commit = new Commit({ sha: this._sha, authorEmail: this._authorEmail, - authorName: thus._authorName, + authorName: this._authorName, authorDate: this._authorDate, coAuthors: this._coAuthors, messageSubject: this._messageSubject, From e0a49666ed74287e273e4c153ac7710d0eb00c28 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 11 Jan 2019 16:55:37 -0800 Subject: [PATCH 1794/4053] fix git-strategies tests --- test/git-strategies.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 6dbd031fdf..0d65c7c993 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -188,6 +188,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; assert.deepEqual(commits[0], { sha: '90b17a8e3fa0218f42afc1dd24c9003e285f4a82', authorEmail: 'kuychaco@github.com', + authorName: 'Katrina Uychaco', authorDate: 1471113656, messageSubject: 'third commit', messageBody: '', @@ -198,6 +199,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; assert.deepEqual(commits[1], { sha: '18920c900bfa6e4844853e7e246607a31c3e2e8c', authorEmail: 'kuychaco@github.com', + authorName: 'Katrina Uychaco', authorDate: 1471113642, messageSubject: 'second commit', messageBody: '', @@ -208,6 +210,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; assert.deepEqual(commits[2], { sha: '46c0d7179fc4e348c3340ff5e7957b9c7d89c07f', authorEmail: 'kuychaco@github.com', + authorName: 'Katrina Uychaco', authorDate: 1471113625, messageSubject: 'first commit', messageBody: '', From a8bea5f424eb82f0bd8337c95ede19c8adc8a75e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 14 Jan 2019 19:45:59 +0100 Subject: [PATCH 1795/4053] when diff is decided to be "too large", we skip hunk building and return a placeholder filepatch. --- lib/models/patch/builder.js | 32 +++++++++++++++++++++++++++++--- lib/models/patch/file-patch.js | 5 +++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index e8e18982a7..511d15c5a6 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -10,6 +10,8 @@ import MultiFilePatch from './multi-file-patch'; export function buildFilePatch(diffs) { const layeredBuffer = initializeBuffer(); + isDiffLarge(diffs); + let filePatch; if (diffs.length === 0) { filePatch = emptyDiffFilePatch(); @@ -80,8 +82,6 @@ function singleDiffFilePatch(diff, layeredBuffer) { const wasSymlink = diff.oldMode === File.modes.SYMLINK; const isSymlink = diff.newMode === File.modes.SYMLINK; - const [hunks, patchMarker] = buildHunks(diff, layeredBuffer); - let oldSymlink = null; let newSymlink = null; if (wasSymlink && !isSymlink) { @@ -99,6 +99,12 @@ function singleDiffFilePatch(diff, layeredBuffer) { const newFile = diff.newPath !== null || diff.newMode !== null ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink}) : nullFile; + + if (isDiffLarge([diff])) { + return FilePatch.createPlaceholder(oldFile, newFile); + } + + const [hunks, patchMarker] = buildHunks(diff, layeredBuffer); const patch = new Patch({status: diff.status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); return new FilePatch(oldFile, newFile, patch); @@ -114,7 +120,6 @@ function dualDiffFilePatch(diff1, diff2, layeredBuffer) { contentChangeDiff = diff1; } - const [hunks, patchMarker] = buildHunks(contentChangeDiff, layeredBuffer); const filePath = contentChangeDiff.oldPath || contentChangeDiff.newPath; const symlink = modeChangeDiff.hunks[0].lines[0].slice(1); @@ -140,6 +145,12 @@ function dualDiffFilePatch(diff1, diff2, layeredBuffer) { const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink}); const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink}); + + if (isDiffLarge([diff1, diff2])) { + return FilePatch.createPlaceholder(oldFile, newFile); + } + + const [hunks, patchMarker] = buildHunks(contentChangeDiff, layeredBuffer); const patch = new Patch({status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); return new FilePatch(oldFile, newFile, patch); @@ -252,3 +263,18 @@ function buildHunks(diff, {buffer, layers}) { return [hunks, patchMarker]; } + +function isDiffLarge(diffs) { + const DIFF_BYTE_THRESHOLD = 32768; + + const size = diffs.reduce((diffSizeCounter, diff) => { + return diffSizeCounter + diff.hunks.reduce((hunkSizeCounter, hunk) => { + return hunkSizeCounter + hunk.lines.reduce((lineSizeCounter, line) => { + return lineSizeCounter + Buffer.byteLength(line, 'utf8'); + }, 0); + }, 0); + }, 0); + + console.log(diffs, `size: ${size}`); + return size > DIFF_BYTE_THRESHOLD; +} diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index c40bd48dd3..9d3c0c3ce9 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -7,6 +7,11 @@ export default class FilePatch { return new this(nullFile, nullFile, Patch.createNull()); } + // TODO: explain what this is for + static createPlaceholder(oldFile, newFile) { + return new this(oldFile, newFile, Patch.createNull()); + } + constructor(oldFile, newFile, patch) { this.oldFile = oldFile; this.newFile = newFile; From e2862ea0515ae40c361fb466b2401006b370d77a Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 14 Jan 2019 20:49:00 +0100 Subject: [PATCH 1796/4053] visually display the diff gate --- lib/models/patch/builder.js | 2 -- lib/models/patch/file-patch.js | 5 +++++ lib/views/multi-file-patch-view.js | 13 +++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 511d15c5a6..b34ae9c3cd 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -10,8 +10,6 @@ import MultiFilePatch from './multi-file-patch'; export function buildFilePatch(diffs) { const layeredBuffer = initializeBuffer(); - isDiffLarge(diffs); - let filePatch; if (diffs.length === 0) { filePatch = emptyDiffFilePatch(); diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 9d3c0c3ce9..b70fbfc3f2 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -22,6 +22,11 @@ export default class FilePatch { return this.oldFile.isPresent() || this.newFile.isPresent() || this.patch.isPresent(); } + isLarge() { + // TODO: fix this + return (this.oldFile.isPresent() || this.newFile.isPresent()) && !this.patch.isPresent(); + } + getOldFile() { return this.oldFile; } diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 9d0655b770..a8c50824ec 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -353,11 +353,24 @@ export default class MultiFilePatchView extends React.Component { + {filePatch.isLarge() && this.renderDiffGate(filePatch)} + {this.renderHunkHeaders(filePatch)} ); } + renderDiffGate(filePatch) { + // TODO: startRange is incorrect here + return ( + + +

    Diff is too large to be displayed.

    +
    +
    + ); + } + renderExecutableModeChangeMeta(filePatch) { if (!filePatch.didChangeExecutableMode()) { return null; From 44f48c5b92140b72aaff8e17624e955df420c91b Mon Sep 17 00:00:00 2001 From: annthurium Date: Mon, 14 Jan 2019 13:09:52 -0800 Subject: [PATCH 1797/4053] test for authorName on commit model --- lib/models/commit.js | 3 ++- test/models/commit.test.js | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/models/commit.js b/lib/models/commit.js index b0edb038d8..81d681d14b 100644 --- a/lib/models/commit.js +++ b/lib/models/commit.js @@ -149,7 +149,8 @@ export default class Commit { isEqual(other) { // Directly comparable properties - for (const property of ['sha', 'authorEmail', 'authorDate', 'messageSubject', 'messageBody', 'unbornRef']) { + const properties = ['sha', 'authorEmail', 'authorDate', 'messageSubject', 'messageBody', 'unbornRef', 'authorName']; + for (const property of properties) { if (this[property] !== other[property]) { return false; } diff --git a/test/models/commit.test.js b/test/models/commit.test.js index 778b89884f..eba27394aa 100644 --- a/test/models/commit.test.js +++ b/test/models/commit.test.js @@ -178,6 +178,13 @@ describe('Commit', function() { assert.isFalse(a.isEqual(b)); }); + it('returns false if author differs', function() { + const a = commitBuilder().authorName('Tilde Ann Thurium').build(); + + const b = commitBuilder().authorName('Vanessa Yuen').build(); + assert.isFalse(a.isEqual(b)); + }); + it('returns false if a co-author differs', function() { const a = commitBuilder().addCoAuthor('me', 'me@email.com').build(); From ba37d1ae3a725359239afb83e2ef3f170a38b23a Mon Sep 17 00:00:00 2001 From: annthurium Date: Mon, 14 Jan 2019 13:37:10 -0800 Subject: [PATCH 1798/4053] add test for authorName in CommitDetailView --- lib/views/commit-detail-view.js | 2 +- test/views/commit-detail-view.test.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index d0fc77b578..aae2457461 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -56,7 +56,7 @@ export default class CommitDetailView extends React.Component {

    {emojify(commit.getMessageSubject())}

    -
    {this.props.commit.authorName}
    +
    {this.props.commit.authorName}
    {this.renderAuthors()} diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js index 29434b3135..19d619cb8a 100644 --- a/test/views/commit-detail-view.test.js +++ b/test/views/commit-detail-view.test.js @@ -62,6 +62,7 @@ describe('CommitDetailView', function() { const commit = commitBuilder() .sha('420') .authorEmail('very@nice.com') + .authorName('Forthe Win') .authorDate(moment().subtract(2, 'days').unix()) .messageSubject('subject') .messageBody('body') @@ -69,6 +70,7 @@ describe('CommitDetailView', function() { .build(); const wrapper = shallow(buildApp({commit})); + assert.strictEqual(wrapper.find('.github-CommitDetailView-authorName').text(), 'Forthe Win'); assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'body'); assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); From c34440f126ee15938f0448f6b41d5a249cb0c3b1 Mon Sep 17 00:00:00 2001 From: annthurium Date: Mon, 14 Jan 2019 13:50:44 -0800 Subject: [PATCH 1799/4053] fix unrelated commented out assertion --- test/views/commit-detail-view.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js index 19d619cb8a..4ea5988430 100644 --- a/test/views/commit-detail-view.test.js +++ b/test/views/commit-detail-view.test.js @@ -75,7 +75,7 @@ describe('CommitDetailView', function() { assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'body'); assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); - // assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), 'https://github.com/atom/github/commit/420'); assert.strictEqual( wrapper.find('img.github-RecentCommit-avatar').prop('src'), 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32', From e217687cc859f6c3ba8b172e69b5f0e30c7f2663 Mon Sep 17 00:00:00 2001 From: annthurium Date: Mon, 14 Jan 2019 14:40:52 -0800 Subject: [PATCH 1800/4053] add test for getter to make codecov happy --- test/models/commit.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/models/commit.test.js b/test/models/commit.test.js index eba27394aa..1e1fc305bf 100644 --- a/test/models/commit.test.js +++ b/test/models/commit.test.js @@ -146,6 +146,12 @@ describe('Commit', function() { }); }); + it('returns the author name', function() { + const authorName = 'Tilde Ann Thurium'; + const commit = commitBuilder().authorName(authorName).build(); + assert.strictEqual(commit.getAuthorName(), authorName) + }); + describe('isEqual()', function() { it('returns true when commits are identical', function() { const a = commitBuilder() From 35948ae6ad23bd1b56473a274096e393bfa08f23 Mon Sep 17 00:00:00 2001 From: annthurium Date: Mon, 14 Jan 2019 15:38:14 -0800 Subject: [PATCH 1801/4053] :shirt: --- test/models/commit.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/models/commit.test.js b/test/models/commit.test.js index 1e1fc305bf..d4c540b1e5 100644 --- a/test/models/commit.test.js +++ b/test/models/commit.test.js @@ -147,9 +147,9 @@ describe('Commit', function() { }); it('returns the author name', function() { - const authorName = 'Tilde Ann Thurium'; - const commit = commitBuilder().authorName(authorName).build(); - assert.strictEqual(commit.getAuthorName(), authorName) + const authorName = 'Tilde Ann Thurium'; + const commit = commitBuilder().authorName(authorName).build(); + assert.strictEqual(commit.getAuthorName(), authorName); }); describe('isEqual()', function() { From 63c5f99d895a5f98d789d444d21f411d62dc444f Mon Sep 17 00:00:00 2001 From: annthurium Date: Tue, 15 Jan 2019 13:28:54 -0800 Subject: [PATCH 1802/4053] show name instead of email address --- lib/views/commit-detail-view.js | 7 +++---- test/views/commit-detail-view.test.js | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index aae2457461..7f8975c339 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -56,7 +56,6 @@ export default class CommitDetailView extends React.Component {

    {emojify(commit.getMessageSubject())}

    -
    {this.props.commit.authorName}
    {this.renderAuthors()} @@ -132,11 +131,11 @@ export default class CommitDetailView extends React.Component { const commit = this.props.commit; const coAuthorCount = commit.getCoAuthors().length; if (coAuthorCount === 0) { - return commit.getAuthorEmail(); + return commit.getAuthorName(); } else if (coAuthorCount === 1) { - return `${commit.getAuthorEmail()} and ${commit.getCoAuthors()[0].email}`; + return `${commit.getAuthorName()} and ${commit.getCoAuthors()[0].name}`; } else { - return `${commit.getAuthorEmail()} and ${coAuthorCount} others`; + return `${commit.getAuthorName()} and ${coAuthorCount} others`; } } diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js index 4ea5988430..4264576bc9 100644 --- a/test/views/commit-detail-view.test.js +++ b/test/views/commit-detail-view.test.js @@ -10,7 +10,7 @@ import Remote, {nullRemote} from '../../lib/models/remote'; import {cloneRepository, buildRepository} from '../helpers'; import {commitBuilder} from '../builder/commit'; -describe('CommitDetailView', function() { +describe.only('CommitDetailView', function() { let repository, atomEnv; beforeEach(async function() { @@ -70,10 +70,9 @@ describe('CommitDetailView', function() { .build(); const wrapper = shallow(buildApp({commit})); - assert.strictEqual(wrapper.find('.github-CommitDetailView-authorName').text(), 'Forthe Win'); assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'body'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'Forthe Win committed 2 days ago'); assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), 'https://github.com/atom/github/commit/420'); assert.strictEqual( @@ -162,33 +161,34 @@ describe('CommitDetailView', function() { describe('when there are no co-authors', function() { it('returns only the author', function() { const commit = commitBuilder() - .authorEmail('blaze@it.com') + .authorName('Steven Universe') + .authorEmail('steven@universe.com') .build(); const wrapper = shallow(buildApp({commit})); - assert.strictEqual(wrapper.instance().getAuthorInfo(), 'blaze@it.com'); + assert.strictEqual(wrapper.instance().getAuthorInfo(), 'Steven Universe'); }); }); describe('when there is one co-author', function() { it('returns author and the co-author', function() { const commit = commitBuilder() - .authorEmail('blaze@it.com') - .addCoAuthor('two', 'two@coauthor.com') + .authorName('Ruby') + .addCoAuthor('Sapphire', 'sapphire@thecrystalgems.party') .build(); const wrapper = shallow(buildApp({commit})); - assert.strictEqual(wrapper.instance().getAuthorInfo(), 'blaze@it.com and two@coauthor.com'); + assert.strictEqual(wrapper.instance().getAuthorInfo(), 'Ruby and Sapphire'); }); }); describe('when there is more than one co-author', function() { it('returns the author and number of co-authors', function() { const commit = commitBuilder() - .authorEmail('blaze@it.com') - .addCoAuthor('two', 'two@coauthor.com') - .addCoAuthor('three', 'three@coauthor.com') + .authorName('Amethyst') + .addCoAuthor('Peridot', 'peri@youclods.horse') + .addCoAuthor('Pearl', 'p@pinkhair.club') .build(); const wrapper = shallow(buildApp({commit})); - assert.strictEqual(wrapper.instance().getAuthorInfo(), 'blaze@it.com and 2 others'); + assert.strictEqual(wrapper.instance().getAuthorInfo(), 'Amethyst and 2 others'); }); }); }); From eff754a9eb74ae85ee590079e5ef5cf4084602a8 Mon Sep 17 00:00:00 2001 From: annthurium Date: Tue, 15 Jan 2019 13:29:25 -0800 Subject: [PATCH 1803/4053] :shirt: againnn --- lib/views/commit-detail-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index 7f8975c339..eada68313f 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -131,7 +131,7 @@ export default class CommitDetailView extends React.Component { const commit = this.props.commit; const coAuthorCount = commit.getCoAuthors().length; if (coAuthorCount === 0) { - return commit.getAuthorName(); + return commit.getAuthorName(); } else if (coAuthorCount === 1) { return `${commit.getAuthorName()} and ${commit.getCoAuthors()[0].name}`; } else { From 1c82c2f84251330bd7c710193ac02fd78589b92d Mon Sep 17 00:00:00 2001 From: annthurium Date: Tue, 15 Jan 2019 13:39:40 -0800 Subject: [PATCH 1804/4053] :fire: .only --- test/views/commit-detail-view.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js index 4264576bc9..ca5166333b 100644 --- a/test/views/commit-detail-view.test.js +++ b/test/views/commit-detail-view.test.js @@ -10,7 +10,7 @@ import Remote, {nullRemote} from '../../lib/models/remote'; import {cloneRepository, buildRepository} from '../helpers'; import {commitBuilder} from '../builder/commit'; -describe.only('CommitDetailView', function() { +describe('CommitDetailView', function() { let repository, atomEnv; beforeEach(async function() { From 825e36e216c1658c8e724cddfe01313fdfc767d3 Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 16 Jan 2019 11:43:25 +0900 Subject: [PATCH 1805/4053] Add a bit more space for multiple avatars --- styles/commit-detail.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/commit-detail.less b/styles/commit-detail.less index 9f9bcc4762..dc1b9b84f3 100644 --- a/styles/commit-detail.less +++ b/styles/commit-detail.less @@ -41,7 +41,7 @@ &-metaText { flex: 1; - margin-left: @avatar-dimensions * 1.3; // leave some space for the avatars + margin-left: @avatar-dimensions * 1.4; // leave some space for the avatars line-height: @avatar-dimensions; color: @text-color-subtle; } From a9db48f084e1c4c65cebc7288f61ae90f1101aef Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 16 Jan 2019 14:20:30 +0100 Subject: [PATCH 1806/4053] experimenting wiht a DelayedPatch but idk --- lib/models/patch/builder.js | 14 ++++++++++---- lib/models/patch/file-patch.js | 5 ++--- lib/models/patch/patch.js | 22 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index b34ae9c3cd..4a8679bb76 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -1,4 +1,4 @@ -import {TextBuffer} from 'atom'; +import {TextBuffer, Point} from 'atom'; import Hunk from './hunk'; import File, {nullFile} from './file'; @@ -99,7 +99,12 @@ function singleDiffFilePatch(diff, layeredBuffer) { : nullFile; if (isDiffLarge([diff])) { - return FilePatch.createPlaceholder(oldFile, newFile); + const delayed = FilePatch.createDelayedFilePatch( + oldFile, newFile, diff, + new Point(layeredBuffer.buffer.getLastRow(), 0), + ); + console.log(delayed.getPatch()); + return delayed; } const [hunks, patchMarker] = buildHunks(diff, layeredBuffer); @@ -145,7 +150,7 @@ function dualDiffFilePatch(diff1, diff2, layeredBuffer) { const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink}); if (isDiffLarge([diff1, diff2])) { - return FilePatch.createPlaceholder(oldFile, newFile); + // TODO: do something with large diff too } const [hunks, patchMarker] = buildHunks(contentChangeDiff, layeredBuffer); @@ -263,7 +268,8 @@ function buildHunks(diff, {buffer, layers}) { } function isDiffLarge(diffs) { - const DIFF_BYTE_THRESHOLD = 32768; + // TODO: fixme + const DIFF_BYTE_THRESHOLD = 500; const size = diffs.reduce((diffSizeCounter, diff) => { return diffSizeCounter + diff.hunks.reduce((hunkSizeCounter, hunk) => { diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index b70fbfc3f2..4882c2760f 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -7,9 +7,8 @@ export default class FilePatch { return new this(nullFile, nullFile, Patch.createNull()); } - // TODO: explain what this is for - static createPlaceholder(oldFile, newFile) { - return new this(oldFile, newFile, Patch.createNull()); + static createDelayedFilePatch(oldFile, newFile, diff, insertPoint) { + return new this(oldFile, newFile, Patch.createDelayedPatch(diff, insertPoint)); } constructor(oldFile, newFile, patch) { diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 0fdafa0011..57bd975dad 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -8,6 +8,10 @@ export default class Patch { return new NullPatch(); } + static createDelayedPatch(diff, insertPoint) { + return new DelayedPatch(diff, insertPoint); + } + constructor({status, hunks, marker}) { this.status = status; this.hunks = hunks; @@ -346,6 +350,24 @@ class NullPatch { } } +// TODO: it prob shouldn't extend NullPatch +class DelayedPatch extends NullPatch { + // should know: + // where to insert + // what the diff is + // reason it's delayed (???) + constructor(diff, insertPoint) { + super(); + this.insertPoint = insertPoint; + this.diff = diff; + } + + getStartRange() { + return new Range(this.insertPoint, this.insertPoint); + } + +} + class BufferBuilder { constructor(original, originalBaseOffset, nextLayeredBuffer) { this.originalBuffer = original; From f621d491fbbf67e53db0b29e7e6d643ec5343a30 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 09:54:07 -0500 Subject: [PATCH 1807/4053] Delayed patch parsing Co-Authored-By: Vanessa Yuen --- lib/models/patch/builder.js | 63 +++++++++++++--------- lib/models/patch/file-patch.js | 13 +++-- lib/models/patch/patch.js | 38 +++++++++++--- test/models/patch/builder.test.js | 86 +++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 36 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 4a8679bb76..30adefe7f2 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -2,21 +2,29 @@ import {TextBuffer, Point} from 'atom'; import Hunk from './hunk'; import File, {nullFile} from './file'; -import Patch from './patch'; +import Patch, {TOO_LARGE} from './patch'; import {Unchanged, Addition, Deletion, NoNewline} from './region'; import FilePatch from './file-patch'; import MultiFilePatch from './multi-file-patch'; -export function buildFilePatch(diffs) { +// TODO: fixme +const DEFAULT_LARGE_DIFF_THRESHOLD = 500; + +export function buildFilePatch(diffs, options) { + const opts = { + largeDiffThreshold: DEFAULT_LARGE_DIFF_THRESHOLD, + ...options, + }; + const layeredBuffer = initializeBuffer(); let filePatch; if (diffs.length === 0) { filePatch = emptyDiffFilePatch(); } else if (diffs.length === 1) { - filePatch = singleDiffFilePatch(diffs[0], layeredBuffer); + filePatch = singleDiffFilePatch(diffs[0], layeredBuffer, opts); } else if (diffs.length === 2) { - filePatch = dualDiffFilePatch(diffs[0], diffs[1], layeredBuffer); + filePatch = dualDiffFilePatch(diffs[0], diffs[1], layeredBuffer, opts); } else { throw new Error(`Unexpected number of diffs: ${diffs.length}`); } @@ -24,7 +32,12 @@ export function buildFilePatch(diffs) { return new MultiFilePatch({filePatches: [filePatch], ...layeredBuffer}); } -export function buildMultiFilePatch(diffs) { +export function buildMultiFilePatch(diffs, options) { + const opts = { + largeDiffThreshold: DEFAULT_LARGE_DIFF_THRESHOLD, + ...options, + }; + const layeredBuffer = initializeBuffer(); const byPath = new Map(); const actions = []; @@ -40,7 +53,7 @@ export function buildMultiFilePatch(diffs) { if (otherHalf) { // The second half. Complete the paired diff, or fail if they have unexpected statuses or modes. const [otherDiff, otherIndex] = otherHalf; - actions[otherIndex] = () => dualDiffFilePatch(diff, otherDiff, layeredBuffer); + actions[otherIndex] = () => dualDiffFilePatch(diff, otherDiff, layeredBuffer, opts); byPath.delete(thePath); } else { // The first half we've seen. @@ -48,7 +61,7 @@ export function buildMultiFilePatch(diffs) { index++; } } else { - actions[index] = () => singleDiffFilePatch(diff, layeredBuffer); + actions[index] = () => singleDiffFilePatch(diff, layeredBuffer, opts); index++; } } @@ -76,7 +89,7 @@ function emptyDiffFilePatch() { return FilePatch.createNull(); } -function singleDiffFilePatch(diff, layeredBuffer) { +function singleDiffFilePatch(diff, layeredBuffer, opts) { const wasSymlink = diff.oldMode === File.modes.SYMLINK; const isSymlink = diff.newMode === File.modes.SYMLINK; @@ -98,22 +111,27 @@ function singleDiffFilePatch(diff, layeredBuffer) { ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink}) : nullFile; - if (isDiffLarge([diff])) { + if (isDiffLarge([diff], opts)) { + const insertPoint = new Point(layeredBuffer.buffer.getLastRow(), 0); const delayed = FilePatch.createDelayedFilePatch( - oldFile, newFile, diff, - new Point(layeredBuffer.buffer.getLastRow(), 0), + oldFile, newFile, insertPoint, + TOO_LARGE, + () => { + const [hunks, patchMarker] = buildHunks(diff, layeredBuffer, insertPoint.row); + return new Patch({status: diff.status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); + }, ); console.log(delayed.getPatch()); return delayed; } - const [hunks, patchMarker] = buildHunks(diff, layeredBuffer); + const [hunks, patchMarker] = buildHunks(diff, layeredBuffer, layeredBuffer.buffer.getLastRow()); const patch = new Patch({status: diff.status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); return new FilePatch(oldFile, newFile, patch); } -function dualDiffFilePatch(diff1, diff2, layeredBuffer) { +function dualDiffFilePatch(diff1, diff2, layeredBuffer, opts) { let modeChangeDiff, contentChangeDiff; if (diff1.oldMode === File.modes.SYMLINK || diff1.newMode === File.modes.SYMLINK) { modeChangeDiff = diff1; @@ -153,7 +171,7 @@ function dualDiffFilePatch(diff1, diff2, layeredBuffer) { // TODO: do something with large diff too } - const [hunks, patchMarker] = buildHunks(contentChangeDiff, layeredBuffer); + const [hunks, patchMarker] = buildHunks(contentChangeDiff, layeredBuffer, layeredBuffer.buffer.getLastRow()); const patch = new Patch({status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); return new FilePatch(oldFile, newFile, patch); @@ -178,7 +196,7 @@ function initializeBuffer() { return {buffer, layers}; } -function buildHunks(diff, {buffer, layers}) { +function buildHunks(diff, {buffer, layers}, insertRow) { const layersByKind = new Map([ [Unchanged, layers.unchanged], [Addition, layers.addition], @@ -187,7 +205,7 @@ function buildHunks(diff, {buffer, layers}) { ]); const hunks = []; - const patchStartRow = buffer.getLastRow(); + const patchStartRow = insertRow; let bufferRow = patchStartRow; let nextLineLength = 0; @@ -228,7 +246,7 @@ function buildHunks(diff, {buffer, layers}) { for (const lineText of hunkData.lines) { const bufferLine = lineText.slice(1) + '\n'; nextLineLength = lineText.length - 1; - buffer.append(bufferLine); + buffer.insert([bufferRow, 0], bufferLine); const ChangeKind = CHANGEKIND[lineText[0]]; if (ChangeKind === undefined) { @@ -267,18 +285,13 @@ function buildHunks(diff, {buffer, layers}) { return [hunks, patchMarker]; } -function isDiffLarge(diffs) { - // TODO: fixme - const DIFF_BYTE_THRESHOLD = 500; - +function isDiffLarge(diffs, opts) { const size = diffs.reduce((diffSizeCounter, diff) => { return diffSizeCounter + diff.hunks.reduce((hunkSizeCounter, hunk) => { - return hunkSizeCounter + hunk.lines.reduce((lineSizeCounter, line) => { - return lineSizeCounter + Buffer.byteLength(line, 'utf8'); - }, 0); + return hunkSizeCounter + hunk.lines.length; }, 0); }, 0); console.log(diffs, `size: ${size}`); - return size > DIFF_BYTE_THRESHOLD; + return size > opts.largeDiffThreshold; } diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 4882c2760f..868ce03753 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -7,8 +7,8 @@ export default class FilePatch { return new this(nullFile, nullFile, Patch.createNull()); } - static createDelayedFilePatch(oldFile, newFile, diff, insertPoint) { - return new this(oldFile, newFile, Patch.createDelayedPatch(diff, insertPoint)); + static createDelayedFilePatch(oldFile, newFile, position, renderStatus, parseFn) { + return new this(oldFile, newFile, Patch.createDelayedPatch(position, renderStatus, parseFn)); } constructor(oldFile, newFile, patch) { @@ -21,9 +21,8 @@ export default class FilePatch { return this.oldFile.isPresent() || this.newFile.isPresent() || this.patch.isPresent(); } - isLarge() { - // TODO: fix this - return (this.oldFile.isPresent() || this.newFile.isPresent()) && !this.patch.isPresent(); + getRenderStatus() { + return this.patch.getRenderStatus(); } getOldFile() { @@ -116,6 +115,10 @@ export default class FilePatch { return this.getPatch().getHunks(); } + triggerDelayedRender() { + this.patch = this.patch.parseFn(); + } + clone(opts = {}) { return new this.constructor( opts.oldFile !== undefined ? opts.oldFile : this.oldFile, diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 57bd975dad..bfe2a69778 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -3,13 +3,19 @@ import {TextBuffer, Range} from 'atom'; import Hunk from './hunk'; import {Unchanged, Addition, Deletion, NoNewline} from './region'; +export const TOO_LARGE = Symbol('too large'); + +export const EXPANDED = Symbol('expanded'); + +export const COLLAPSED = Symbol('collapsed'); + export default class Patch { static createNull() { return new NullPatch(); } - static createDelayedPatch(diff, insertPoint) { - return new DelayedPatch(diff, insertPoint); + static createDelayedPatch(position, renderStatus, parseFn) { + return new DelayedPatch(position, renderStatus, parseFn); } constructor({status, hunks, marker}) { @@ -269,6 +275,14 @@ export default class Patch { isPresent() { return true; } + + getRenderStatus() { + return EXPANDED; + } + + parseFn() { + return this; + } } class NullPatch { @@ -348,6 +362,14 @@ class NullPatch { isPresent() { return false; } + + renderStatus() { + return EXPANDED; + } + + parseFn() { + return this; + } } // TODO: it prob shouldn't extend NullPatch @@ -356,16 +378,20 @@ class DelayedPatch extends NullPatch { // where to insert // what the diff is // reason it's delayed (???) - constructor(diff, insertPoint) { + constructor(position, renderStatus, parseFn) { super(); - this.insertPoint = insertPoint; - this.diff = diff; + this.position = position; + this.renderStatus = renderStatus; + this.parseFn = parseFn; } getStartRange() { - return new Range(this.insertPoint, this.insertPoint); + return new Range(this.position, this.position); } + getRenderStatus() { + return this.renderStatus; + } } class BufferBuilder { diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 8e64488f05..9e759c2ebe 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -1,4 +1,5 @@ import {buildFilePatch, buildMultiFilePatch} from '../../../lib/models/patch'; +import {TOO_LARGE, EXPANDED} from '../../../lib/models/patch/patch'; import {assertInPatch, assertInFilePatch} from '../../helpers'; describe('buildFilePatch', function() { @@ -800,6 +801,91 @@ describe('buildFilePatch', function() { }); }); + describe('with a large diff', function() { + it('creates a DelayedPatch when the diff is "too large"', function() { + const mfp = buildMultiFilePatch([ + { + oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100755', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 3, + lines: [' line-0', '+line-1', '-line-2', ' line-3'], + }, + ], + }, + { + oldPath: 'second', oldMode: '100644', newPath: 'second', newMode: '100755', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 1, newStartLine: 1, newLineCount: 2, + lines: [' line-4', '+line-5'], + }, + ], + }, + ], {largeDiffThreshold: 3}); + + assert.lengthOf(mfp.getFilePatches(), 2); + const [fp0, fp1] = mfp.getFilePatches(); + + assert.strictEqual(fp0.getRenderStatus(), TOO_LARGE); + assert.strictEqual(fp0.getOldPath(), 'first'); + assert.strictEqual(fp0.getNewPath(), 'first'); + assert.deepEqual(fp0.getStartRange().serialize(), [[0, 0], [0, 0]]); + assertInFilePatch(fp0).hunks(); + + assert.strictEqual(fp1.getRenderStatus(), EXPANDED); + assert.strictEqual(fp1.getOldPath(), 'second'); + assert.strictEqual(fp1.getNewPath(), 'second'); + assert.deepEqual(fp1.getMarker().getRange().serialize(), [[0, 0], [1, 6]]); + assertInFilePatch(fp1, mfp.getBuffer()).hunks( + { + startRow: 0, endRow: 1, header: '@@ -1,1 +1,2 @@', regions: [ + {kind: 'unchanged', string: ' line-4\n', range: [[0, 0], [0, 6]]}, + {kind: 'addition', string: '+line-5\n', range: [[1, 0], [1, 6]]}, + ], + }, + ); + }); + + it('re-parse a DelayedPatch as a Patch', function() { + const mfp = buildMultiFilePatch([ + { + oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100644', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 3, + lines: [' line-0', '+line-1', '-line-2', ' line-3'], + }, + ], + }, + ], {largeDiffThreshold: 3}); + + assert.lengthOf(mfp.getFilePatches(), 1); + const [fp] = mfp.getFilePatches(); + + assert.strictEqual(fp.getRenderStatus(), TOO_LARGE); + assert.strictEqual(fp.getOldPath(), 'first'); + assert.strictEqual(fp.getNewPath(), 'first'); + assert.deepEqual(fp.getStartRange().serialize(), [[0, 0], [0, 0]]); + assertInFilePatch(fp).hunks(); + + fp.triggerDelayedRender(); + + assert.strictEqual(fp.getRenderStatus(), EXPANDED); + assert.deepEqual(fp.getMarker().getRange().serialize(), [[0, 0], [3, 6]]); + assertInFilePatch(fp, mfp.getBuffer()).hunks( + { + startRow: 0, endRow: 3, header: '@@ -1,3 +1,3 @@', regions: [ + {kind: 'unchanged', string: ' line-0\n', range: [[0, 0], [0, 6]]}, + {kind: 'addition', string: '+line-1\n', range: [[1, 0], [1, 6]]}, + {kind: 'deletion', string: '-line-2\n', range: [[2, 0], [2, 6]]}, + {kind: 'unchanged', string: ' line-3\n', range: [[3, 0], [3, 6]]}, + ], + }, + ); + }); + }); + it('throws an error with an unexpected number of diffs', function() { assert.throws(() => buildFilePatch([1, 2, 3]), /Unexpected number of diffs: 3/); }); From a729493cdfbbb70faf728bb3ab1b1b32754807b2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 10:55:34 -0500 Subject: [PATCH 1808/4053] Correct pre-existing markers after building patch hunks --- lib/models/patch/builder.js | 11 ++++ test/models/patch/builder.test.js | 99 +++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 30adefe7f2..9a52850237 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -205,6 +205,12 @@ function buildHunks(diff, {buffer, layers}, insertRow) { ]); const hunks = []; + const afterMarkers = []; + for (const layerName in layers) { + const layer = layers[layerName]; + afterMarkers.push(...layer.findMarkers({startPosition: [insertRow, 0]})); + } + const patchStartRow = insertRow; let bufferRow = patchStartRow; let nextLineLength = 0; @@ -282,6 +288,11 @@ function buildHunks(diff, {buffer, layers}, insertRow) { {invalidate: 'never', exclusive: false}, ); + // Correct any markers that used to start at the insertion point. + for (const marker of afterMarkers) { + marker.setTailPosition([bufferRow, 0]); + } + return [hunks, patchMarker]; } diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 9e759c2ebe..550b17d506 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -884,6 +884,105 @@ describe('buildFilePatch', function() { }, ); }); + + it('does not interfere with markers from surrounding patches when expanded', function() { + const mfp = buildMultiFilePatch([ + { + oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100644', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 3, + lines: [' line-0', '+line-1', '-line-2', ' line-3'], + }, + ], + }, + { + oldPath: 'big', oldMode: '100644', newPath: 'big', newMode: '100644', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 4, + lines: [' line-0', '+line-1', '+line-2', '-line-3', ' line-4'], + }, + ], + }, + { + oldPath: 'last', oldMode: '100644', newPath: 'last', newMode: '100644', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 3, + lines: [' line-0', '+line-1', '-line-2', ' line-3'], + }, + ], + } + ], {largeDiffThreshold: 4}); + + assert.lengthOf(mfp.getFilePatches(), 3); + const [fp0, fp1, fp2] = mfp.getFilePatches(); + assert.strictEqual(fp0.getRenderStatus(), EXPANDED); + assertInFilePatch(fp0, mfp.getBuffer()).hunks( + { + startRow: 0, endRow: 3, header: '@@ -1,3 +1,3 @@', regions: [ + {kind: 'unchanged', string: ' line-0\n', range: [[0, 0], [0, 6]]}, + {kind: 'addition', string: '+line-1\n', range: [[1, 0], [1, 6]]}, + {kind: 'deletion', string: '-line-2\n', range: [[2, 0], [2, 6]]}, + {kind: 'unchanged', string: ' line-3\n', range: [[3, 0], [3, 6]]}, + ], + }, + ); + + assert.strictEqual(fp1.getRenderStatus(), TOO_LARGE); + assertInFilePatch(fp1, mfp.getBuffer()).hunks(); + + assert.strictEqual(fp2.getRenderStatus(), EXPANDED); + assertInFilePatch(fp2, mfp.getBuffer()).hunks( + { + startRow: 4, endRow: 7, header: '@@ -1,3 +1,3 @@', regions: [ + {kind: 'unchanged', string: ' line-0\n', range: [[4, 0], [4, 6]]}, + {kind: 'addition', string: '+line-1\n', range: [[5, 0], [5, 6]]}, + {kind: 'deletion', string: '-line-2\n', range: [[6, 0], [6, 6]]}, + {kind: 'unchanged', string: ' line-3\n', range: [[7, 0], [7, 6]]}, + ], + }, + ); + + fp1.triggerDelayedRender(); + + assert.strictEqual(fp0.getRenderStatus(), EXPANDED); + assertInFilePatch(fp0, mfp.getBuffer()).hunks( + { + startRow: 0, endRow: 3, header: '@@ -1,3 +1,3 @@', regions: [ + {kind: 'unchanged', string: ' line-0\n', range: [[0, 0], [0, 6]]}, + {kind: 'addition', string: '+line-1\n', range: [[1, 0], [1, 6]]}, + {kind: 'deletion', string: '-line-2\n', range: [[2, 0], [2, 6]]}, + {kind: 'unchanged', string: ' line-3\n', range: [[3, 0], [3, 6]]}, + ], + }, + ); + + assert.strictEqual(fp1.getRenderStatus(), EXPANDED); + assertInFilePatch(fp1, mfp.getBuffer()).hunks( + { + startRow: 4, endRow: 8, header: '@@ -1,3 +1,4 @@', regions: [ + {kind: 'unchanged', string: ' line-0\n', range: [[4, 0], [4, 6]]}, + {kind: 'addition', string: '+line-1\n+line-2\n', range: [[5, 0], [6, 6]]}, + {kind: 'deletion', string: '-line-3\n', range: [[7, 0], [7, 6]]}, + {kind: 'unchanged', string: ' line-4\n', range: [[8, 0], [8, 6]]}, + ], + }, + ); + + assert.strictEqual(fp2.getRenderStatus(), EXPANDED); + assertInFilePatch(fp2, mfp.getBuffer()).hunks( + { + startRow: 9, endRow: 12, header: '@@ -1,3 +1,3 @@', regions: [ + {kind: 'unchanged', string: ' line-0\n', range: [[9, 0], [9, 6]]}, + {kind: 'addition', string: '+line-1\n', range: [[10, 0], [10, 6]]}, + {kind: 'deletion', string: '-line-2\n', range: [[11, 0], [11, 6]]}, + {kind: 'unchanged', string: ' line-3\n', range: [[12, 0], [12, 6]]}, + ], + }, + ); + }); }); it('throws an error with an unexpected number of diffs', function() { From 68926e5d4ffe41548b5117605f8767d3a4888be1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 11:03:36 -0500 Subject: [PATCH 1809/4053] :shirt: --- test/models/patch/builder.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 550b17d506..5ee54e42d3 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -913,7 +913,7 @@ describe('buildFilePatch', function() { lines: [' line-0', '+line-1', '-line-2', ' line-3'], }, ], - } + }, ], {largeDiffThreshold: 4}); assert.lengthOf(mfp.getFilePatches(), 3); From 4686cfcda3224df682830ed69150e73dc742e0c0 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 16 Jan 2019 18:14:10 +0100 Subject: [PATCH 1810/4053] get things to render properly first --- lib/models/patch/builder.js | 8 +++----- lib/views/multi-file-patch-view.js | 4 +++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 9a52850237..67c8140e31 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -8,7 +8,7 @@ import FilePatch from './file-patch'; import MultiFilePatch from './multi-file-patch'; // TODO: fixme -const DEFAULT_LARGE_DIFF_THRESHOLD = 500; +const DEFAULT_LARGE_DIFF_THRESHOLD = 10; export function buildFilePatch(diffs, options) { const opts = { @@ -68,7 +68,7 @@ export function buildMultiFilePatch(diffs, options) { // Populate unpaired diffs that looked like they could be part of a pair, but weren't. for (const [unpairedDiff, originalIndex] of byPath.values()) { - actions[originalIndex] = () => singleDiffFilePatch(unpairedDiff, layeredBuffer); + actions[originalIndex] = () => singleDiffFilePatch(unpairedDiff, layeredBuffer, opts); } const filePatches = actions.map(action => action()); @@ -121,7 +121,6 @@ function singleDiffFilePatch(diff, layeredBuffer, opts) { return new Patch({status: diff.status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); }, ); - console.log(delayed.getPatch()); return delayed; } @@ -167,7 +166,7 @@ function dualDiffFilePatch(diff1, diff2, layeredBuffer, opts) { const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink}); const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink}); - if (isDiffLarge([diff1, diff2])) { + if (isDiffLarge([diff1, diff2]), opts) { // TODO: do something with large diff too } @@ -303,6 +302,5 @@ function isDiffLarge(diffs, opts) { }, 0); }, 0); - console.log(diffs, `size: ${size}`); return size > opts.largeDiffThreshold; } diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index a8c50824ec..bf2b7daa3e 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -22,6 +22,8 @@ import CommitDetailItem from '../items/commit-detail-item'; import IssueishDetailItem from '../items/issueish-detail-item'; import File from '../models/patch/file'; +import {TOO_LARGE} from '../models/patch/patch'; + const executableText = { [File.modes.NORMAL]: 'non executable', [File.modes.EXECUTABLE]: 'executable', @@ -353,7 +355,7 @@ export default class MultiFilePatchView extends React.Component { - {filePatch.isLarge() && this.renderDiffGate(filePatch)} + {filePatch.getPatch().getRenderStatus() === TOO_LARGE && this.renderDiffGate(filePatch)} {this.renderHunkHeaders(filePatch)} From 0c3a2a6457690458e831c9da7f6629a242149a4f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 12:25:33 -0500 Subject: [PATCH 1811/4053] Accept renderStatusOverrides in ye MultiFilePatch builder --- lib/models/patch/builder.js | 50 ++++++++++++++++++------------- test/models/patch/builder.test.js | 32 ++++++++++++++++++++ 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 67c8140e31..6c75fc5b0d 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -2,19 +2,22 @@ import {TextBuffer, Point} from 'atom'; import Hunk from './hunk'; import File, {nullFile} from './file'; -import Patch, {TOO_LARGE} from './patch'; +import Patch, {TOO_LARGE, EXPANDED} from './patch'; import {Unchanged, Addition, Deletion, NoNewline} from './region'; import FilePatch from './file-patch'; import MultiFilePatch from './multi-file-patch'; -// TODO: fixme -const DEFAULT_LARGE_DIFF_THRESHOLD = 10; +const DEFAULT_OPTIONS = { + // Number of lines after which we consider the diff "large" + // TODO: Set this based on performance measurements + largeDiffThreshold: 10, + + // Map of file path (relative to repository root) to Patch render status (EXPANDED, COLLAPSED, TOO_LARGE) + renderStatusOverrides: {}, +}; export function buildFilePatch(diffs, options) { - const opts = { - largeDiffThreshold: DEFAULT_LARGE_DIFF_THRESHOLD, - ...options, - }; + const opts = {...DEFAULT_OPTIONS, ...options}; const layeredBuffer = initializeBuffer(); @@ -33,10 +36,7 @@ export function buildFilePatch(diffs, options) { } export function buildMultiFilePatch(diffs, options) { - const opts = { - largeDiffThreshold: DEFAULT_LARGE_DIFF_THRESHOLD, - ...options, - }; + const opts = {...DEFAULT_OPTIONS, ...options}; const layeredBuffer = initializeBuffer(); const byPath = new Map(); @@ -111,23 +111,31 @@ function singleDiffFilePatch(diff, layeredBuffer, opts) { ? new File({path: diff.newPath, mode: diff.newMode, symlink: newSymlink}) : nullFile; - if (isDiffLarge([diff], opts)) { + const renderStatusOverride = + (oldFile.isPresent() && opts.renderStatusOverrides[oldFile.getPath()]) || + (newFile.isPresent() && opts.renderStatusOverrides[newFile.getPath()]) || + undefined; + + const renderStatus = renderStatusOverride || + (isDiffLarge([diff], opts) && TOO_LARGE) || + EXPANDED; + + if (renderStatus === TOO_LARGE) { const insertPoint = new Point(layeredBuffer.buffer.getLastRow(), 0); - const delayed = FilePatch.createDelayedFilePatch( - oldFile, newFile, insertPoint, - TOO_LARGE, + + return FilePatch.createDelayedFilePatch( + oldFile, newFile, insertPoint, TOO_LARGE, () => { const [hunks, patchMarker] = buildHunks(diff, layeredBuffer, insertPoint.row); return new Patch({status: diff.status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); }, ); - return delayed; - } - - const [hunks, patchMarker] = buildHunks(diff, layeredBuffer, layeredBuffer.buffer.getLastRow()); - const patch = new Patch({status: diff.status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); + } else { + const [hunks, patchMarker] = buildHunks(diff, layeredBuffer, layeredBuffer.buffer.getLastRow()); + const patch = new Patch({status: diff.status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); - return new FilePatch(oldFile, newFile, patch); + return new FilePatch(oldFile, newFile, patch); + } } function dualDiffFilePatch(diff1, diff2, layeredBuffer, opts) { diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 5ee54e42d3..59951c2f63 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -983,6 +983,38 @@ describe('buildFilePatch', function() { }, ); }); + + it('does not create a DelayedPatch when the patch has been explicitly expanded', function() { + const mfp = buildMultiFilePatch([ + { + oldPath: 'big/file.txt', oldMode: '100644', newPath: 'big/file.txt', newMode: '100755', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 3, + lines: [' line-0', '+line-1', '-line-2', ' line-3'], + }, + ], + }, + ], {largeDiffThreshold: 3, renderStatusOverrides: {'big/file.txt': EXPANDED}}); + + assert.lengthOf(mfp.getFilePatches(), 1); + const [fp] = mfp.getFilePatches(); + + assert.strictEqual(fp.getRenderStatus(), EXPANDED); + assert.strictEqual(fp.getOldPath(), 'big/file.txt'); + assert.strictEqual(fp.getNewPath(), 'big/file.txt'); + assert.deepEqual(fp.getMarker().getRange().serialize(), [[0, 0], [3, 6]]); + assertInFilePatch(fp, mfp.getBuffer()).hunks( + { + startRow: 0, endRow: 3, header: '@@ -1,1 +1,2 @@', regions: [ + {kind: 'unchanged', string: ' line-0\n', range: [[0, 0], [0, 6]]}, + {kind: 'addition', string: '+line-1\n', range: [[1, 0], [1, 6]]}, + {kind: 'deletion', string: '-line-2\n', range: [[2, 0], [2, 6]]}, + {kind: 'unchanged', string: ' line-3\n', range: [[3, 0], [3, 6]]}, + ], + }, + ); + }); }); it('throws an error with an unexpected number of diffs', function() { From 7db6fb04fb41d057581d3d58edf0eac7bff85884 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 12:31:53 -0500 Subject: [PATCH 1812/4053] Pass builder options to getFilePatchForPath and getStagedChangesPatch --- lib/models/repository-states/present.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 830c18280e..4604efed66 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -698,16 +698,27 @@ export default class Present extends State { return {stagedFiles, unstagedFiles, mergeConflictFiles}; } - getFilePatchForPath(filePath, {staged} = {staged: false}) { - return this.cache.getOrSet(Keys.filePatch.oneWith(filePath, {staged}), async () => { - const diffs = await this.git().getDiffsForFilePath(filePath, {staged}); - return buildFilePatch(diffs); + getFilePatchForPath(filePath, options) { + const opts = { + staged: false, + builder: {}, + ...options, + }; + + return this.cache.getOrSet(Keys.filePatch.oneWith(filePath, {staged: opts.staged}), async () => { + const diffs = await this.git().getDiffsForFilePath(filePath, {staged: opts.staged}); + return buildFilePatch(diffs, opts.builder); }); } - getStagedChangesPatch() { + getStagedChangesPatch(options) { + const opts = { + builder: {}, + ...options, + }; + return this.cache.getOrSet(Keys.stagedChanges, () => { - return this.git().getStagedChangesPatch().then(buildMultiFilePatch); + return this.git().getStagedChangesPatch().then(diffs => buildMultiFilePatch(diffs, opts.builder)); }); } From 9de0eae26de12dd6aed388fac0a1ec2d986fbc86 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 16 Jan 2019 18:17:36 +0100 Subject: [PATCH 1813/4053] some clean up --- lib/models/patch/patch.js | 4 ---- lib/views/multi-file-patch-view.js | 1 - 2 files changed, 5 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index bfe2a69778..dd389a59c1 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -374,10 +374,6 @@ class NullPatch { // TODO: it prob shouldn't extend NullPatch class DelayedPatch extends NullPatch { - // should know: - // where to insert - // what the diff is - // reason it's delayed (???) constructor(position, renderStatus, parseFn) { super(); this.position = position; diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index bf2b7daa3e..d36af915bd 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -363,7 +363,6 @@ export default class MultiFilePatchView extends React.Component { } renderDiffGate(filePatch) { - // TODO: startRange is incorrect here return ( From e8f372ad3b6f5e143ea6d6a4420a8956d87a91ba Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 16 Jan 2019 19:02:56 +0100 Subject: [PATCH 1814/4053] click to show diff??? --- lib/views/multi-file-patch-view.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index d36af915bd..54956b1bcc 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -363,10 +363,14 @@ export default class MultiFilePatchView extends React.Component { } renderDiffGate(filePatch) { + const showDiff = () => filePatch.triggerDelayedRender(); return ( -

    Diff is too large to be displayed.

    +

    + Large diffs are collapsed by default for performance reasons. + Show diff anyway? +

    ); From dc04f498a99bb1fd9e55b2b70d6fad919e887e1d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 13:52:52 -0500 Subject: [PATCH 1815/4053] Add a renderStatus to Patch --- lib/models/patch/patch.js | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index dd389a59c1..db4801bd45 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -18,10 +18,11 @@ export default class Patch { return new DelayedPatch(position, renderStatus, parseFn); } - constructor({status, hunks, marker}) { + constructor({status, hunks, marker, renderStatus}) { this.status = status; this.hunks = hunks; this.marker = marker; + this.renderStatus = renderStatus || EXPANDED; this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); } @@ -69,9 +70,26 @@ export default class Patch { status: opts.status !== undefined ? opts.status : this.getStatus(), hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), marker: opts.marker !== undefined ? opts.marker : this.getMarker(), + renderStatus: opts.renderStatus !== undefined ? opts.renderStatus : this.getRenderStatus(), }); } + collapsed() { + if (this.getRenderStatus() === COLLAPSED) { + return this; + } + + return this.clone({renderStatus: COLLAPSED}); + } + + expanded() { + if (this.getRenderStatus() === EXPANDED) { + return this; + } + + return this.clone({renderStatus: EXPANDED}); + } + buildStagePatchForLines(originalBuffer, nextLayeredBuffer, rowSet) { const originalBaseOffset = this.getMarker().getRange().start.row; const builder = new BufferBuilder(originalBuffer, originalBaseOffset, nextLayeredBuffer); @@ -277,7 +295,7 @@ export default class Patch { } getRenderStatus() { - return EXPANDED; + return this.renderStatus; } parseFn() { @@ -331,7 +349,8 @@ class NullPatch { if ( opts.status === undefined && opts.hunks === undefined && - opts.marker === undefined + opts.marker === undefined && + opts.renderStatus === undefined ) { return this; } else { @@ -339,6 +358,7 @@ class NullPatch { status: opts.status !== undefined ? opts.status : this.getStatus(), hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), marker: opts.marker !== undefined ? opts.marker : this.getMarker(), + renderStatus: opts.renderStatus !== undefined ? opts.renderStatus : this.getRenderStatus(), }); } } @@ -363,7 +383,7 @@ class NullPatch { return false; } - renderStatus() { + getRenderStatus() { return EXPANDED; } From f0b8371ee74ab75b6f56a07c804543626873a323 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 13:53:10 -0500 Subject: [PATCH 1816/4053] Emit events from a FilePatch when its render status changes --- lib/models/patch/file-patch.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 868ce03753..230928226a 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -1,3 +1,5 @@ +import {Emitter} from 'event-kit'; + import {nullFile} from './file'; import Patch from './patch'; import {toGitPathSep} from '../../helpers'; @@ -15,6 +17,8 @@ export default class FilePatch { this.oldFile = oldFile; this.newFile = newFile; this.patch = patch; + + this.emitter = new Emitter(); } isPresent() { @@ -116,7 +120,35 @@ export default class FilePatch { } triggerDelayedRender() { - this.patch = this.patch.parseFn(); + const nextPatch = this.patch.parseFn(); + if (nextPatch !== this.patch) { + this.patch = nextPatch; + this.didChangeRenderStatus(); + } + } + + triggerCollapse() { + const nextPatch = this.patch.collapsed(); + if (nextPatch !== this.patch) { + this.patch = nextPatch; + this.didChangeRenderStatus(); + } + } + + triggerExpand() { + const nextPatch = this.patch.expanded(); + if (nextPatch !== this.patch) { + this.patch = nextPatch; + this.didChangeRenderStatus(); + } + } + + didChangeRenderStatus() { + return this.emitter.emit('change-render-status', this); + } + + onDidChangeRenderStatus(callback) { + return this.emitter.on('change-render-status', callback); } clone(opts = {}) { From 9dad4f7942c0d27ee668e6946ec4b5605d8ba76f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 13:53:36 -0500 Subject: [PATCH 1817/4053] Unit tests for FilePatch render status events --- test/builder/patch.js | 13 ++++- test/models/patch/file-patch.test.js | 75 +++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index 18ff5067b5..c2e0e830ab 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -122,6 +122,11 @@ class FilePatchBuilder { return this; } + renderStatus(...args) { + this.patchBuilder.renderStatus(...args); + return this; + } + empty() { this.patchBuilder.empty(); return this; @@ -175,6 +180,7 @@ class PatchBuilder { constructor(layeredBuffer = null) { this.layeredBuffer = layeredBuffer; + this._renderStatus = undefined; this._status = 'modified'; this.hunks = []; @@ -183,6 +189,11 @@ class PatchBuilder { this.explicitlyEmpty = false; } + renderStatus(status) { + this._renderStatus = status; + return this; + } + status(st) { if (['modified', 'added', 'deleted'].indexOf(st) === -1) { throw new Error(`Unrecognized status: ${st} (must be 'modified', 'added' or 'deleted')`); @@ -221,7 +232,7 @@ class PatchBuilder { const marker = this.layeredBuffer.markFrom('patch', this.patchStart); return this.layeredBuffer.wrapReturn({ - patch: new Patch({status: this._status, hunks: this.hunks, marker}), + patch: new Patch({status: this._status, hunks: this.hunks, marker, renderStatus: this._renderStatus}), }); } } diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index abc0b5551d..34bc44dbfb 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -1,11 +1,12 @@ -import {TextBuffer} from 'atom'; +import {TextBuffer, Point} from 'atom'; import FilePatch from '../../../lib/models/patch/file-patch'; import File, {nullFile} from '../../../lib/models/patch/file'; -import Patch from '../../../lib/models/patch/patch'; +import Patch, {TOO_LARGE, COLLAPSED, EXPANDED} from '../../../lib/models/patch/patch'; import Hunk from '../../../lib/models/patch/hunk'; import {Unchanged, Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; import {assertInFilePatch} from '../../helpers'; +import {filePatchBuilder, patchBuilder} from '../../builder/patch'; describe('FilePatch', function() { it('delegates methods to its files and patch', function() { @@ -669,6 +670,76 @@ describe('FilePatch', function() { assert.isFalse(nullFilePatch.buildUnstagePatchForLines(new Set([0])).isPresent()); assert.strictEqual(nullFilePatch.toStringIn(new TextBuffer()), ''); }); + + describe('render status changes', function() { + let sub; + + afterEach(function() { + sub && sub.dispose(); + }); + + it('announces a delayed render to subscribers', function() { + const {patch: expandedPatch} = patchBuilder().build(); + + const filePatch = FilePatch.createDelayedFilePatch( + new File({path: 'file-0.txt', mode: '100644'}), + new File({path: 'file-1.txt', mode: '100644'}), + new Point(0, 0), + TOO_LARGE, + () => expandedPatch, + ); + + const callback = sinon.spy(); + sub = filePatch.onDidChangeRenderStatus(callback); + + assert.strictEqual(TOO_LARGE, filePatch.getRenderStatus()); + assert.notStrictEqual(filePatch.getPatch(), expandedPatch); + filePatch.triggerDelayedRender(); + + assert.strictEqual(EXPANDED, filePatch.getRenderStatus()); + assert.strictEqual(filePatch.getPatch(), expandedPatch); + assert.isTrue(callback.calledWith(filePatch)); + }); + + it('announces the collapse of an expanded patch', function() { + const {filePatch} = filePatchBuilder().build(); + + const callback = sinon.spy(); + sub = filePatch.onDidChangeRenderStatus(callback); + + assert.strictEqual(EXPANDED, filePatch.getRenderStatus()); + filePatch.triggerCollapse(); + + assert.strictEqual(COLLAPSED, filePatch.getRenderStatus()); + assert.isTrue(callback.calledWith(filePatch)); + }); + + it('announces the expansion of a collapsed patch', function() { + const {filePatch} = filePatchBuilder().renderStatus(COLLAPSED).build(); + + const callback = sinon.spy(); + sub = filePatch.onDidChangeRenderStatus(callback); + + assert.strictEqual(COLLAPSED, filePatch.getRenderStatus()); + filePatch.triggerExpand(); + + assert.strictEqual(EXPANDED, filePatch.getRenderStatus()); + assert.isTrue(callback.calledWith(filePatch)); + }); + + it('does not announce non-changes', function() { + const {filePatch} = filePatchBuilder().build(); + + const callback = sinon.spy(); + sub = filePatch.onDidChangeRenderStatus(callback); + + filePatch.triggerDelayedRender(); + assert.isFalse(callback.called); + + filePatch.triggerExpand(); + assert.isFalse(callback.called); + }); + }); }); function buildLayers(buffer) { From cd5cc2899040950363f7f0018c363f75fa2d076e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 14:27:04 -0500 Subject: [PATCH 1818/4053] Remember changed renderStatus --- lib/containers/changed-file-container.js | 35 ++++++++++++++++++- .../containers/changed-file-container.test.js | 21 ++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/lib/containers/changed-file-container.js b/lib/containers/changed-file-container.js index fc9a83ee4c..221ac0c7fe 100644 --- a/lib/containers/changed-file-container.js +++ b/lib/containers/changed-file-container.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import yubikiri from 'yubikiri'; +import {CompositeDisposable} from 'event-kit'; import {autobind} from '../helpers'; import ObserveModel from '../views/observe-model'; @@ -12,6 +13,7 @@ export default class ChangedFileContainer extends React.Component { repository: PropTypes.object.isRequired, stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), relPath: PropTypes.string.isRequired, + largeDiffThreshold: PropTypes.number, workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, @@ -27,13 +29,26 @@ export default class ChangedFileContainer extends React.Component { constructor(props) { super(props); autobind(this, 'fetchData', 'renderWithData'); + + this.lastMultiFilePatch = null; + this.sub = new CompositeDisposable(); + + this.state = {renderStatusOverride: null}; } fetchData(repository) { const staged = this.props.stagingStatus === 'staged'; + const builder = {}; + if (this.state.renderStatusOverride !== null) { + builder.renderStatusOverrides = {[this.props.relPath]: this.state.renderStatusOverride}; + } + if (this.props.largeDiffThreshold !== undefined) { + builder.largeDiffThreshold = this.props.largeDiffThreshold; + } + return yubikiri({ - multiFilePatch: repository.getFilePatchForPath(this.props.relPath, {staged}), + multiFilePatch: repository.getFilePatchForPath(this.props.relPath, {staged, builder}), isPartiallyStaged: repository.isPartiallyStaged(this.props.relPath), hasUndoHistory: repository.hasDiscardHistory(this.props.relPath), }); @@ -48,6 +63,20 @@ export default class ChangedFileContainer extends React.Component { } renderWithData(data) { + const currentMultiFilePatch = data && data.multiFilePatch; + if (currentMultiFilePatch !== this.lastMultiFilePatch) { + this.sub.dispose(); + if (currentMultiFilePatch) { + // Keep this component's renderStatusOverride synchronized with the FilePatch we're rendering + this.sub = new CompositeDisposable( + ...currentMultiFilePatch.getFilePatches().map(fp => fp.onDidChangeRenderStatus(() => { + this.setState({renderStatusOverride: fp.getRenderStatus()}); + })), + ); + } + this.lastMultiFilePatch = currentMultiFilePatch; + } + if (this.props.repository.isLoading() || data === null) { return ; } @@ -59,4 +88,8 @@ export default class ChangedFileContainer extends React.Component { /> ); } + + componentWillUnmount() { + this.sub.dispose(); + } } diff --git a/test/containers/changed-file-container.test.js b/test/containers/changed-file-container.test.js index dd8fbf01f7..80573cbdad 100644 --- a/test/containers/changed-file-container.test.js +++ b/test/containers/changed-file-container.test.js @@ -5,6 +5,7 @@ import {mount} from 'enzyme'; import ChangedFileContainer from '../../lib/containers/changed-file-container'; import ChangedFileItem from '../../lib/items/changed-file-item'; +import {TOO_LARGE, EXPANDED} from '../../lib/models/patch/patch'; import {cloneRepository, buildRepository} from '../helpers'; describe('ChangedFileContainer', function() { @@ -17,7 +18,7 @@ describe('ChangedFileContainer', function() { repository = await buildRepository(workdirPath); // a.txt: unstaged changes - await fs.writeFile(path.join(workdirPath, 'a.txt'), 'changed\n'); + await fs.writeFile(path.join(workdirPath, 'a.txt'), '0\n1\n2\n3\n4\n5\n'); // b.txt: staged changes await fs.writeFile(path.join(workdirPath, 'b.txt'), 'changed\n'); @@ -100,4 +101,22 @@ describe('ChangedFileContainer', function() { const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged', extra})); await assert.async.strictEqual(wrapper.update().find('MultiFilePatchView').prop('extra'), extra); }); + + it('remembers previously expanded large FilePatches', async function() { + const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged', largeDiffThreshold: 2})); + + await assert.async.isTrue(wrapper.update().exists('ChangedFileController')); + const before = wrapper.find('ChangedFileController').prop('multiFilePatch'); + assert.strictEqual(before.getFilePatches()[0].getRenderStatus(), TOO_LARGE); + + before.getFilePatches()[0].triggerDelayedRender(); + assert.strictEqual(before.getFilePatches()[0].getRenderStatus(), EXPANDED); + + repository.refresh(); + await assert.async.notStrictEqual(wrapper.update().find('ChangedFileController').prop('multiFilePatch'), before); + + const after = wrapper.find('ChangedFileController').prop('multiFilePatch'); + assert.notStrictEqual(after, before); + assert.strictEqual(after.getFilePatches()[0].getRenderStatus(), EXPANDED); + }); }); From 7eab1197df074d1066698e543e5fa053075f8aad Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Fri, 21 Dec 2018 12:42:51 -0800 Subject: [PATCH 1819/4053] add collapse button to file patch header --- lib/views/file-patch-header-view.js | 15 +++++++++++++++ styles/file-patch-view.less | 11 +++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 1425221120..a78b931750 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -4,6 +4,7 @@ import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; +import Octicon from '../atom/octicon'; import RefHolder from '../models/ref-holder'; import IssueishDetailItem from '../items/issueish-detail-item'; import ChangedFileItem from '../items/changed-file-item'; @@ -25,7 +26,10 @@ export default class FilePatchHeaderView extends React.Component { undoLastDiscard: PropTypes.func.isRequired, diveIntoMirrorPatch: PropTypes.func.isRequired, openFile: PropTypes.func.isRequired, + // should probably change 'toggleFile' to 'toggleFileStagingStatus' + // because the addition of another toggling function makes the old name confusing. toggleFile: PropTypes.func.isRequired, + toggleFileCollapse: PropTypes.func, itemType: ItemTypePropType.isRequired, }; @@ -42,12 +46,23 @@ export default class FilePatchHeaderView extends React.Component {
    {this.renderTitle()} + {this.renderCollapseButton()} {this.renderButtonGroup()}
    ); } + renderCollapseButton() { + return ( + + ); + } + renderTitle() { if (this.props.itemType === ChangedFileItem) { const status = this.props.stagingStatus; diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index a51fd5d820..cc9af4ed6e 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -64,6 +64,17 @@ margin-right: @component-padding; } + &-collapseButton { + background: transparent; + border: none; + // dont' judge me. flexbox seemed like overkill, ok? + float: right; + } + + &-collapseButtonIcon { + position: absolute; + } + &-container { flex: 1; display: flex; From b2512645f062256ea04361935b41c77c94de12a2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 15:03:17 -0500 Subject: [PATCH 1820/4053] Use a Marker instead of a Point to track delayed patch location --- lib/models/patch/builder.js | 10 +++++++--- lib/models/patch/file-patch.js | 6 +++--- lib/models/patch/patch.js | 29 ++++++++--------------------- 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 6c75fc5b0d..75e1f4d11f 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -121,12 +121,16 @@ function singleDiffFilePatch(diff, layeredBuffer, opts) { EXPANDED; if (renderStatus === TOO_LARGE) { - const insertPoint = new Point(layeredBuffer.buffer.getLastRow(), 0); + const insertRow = layeredBuffer.buffer.getLastRow(); + const patchMarker = layeredBuffer.layers.patch.markPosition( + [insertRow, 0], + {invalidate: 'never', exclusive: false}, + ); return FilePatch.createDelayedFilePatch( - oldFile, newFile, insertPoint, TOO_LARGE, + oldFile, newFile, patchMarker, TOO_LARGE, () => { - const [hunks, patchMarker] = buildHunks(diff, layeredBuffer, insertPoint.row); + const [hunks] = buildHunks(diff, layeredBuffer, insertRow, patchMarker); return new Patch({status: diff.status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); }, ); diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 230928226a..362f630ae2 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -9,8 +9,8 @@ export default class FilePatch { return new this(nullFile, nullFile, Patch.createNull()); } - static createDelayedFilePatch(oldFile, newFile, position, renderStatus, parseFn) { - return new this(oldFile, newFile, Patch.createDelayedPatch(position, renderStatus, parseFn)); + static createDelayedFilePatch(oldFile, newFile, marker, renderStatus, parseFn) { + return new this(oldFile, newFile, Patch.createDelayedPatch(marker, renderStatus, parseFn)); } constructor(oldFile, newFile, patch) { @@ -120,7 +120,7 @@ export default class FilePatch { } triggerDelayedRender() { - const nextPatch = this.patch.parseFn(); + const nextPatch = this.patch.parseFn(this.patch); if (nextPatch !== this.patch) { this.patch = nextPatch; this.didChangeRenderStatus(); diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index db4801bd45..99741312ca 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -14,17 +14,22 @@ export default class Patch { return new NullPatch(); } - static createDelayedPatch(position, renderStatus, parseFn) { - return new DelayedPatch(position, renderStatus, parseFn); + static createDelayedPatch(marker, renderStatus, parseFn) { + return new this({status: null, hunks: [], marker, renderStatus, parseFn}); } - constructor({status, hunks, marker, renderStatus}) { + constructor({status, hunks, marker, renderStatus, parseFn}) { this.status = status; this.hunks = hunks; this.marker = marker; this.renderStatus = renderStatus || EXPANDED; this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); + + if (parseFn) { + // Override the prototype's method + this.parseFn = parseFn; + } } getStatus() { @@ -392,24 +397,6 @@ class NullPatch { } } -// TODO: it prob shouldn't extend NullPatch -class DelayedPatch extends NullPatch { - constructor(position, renderStatus, parseFn) { - super(); - this.position = position; - this.renderStatus = renderStatus; - this.parseFn = parseFn; - } - - getStartRange() { - return new Range(this.position, this.position); - } - - getRenderStatus() { - return this.renderStatus; - } -} - class BufferBuilder { constructor(original, originalBaseOffset, nextLayeredBuffer) { this.originalBuffer = original; From 0bfacb6bbf77398e4ef71ccc73531f4644e1aa1a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 15:25:34 -0500 Subject: [PATCH 1821/4053] Track changed renderStatus in CommitPreviewContainer --- lib/containers/commit-preview-container.js | 39 ++++++++++++++++++- .../commit-preview-container.test.js | 30 ++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/lib/containers/commit-preview-container.js b/lib/containers/commit-preview-container.js index ef77f354fe..9b6cf49240 100644 --- a/lib/containers/commit-preview-container.js +++ b/lib/containers/commit-preview-container.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import yubikiri from 'yubikiri'; +import {CompositeDisposable} from 'event-kit'; import ObserveModel from '../views/observe-model'; import LoadingView from '../views/loading-view'; @@ -9,11 +10,27 @@ import CommitPreviewController from '../controllers/commit-preview-controller'; export default class CommitPreviewContainer extends React.Component { static propTypes = { repository: PropTypes.object.isRequired, + largeDiffThreshold: PropTypes.number, + } + + constructor(props) { + super(props); + + this.lastMultiFilePatch = null; + this.sub = new CompositeDisposable(); + + this.state = {renderStatusOverrides: {}}; } fetchData = repository => { + const builder = {renderStatusOverrides: this.state.renderStatusOverrides}; + + if (this.props.largeDiffThreshold !== undefined) { + builder.largeDiffThreshold = this.props.largeDiffThreshold; + } + return yubikiri({ - multiFilePatch: repository.getStagedChangesPatch(), + multiFilePatch: repository.getStagedChangesPatch({builder}), }); } @@ -26,6 +43,26 @@ export default class CommitPreviewContainer extends React.Component { } renderResult = data => { + const currentMultiFilePatch = data && data.multiFilePatch; + if (currentMultiFilePatch !== this.lastMultiFilePatch) { + this.sub.dispose(); + if (currentMultiFilePatch) { + this.sub = new CompositeDisposable( + ...currentMultiFilePatch.getFilePatches().map(fp => fp.onDidChangeRenderStatus(() => { + this.setState(prevState => { + return { + renderStatusOverrides: { + ...prevState.renderStatusOverrides, + [fp.getPath()]: fp.getRenderStatus(), + }, + }; + }); + })), + ); + } + this.lastMultiFilePatch = currentMultiFilePatch; + } + if (this.props.repository.isLoading() || data === null) { return ; } diff --git a/test/containers/commit-preview-container.test.js b/test/containers/commit-preview-container.test.js index c11a5a9164..c24002d0ae 100644 --- a/test/containers/commit-preview-container.test.js +++ b/test/containers/commit-preview-container.test.js @@ -1,8 +1,11 @@ import React from 'react'; import {mount} from 'enzyme'; +import fs from 'fs-extra'; +import path from 'path'; import CommitPreviewContainer from '../../lib/containers/commit-preview-container'; import CommitPreviewItem from '../../lib/items/commit-preview-item'; +import {TOO_LARGE, EXPANDED} from '../../lib/models/patch/patch'; import {cloneRepository, buildRepository} from '../helpers'; describe('CommitPreviewContainer', function() { @@ -71,4 +74,31 @@ describe('CommitPreviewContainer', function() { await assert.async.isTrue(wrapper.update().find('CommitPreviewController').exists()); assert.strictEqual(wrapper.find('CommitPreviewController').prop('multiFilePatch'), patch); }); + + it('remembers previously expanded large patches', async function() { + const wd = repository.getWorkingDirectoryPath(); + await repository.getLoadPromise(); + + await fs.writeFile(path.join(wd, 'file-0.txt'), '0\n1\n2\n3\n4\n5\n', {encoding: 'utf8'}); + await fs.writeFile(path.join(wd, 'file-1.txt'), '0\n1\n2\n', {encoding: 'utf8'}); + await repository.stageFiles(['file-0.txt', 'file-1.txt']); + + repository.refresh(); + + const wrapper = mount(buildApp({largeDiffThreshold: 3})); + await assert.async.isTrue(wrapper.update().exists('CommitPreviewController')); + + const before = wrapper.find('CommitPreviewController').prop('multiFilePatch'); + assert.strictEqual(before.getFilePatches()[0].getRenderStatus(), TOO_LARGE); + assert.strictEqual(before.getFilePatches()[1].getRenderStatus(), EXPANDED); + + before.getFilePatches()[0].triggerDelayedRender(); + repository.refresh(); + + await assert.async.notStrictEqual(wrapper.update().find('CommitPreviewController').prop('multiFilePatch'), before); + const after = wrapper.find('CommitPreviewController').prop('multiFilePatch'); + + assert.strictEqual(after.getFilePatches()[0].getRenderStatus(), EXPANDED); + assert.strictEqual(after.getFilePatches()[1].getRenderStatus(), EXPANDED); + }); }); From 40cde30b343b0e8b490ba4ce4e42447f06f68ebc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 16 Jan 2019 15:40:21 -0500 Subject: [PATCH 1822/4053] Ditto for CommitDetailContainer --- lib/containers/commit-detail-container.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/containers/commit-detail-container.js b/lib/containers/commit-detail-container.js index c624d127f7..eedf3370be 100644 --- a/lib/containers/commit-detail-container.js +++ b/lib/containers/commit-detail-container.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import yubikiri from 'yubikiri'; +import {CompositeDisposable} from 'event-kit'; import ObserveModel from '../views/observe-model'; import LoadingView from '../views/loading-view'; @@ -13,6 +14,13 @@ export default class CommitDetailContainer extends React.Component { itemType: PropTypes.func.isRequired, } + constructor(props) { + super(props); + + this.lastCommit = null; + this.sub = new CompositeDisposable(); + } + fetchData = repository => { return yubikiri({ commit: repository.getCommit(this.props.sha), @@ -31,6 +39,19 @@ export default class CommitDetailContainer extends React.Component { } renderResult = data => { + const currentCommit = data && data.commit; + if (currentCommit !== this.lastCommit) { + this.sub.dispose(); + if (currentCommit && currentCommit.isPresent()) { + this.sub = new CompositeDisposable( + ...currentCommit.getMultiFileDiff().getFilePatches().map(fp => fp.onDidChangeRenderStatus(() => { + this.forceUpdate(); + })), + ); + } + this.lastCommit = currentCommit; + } + if (this.props.repository.isLoading() || data === null || !data.commit.isPresent()) { return ; } From be2ab8f5deb6d9ca7d78965ab7bafda69ad51776 Mon Sep 17 00:00:00 2001 From: annthurium Date: Wed, 16 Jan 2019 13:50:05 -0800 Subject: [PATCH 1823/4053] fix bad parens in mfp builder --- lib/models/patch/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 75e1f4d11f..45bbcdc9c2 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -178,7 +178,7 @@ function dualDiffFilePatch(diff1, diff2, layeredBuffer, opts) { const oldFile = new File({path: filePath, mode: oldMode, symlink: oldSymlink}); const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink}); - if (isDiffLarge([diff1, diff2]), opts) { + if (isDiffLarge([diff1, diff2], opts)) { // TODO: do something with large diff too } From e28522ededfa992f89fab4032f9963f0469eff19 Mon Sep 17 00:00:00 2001 From: annthurium Date: Wed, 16 Jan 2019 15:10:17 -0800 Subject: [PATCH 1824/4053] don't render pull request comments if patch exceeds large diff threshold --- lib/models/patch/multi-file-patch.js | 15 +++++++++++++++ lib/views/multi-file-patch-view.js | 1 + lib/views/pr-review-comments-view.js | 3 +++ 3 files changed, 19 insertions(+) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index e21ae6158f..bf88e1fa32 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,6 +1,8 @@ import {TextBuffer, Range} from 'atom'; import {RBTree} from 'bintrees'; +import {TOO_LARGE} from './patch'; + export default class MultiFilePatch { constructor({buffer, layers, filePatches}) { this.buffer = buffer || null; @@ -339,6 +341,19 @@ export default class MultiFilePatch { return false; } + doesPatchExceedLargeDiffThreshold = filePatchPath => { + + const patches = this.getFilePatches(); + // can avoid iterating through all patches by adding a map + const patch = patches.filter(p => p.getPath() === filePatchPath)[0]; + // do I need to handle case where patch is null or something? + if (patch.getRenderStatus() === TOO_LARGE) { + return true; + } else { + return false; + } + } + getBufferRowForDiffPosition = (fileName, diffRow) => { const {startBufferRow, index} = this.diffRowOffsetIndices.get(fileName); const {offset} = index.lowerBound({diffRow}).data(); diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 54956b1bcc..05bcd3635f 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -321,6 +321,7 @@ export default class MultiFilePatchView extends React.Component { ); } else { diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index 80e89af272..af5feaece9 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -33,6 +33,9 @@ export default class PullRequestCommentsView extends React.Component { } const nativePath = toNativePathSep(rootComment.path); + if (this.props.doesPatchExceedLargeDiffThreshold(rootComment.path)) { + return null; + } const row = this.props.getBufferRowForDiffPosition(nativePath, rootComment.position); const point = new Point(row, 0); const range = new Range(point, point); From b66c88c3c5a33be85a2c97aab143d540dfc0c814 Mon Sep 17 00:00:00 2001 From: annthurium Date: Wed, 16 Jan 2019 15:16:47 -0800 Subject: [PATCH 1825/4053] use a Map to avoid iterating through patches --- lib/models/patch/multi-file-patch.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index bf88e1fa32..193b4cd803 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -17,6 +17,7 @@ export default class MultiFilePatch { this.filePatches = filePatches || []; this.filePatchesByMarker = new Map(); + this.filePatchesByPath = new Map(); this.hunksByMarker = new Map(); // Store a map of {diffRow, offset} for each FilePatch where offset is the number of Hunk headers within the current @@ -24,6 +25,7 @@ export default class MultiFilePatch { this.diffRowOffsetIndices = new Map(); for (const filePatch of this.filePatches) { + this.filePatchesByPath.set(filePatch.getPath(), filePatch); this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); let diffRow = 1; @@ -342,16 +344,9 @@ export default class MultiFilePatch { } doesPatchExceedLargeDiffThreshold = filePatchPath => { - - const patches = this.getFilePatches(); - // can avoid iterating through all patches by adding a map - const patch = patches.filter(p => p.getPath() === filePatchPath)[0]; + const patch = this.filePatchesByPath.get(filePatchPath); + return patch.getRenderStatus() === TOO_LARGE; // do I need to handle case where patch is null or something? - if (patch.getRenderStatus() === TOO_LARGE) { - return true; - } else { - return false; - } } getBufferRowForDiffPosition = (fileName, diffRow) => { From 98fbfc910e76cccc6359558c5370bfe28566686d Mon Sep 17 00:00:00 2001 From: annthurium Date: Wed, 16 Jan 2019 15:22:07 -0800 Subject: [PATCH 1826/4053] deal with null patches ...not sure how we would get into this situation, but it seems like an important case to cover. --- lib/models/patch/multi-file-patch.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 193b4cd803..be4f516e1b 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -345,8 +345,10 @@ export default class MultiFilePatch { doesPatchExceedLargeDiffThreshold = filePatchPath => { const patch = this.filePatchesByPath.get(filePatchPath); + if (!patch) { + return null; + } return patch.getRenderStatus() === TOO_LARGE; - // do I need to handle case where patch is null or something? } getBufferRowForDiffPosition = (fileName, diffRow) => { From ccf2a99fc04e917f0a112f8a56fc96e12f2c00c5 Mon Sep 17 00:00:00 2001 From: annthurium Date: Wed, 16 Jan 2019 15:26:08 -0800 Subject: [PATCH 1827/4053] :shirt: --- lib/models/patch/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 45bbcdc9c2..29fc3f6156 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -1,4 +1,4 @@ -import {TextBuffer, Point} from 'atom'; +import {TextBuffer} from 'atom'; import Hunk from './hunk'; import File, {nullFile} from './file'; From 26ee209b4a37d03d1f56a57d136751fbb56dc2da Mon Sep 17 00:00:00 2001 From: annthurium Date: Wed, 16 Jan 2019 15:27:36 -0800 Subject: [PATCH 1828/4053] danananana PROP TYPES --- lib/controllers/pr-reviews-controller.js | 1 + lib/views/pr-review-comments-view.js | 1 + test/controllers/pr-reviews-controller.test.js | 1 + 3 files changed, 3 insertions(+) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index d10839eb69..9c72522333 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -19,6 +19,7 @@ export default class PullRequestReviewsController extends React.Component { ), }), getBufferRowForDiffPosition: PropTypes.func.isRequired, + doesPatchExceedLargeDiffThreshold: PropTypes.func.isRequired, } constructor(props) { diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index af5feaece9..9268728284 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -23,6 +23,7 @@ export default class PullRequestCommentsView extends React.Component { })), getBufferRowForDiffPosition: PropTypes.func.isRequired, switchToIssueish: PropTypes.func.isRequired, + doesPatchExceedLargeDiffThreshold: PropTypes.func.isRequired, } render() { diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index 83cfa8eadd..b6080d5b3c 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -43,6 +43,7 @@ describe('PullRequestReviewsController', function() { switchToIssueish: () => {}, getBufferRowForDiffPosition: () => {}, + doesPatchExceedLargeDiffThreshold: () => {}, pullRequest: {reviews}, ...overrideProps, }; From 010cd56374443aeb7b95199af9c4e04b45a09e5c Mon Sep 17 00:00:00 2001 From: annthurium Date: Wed, 16 Jan 2019 16:31:34 -0800 Subject: [PATCH 1829/4053] also check for COLLAPSED patch state --- lib/controllers/pr-reviews-controller.js | 2 +- lib/models/patch/multi-file-patch.js | 7 ++++--- lib/views/multi-file-patch-view.js | 2 +- lib/views/pr-review-comments-view.js | 7 ++++--- test/controllers/pr-reviews-controller.test.js | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 9c72522333..f2a836fc32 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -19,7 +19,7 @@ export default class PullRequestReviewsController extends React.Component { ), }), getBufferRowForDiffPosition: PropTypes.func.isRequired, - doesPatchExceedLargeDiffThreshold: PropTypes.func.isRequired, + isPatchTooLargeOrCollapsed: PropTypes.func.isRequired, } constructor(props) { diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index be4f516e1b..0490c5fd12 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,7 +1,7 @@ import {TextBuffer, Range} from 'atom'; import {RBTree} from 'bintrees'; -import {TOO_LARGE} from './patch'; +import {TOO_LARGE, COLLAPSED} from './patch'; export default class MultiFilePatch { constructor({buffer, layers, filePatches}) { @@ -343,12 +343,13 @@ export default class MultiFilePatch { return false; } - doesPatchExceedLargeDiffThreshold = filePatchPath => { + isPatchTooLargeOrCollapsed = filePatchPath => { const patch = this.filePatchesByPath.get(filePatchPath); if (!patch) { return null; } - return patch.getRenderStatus() === TOO_LARGE; + const renderStatus = patch.getRenderStatus(); + return renderStatus === TOO_LARGE || renderStatus === COLLAPSED; } getBufferRowForDiffPosition = (fileName, diffRow) => { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 05bcd3635f..5554ac910c 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -321,7 +321,7 @@ export default class MultiFilePatchView extends React.Component { ); } else { diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index 9268728284..ca597f066e 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -23,7 +23,7 @@ export default class PullRequestCommentsView extends React.Component { })), getBufferRowForDiffPosition: PropTypes.func.isRequired, switchToIssueish: PropTypes.func.isRequired, - doesPatchExceedLargeDiffThreshold: PropTypes.func.isRequired, + isPatchTooLargeOrCollapsed: PropTypes.func.isRequired, } render() { @@ -33,10 +33,11 @@ export default class PullRequestCommentsView extends React.Component { return null; } - const nativePath = toNativePathSep(rootComment.path); - if (this.props.doesPatchExceedLargeDiffThreshold(rootComment.path)) { + // if file patch is collapsed or too large, do not render the comments + if (this.props.isPatchTooLargeOrCollapsed(rootComment.path)) { return null; } + const nativePath = toNativePathSep(rootComment.path); const row = this.props.getBufferRowForDiffPosition(nativePath, rootComment.position); const point = new Point(row, 0); const range = new Range(point, point); diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index b6080d5b3c..fcd44eacc8 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -43,7 +43,7 @@ describe('PullRequestReviewsController', function() { switchToIssueish: () => {}, getBufferRowForDiffPosition: () => {}, - doesPatchExceedLargeDiffThreshold: () => {}, + isPatchTooLargeOrCollapsed: () => {}, pullRequest: {reviews}, ...overrideProps, }; From cd2b7f982c119abf53066e67621404915f086b36 Mon Sep 17 00:00:00 2001 From: annthurium Date: Wed, 16 Jan 2019 16:56:09 -0800 Subject: [PATCH 1830/4053] honk if you like test helpers --- lib/views/pr-review-comments-view.js | 1 + test/views/pr-comments-view.test.js | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index ca597f066e..f31a9fe36b 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -11,6 +11,7 @@ import GithubDotcomMarkdown from './github-dotcom-markdown'; import Timeago from './timeago'; export default class PullRequestCommentsView extends React.Component { + // do we even need the relay props here? does not look like they are used static propTypes = { relay: PropTypes.shape({ hasMore: PropTypes.func.isRequired, diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index 15e2562626..7e32b755da 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -6,6 +6,23 @@ import {pullRequestBuilder} from '../builder/pr'; import PullRequestCommentsView, {PullRequestCommentView} from '../../lib/views/pr-review-comments-view'; describe('PullRequestCommentsView', function() { + function buildApp(multiFilePatch, pullRequest, overrideProps) { + const relay = { + hasMore: () => {}, + loadMore: () => {}, + isLoading: () => {}, + }; + return shallow( + {}} + {...overrideProps} + />, + ); + } it('adjusts the position for comments after hunk headers', function() { const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(fp => { @@ -31,7 +48,7 @@ describe('PullRequestCommentsView', function() { }) .build(); - const wrapper = shallow(); + const wrapper = buildApp(multiFilePatch, pr, {}); assert.deepEqual(wrapper.find('Marker').at(0).prop('bufferRange').serialize(), [[1, 0], [1, 0]]); assert.deepEqual(wrapper.find('Marker').at(1).prop('bufferRange').serialize(), [[12, 0], [12, 0]]); @@ -57,7 +74,7 @@ describe('PullRequestCommentsView', function() { }) .build(); - const wrapper = shallow(); + const wrapper = buildApp(multiFilePatch, pr, {}); const comments = wrapper.find('PullRequestCommentView'); assert.lengthOf(comments, 1); From 8324cb3080cd684a11bb1a7b23626c1380f408ab Mon Sep 17 00:00:00 2001 From: annthurium Date: Wed, 16 Jan 2019 17:00:15 -0800 Subject: [PATCH 1831/4053] clean up pr comments builder unnecessary console output during tests is a bummer. --- test/builder/pr.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/builder/pr.js b/test/builder/pr.js index 3616872b77..52c72a95dc 100644 --- a/test/builder/pr.js +++ b/test/builder/pr.js @@ -6,9 +6,10 @@ class CommentBuilder { this._authorLogin = 'someone'; this._authorAvatarUrl = 'https://avatars3.githubusercontent.com/u/17565?s=32&v=4'; this._url = 'https://github.com/atom/github/pull/1829/files#r242224689'; - this._createdAt = 0; + this._createdAt = '2018-12-27T17:51:17Z'; this._body = 'Lorem ipsum dolor sit amet, te urbanitas appellantur est.'; this._replyTo = null; + this._isMinimized = false; } id(i) { @@ -16,6 +17,11 @@ class CommentBuilder { return this; } + minmized(m) { + this._isMinimized = m; + return this; + } + path(p) { this._path = p; return this; @@ -69,6 +75,7 @@ class CommentBuilder { createdAt: this._createdAt, url: this._url, replyTo: this._replyTo, + isMinimized: this._isMinimized, }; } } From ee6c9c22f2204df05cdb988ed64d99d79649a5fa Mon Sep 17 00:00:00 2001 From: annthurium Date: Wed, 16 Jan 2019 17:10:30 -0800 Subject: [PATCH 1832/4053] [wip] add tests for `isPatchTooLargeOrCollapsed` --- test/models/patch/multi-file-patch.test.js | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index cad02eaffa..61fa236f7a 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -2,9 +2,12 @@ import dedent from 'dedent-js'; import {multiFilePatchBuilder, filePatchBuilder} from '../../builder/patch'; +import {TOO_LARGE} from '../../../lib/models/patch/patch'; import MultiFilePatch from '../../../lib/models/patch/multi-file-patch'; + import {assertInFilePatch} from '../../helpers'; + describe('MultiFilePatch', function() { it('creates an empty patch when constructed with no arguments', function() { const empty = new MultiFilePatch({}); @@ -704,6 +707,41 @@ describe('MultiFilePatch', function() { }); }); + describe('isPatchTooLargeOrCollapsed', function() { + let multiFilePatch; + beforeEach(function() { + multiFilePatch = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-0')); + // do we even give a shit about the hunks here? + fp.addHunk(h => h.unchanged('0').added('1').deleted('2', '3').unchanged('4')); + fp.addHunk(h => h.unchanged('5').added('6').unchanged('7')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('file-1')); + fp.addHunk(h => h.unchanged('8').deleted('9', '10').unchanged('11')); + }) + .build() + .multiFilePatch; + }); + it('returns true if patch exceeds large diff threshold', function() { + // todo: what is the best way to use the patch builder to build at patch that + // exceeds the large diff threshold? + }); + + it('returns true if patch is collapsed', function() { + }); + + it('returns false if patch does not exceed large diff threshold and is not collapsed', function() { + assert.isFalse(multiFilePatch.isPatchTooLargeOrCollapsed('file-1')); + }); + + it('returns null if patch does not exist', function() { + assert.isNull(multiFilePatch.isPatchTooLargeOrCollapsed('invalid-file-path')); + }); + }); + + describe('diff position translation', function() { it('offsets rows in the first hunk by the first hunk header', function() { const {multiFilePatch} = multiFilePatchBuilder() From 49d12128b36ddf3bd0ea36be9db7bc2f8efe5cf1 Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 17 Jan 2019 17:21:59 +0900 Subject: [PATCH 1833/4053] Replace PR list with progress bars --- docs/feature-requests/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 2fc225648d..4d30fdd02b 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -18,7 +18,7 @@ Peer review is also a critical part of the path to acceptance for pull requests ### Pull Request list -![image](https://user-images.githubusercontent.com/378023/46524357-89bf6580-c8c3-11e8-8e2d-ea02d5a1f278.png) +![image](https://user-images.githubusercontent.com/378023/51304737-4658c380-1a7c-11e9-8edb-7ceafeedabe5.png) * Review progress is indicated for open pull requests listed in the GitHub panel. * The pull request corresponding to the checked out branch gets special treatment in its own section at the top of the list. From 6c6a5b6050856bd0df91e329f3efda3e3801c1aa Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 17 Jan 2019 17:31:08 +0900 Subject: [PATCH 1834/4053] Replace PR list actions --- docs/feature-requests/003-pull-request-review.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 4d30fdd02b..6cc1655e77 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -23,10 +23,10 @@ Peer review is also a critical part of the path to acceptance for pull requests * Review progress is indicated for open pull requests listed in the GitHub panel. * The pull request corresponding to the checked out branch gets special treatment in its own section at the top of the list. -![center pane](https://user-images.githubusercontent.com/378023/46985265-75c9fe00-d124-11e8-9b34-572cd1aaf701.png) +![center pane](https://user-images.githubusercontent.com/378023/51305096-45746180-1a7d-11e9-801b-37b3ab0c862a.png) * Clicking a pull request in the list opens a `PullRequestDetailItem` in the workspace center. -* Clicking the "Reviews" label or progress bar opens a `PullRequestReviewsItem` in the right dock. +* Clicking the progress bar opens a `PullRequestReviewsItem` in the left dock. ### PullRequestDetailItem From b72a00655442b3026d78d8e77bf4299849df2e10 Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 17 Jan 2019 17:35:29 +0900 Subject: [PATCH 1835/4053] Replace header and footer Plus remove outdated mockups --- docs/feature-requests/003-pull-request-review.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 6cc1655e77..a99d9e2a4a 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -32,7 +32,7 @@ Peer review is also a critical part of the path to acceptance for pull requests #### Header -![header](https://user-images.githubusercontent.com/378023/46536358-3829d180-c8e9-11e8-9167-3d1003ab566b.png) +![header](https://user-images.githubusercontent.com/378023/51305325-e400c280-1a7d-11e9-9b4e-b9cf2d326dd5.png) At the top of each `PullRequestDetailItem` is a summary about the pull request, followed by the tabs to switch between different sub-views. @@ -45,15 +45,9 @@ Below the tabs is a "tools bar" with controls to toggle review comments or colla #### Footer -> TODO: Add "open" button +![reviews panel](https://user-images.githubusercontent.com/378023/51305353-f2e77500-1a7d-11e9-96f0-da879d49da3a.png) -![reviews panel](https://user-images.githubusercontent.com/378023/46536010-17ad4780-c8e8-11e8-8338-338bb592efc5.png) - -A panel at the bottom of the pane shows the progress for resolved review comments. It also has a "Review Changes" button to create a new review. This panel is persistent throughout all sub-views. It allows creating new reviews no matter where you are. Below examples with the existing sub-views: - -Overview | Commits | Build Status ---- | --- | --- -![overview](https://user-images.githubusercontent.com/378023/46535907-ca30da80-c8e7-11e8-9401-2b8660d56c25.png) | ![commits](https://user-images.githubusercontent.com/378023/46535908-ca30da80-c8e7-11e8-87ca-01637f2554b6.png) | ![build status](https://user-images.githubusercontent.com/378023/46535909-cac97100-c8e7-11e8-8813-47fdaa3ece57.png) +A panel at the bottom of the pane shows the progress for resolved review comments. It also has a "Review Changes" button to create a new review. This panel is persistent throughout all sub-views. It allows creating new reviews no matter where you are. When the pull request is checked out, an "open" button is shown in the review footer. Clicking "open" opens a `PullRequestReviewsItem` for this pull request's review comments as an item in the right workspace dock. From 5ca37fb4196e0489d6137fed740099823badac97 Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 17 Jan 2019 17:54:33 +0900 Subject: [PATCH 1836/4053] Update "Files" mockups --- docs/feature-requests/003-pull-request-review.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index a99d9e2a4a..2b22be338d 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -55,15 +55,15 @@ When the pull request is checked out, an "open" button is shown in the review fo Clicking on the "Files Changed" tab displays the full, multi-file diff associated with the pull request. This is akin to the "Files changed" tab on dotcom. -> TODO: Change "Show review comments" checkbox to an expand/collapse review summaries control - -![files](https://user-images.githubusercontent.com/378023/46536560-d3bb4200-c8e9-11e8-9764-dca0b84245cf.png) +![files](https://user-images.githubusercontent.com/378023/51305826-43ab9d80-1a7f-11e9-8b41-42bc4812d214.png) Clicking on the "Expand review summaries" control in the filter bar reveals an inline panel that displays the summary of each review created on this pull request, including the review's author, the review's current state, its summary comment, and a progress bar showing how many of the review comments associated with this review have been marked as resolved. -![review summary list panel](https://user-images.githubusercontent.com/17565/50930369-e172ed00-142d-11e9-8ae4-00106dde80f5.png) +![review summary list panel](https://user-images.githubusercontent.com/378023/51305827-43ab9d80-1a7f-11e9-90be-902e541b805f.png) + +Clicking on "Comments" within each review summary block hides or reveals the review summary comments associated with that review in diff on this tab. Clicking the "Collapse review summaries" control conceals the review summary panel again. -Clicking the checkbox within each review summary block hides or reveals the review summary comments associated with that review in diff on this tab. Clicking the "Collapse review summaries" control conceals the review summary panel again. +![review summary list panel with comments](https://user-images.githubusercontent.com/378023/51306485-ca14af00-1a80-11e9-8e52-bb4d55928736.png) Beneath the review summary panel is the pull request's combined diff. Diffs are editable, but _only_ if the pull request branch is checked out and the local branch history has not diverged incompatibly from the remote branch history. From dccb70a740d932fa658524d885c238839e8be907 Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 17 Jan 2019 17:58:30 +0900 Subject: [PATCH 1837/4053] Replace pull request reviews item --- docs/feature-requests/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 2b22be338d..eb03b8e8e2 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -124,7 +124,7 @@ This item is opened in the workspace's right dock when the user: It shows a scrollable view of all of the reviews and comments associated with a specific pull request, -![pull request reviews item](https://user-images.githubusercontent.com/17565/50931285-213ad400-1430-11e9-8dd2-bd0cc98216fa.png) +![pull request reviews item](https://user-images.githubusercontent.com/378023/51306720-6939a680-1a81-11e9-8eaa-ffd480e3d47f.png) Reviews are sorted by "urgency," showing reviews that still need to be addressed at the top. Within each group, sorting is done by "newest first". From 07336769f5acd64b799c1cb05da33906253cd1fa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 17 Jan 2019 08:35:54 -0500 Subject: [PATCH 1838/4053] Test case for Patch Marker movement --- test/models/patch/builder.test.js | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 59951c2f63..d917d8af36 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -984,6 +984,50 @@ describe('buildFilePatch', function() { ); }); + it('preserves patch markers when a delayed patch is expanded', function() { + const mfp = buildMultiFilePatch([ + { + oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100644', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 3, + lines: [' line-0', '+line-1', '-line-2', ' line-3'], + }, + ], + }, + { + oldPath: 'second', oldMode: '100644', newPath: 'second', newMode: '100644', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 3, + lines: [' line-0', '+line-1', '-line-2', ' line-3'], + }, + ], + }, + { + oldPath: 'third', oldMode: '100644', newPath: 'third', newMode: '100644', status: 'modified', + hunks: [ + { + oldStartLine: 1, oldLineCount: 3, newStartLine: 1, newLineCount: 3, + lines: [' line-0', '+line-1', '-line-2', ' line-3'], + }, + ], + }, + ], {largeDiffThreshold: 3}); + + assert.lengthOf(mfp.getFilePatches(), 3); + const [fp0, fp1, fp2] = mfp.getFilePatches(); + assert.deepEqual(fp0.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); + assert.deepEqual(fp1.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); + assert.deepEqual(fp2.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); + + fp1.triggerDelayedRender(); + + assert.deepEqual(fp0.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); + assert.deepEqual(fp1.getMarker().getRange().serialize(), [[0, 0], [3, 6]]); + assert.deepEqual(fp2.getMarker().getRange().serialize(), [[4, 0], [4, 0]]); + }); + it('does not create a DelayedPatch when the patch has been explicitly expanded', function() { const mfp = buildMultiFilePatch([ { From 1237458dec35cc4ee510bf3933a65374c9e981bf Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 17 Jan 2019 10:23:21 -0500 Subject: [PATCH 1839/4053] Extract LayeredBuffer class and use it consistently in Patch logic --- lib/models/patch/builder.js | 84 ++++++++------------ lib/models/patch/hunk.js | 2 + lib/models/patch/layered-buffer.js | 43 ++++++++++ lib/models/patch/multi-file-patch.js | 112 +++++++++------------------ lib/models/patch/patch.js | 76 +++++++++--------- lib/models/patch/region.js | 8 ++ test/builder/patch.js | 85 ++++++++------------ test/models/patch/builder.test.js | 4 +- 8 files changed, 193 insertions(+), 221 deletions(-) create mode 100644 lib/models/patch/layered-buffer.js diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 29fc3f6156..6a308b2f7d 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -1,5 +1,4 @@ -import {TextBuffer} from 'atom'; - +import LayeredBuffer from './layered-buffer'; import Hunk from './hunk'; import File, {nullFile} from './file'; import Patch, {TOO_LARGE, EXPANDED} from './patch'; @@ -10,7 +9,7 @@ import MultiFilePatch from './multi-file-patch'; const DEFAULT_OPTIONS = { // Number of lines after which we consider the diff "large" // TODO: Set this based on performance measurements - largeDiffThreshold: 10, + largeDiffThreshold: 100, // Map of file path (relative to repository root) to Patch render status (EXPANDED, COLLAPSED, TOO_LARGE) renderStatusOverrides: {}, @@ -19,7 +18,7 @@ const DEFAULT_OPTIONS = { export function buildFilePatch(diffs, options) { const opts = {...DEFAULT_OPTIONS, ...options}; - const layeredBuffer = initializeBuffer(); + const layeredBuffer = new LayeredBuffer(); let filePatch; if (diffs.length === 0) { @@ -32,13 +31,13 @@ export function buildFilePatch(diffs, options) { throw new Error(`Unexpected number of diffs: ${diffs.length}`); } - return new MultiFilePatch({filePatches: [filePatch], ...layeredBuffer}); + return new MultiFilePatch({layeredBuffer, filePatches: [filePatch]}); } export function buildMultiFilePatch(diffs, options) { const opts = {...DEFAULT_OPTIONS, ...options}; - const layeredBuffer = initializeBuffer(); + const layeredBuffer = new LayeredBuffer(); const byPath = new Map(); const actions = []; @@ -82,7 +81,7 @@ export function buildMultiFilePatch(diffs, options) { } }); - return new MultiFilePatch({filePatches, ...layeredBuffer}); + return new MultiFilePatch({layeredBuffer, filePatches}); } function emptyDiffFilePatch() { @@ -121,8 +120,9 @@ function singleDiffFilePatch(diff, layeredBuffer, opts) { EXPANDED; if (renderStatus === TOO_LARGE) { - const insertRow = layeredBuffer.buffer.getLastRow(); - const patchMarker = layeredBuffer.layers.patch.markPosition( + const insertRow = layeredBuffer.getBuffer().getLastRow(); + const patchMarker = layeredBuffer.markPosition( + Patch.layerName, [insertRow, 0], {invalidate: 'never', exclusive: false}, ); @@ -131,12 +131,12 @@ function singleDiffFilePatch(diff, layeredBuffer, opts) { oldFile, newFile, patchMarker, TOO_LARGE, () => { const [hunks] = buildHunks(diff, layeredBuffer, insertRow, patchMarker); - return new Patch({status: diff.status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); + return new Patch({status: diff.status, hunks, marker: patchMarker}); }, ); } else { - const [hunks, patchMarker] = buildHunks(diff, layeredBuffer, layeredBuffer.buffer.getLastRow()); - const patch = new Patch({status: diff.status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); + const [hunks, patchMarker] = buildHunks(diff, layeredBuffer, layeredBuffer.getBuffer().getLastRow()); + const patch = new Patch({status: diff.status, hunks, marker: patchMarker}); return new FilePatch(oldFile, newFile, patch); } @@ -182,8 +182,8 @@ function dualDiffFilePatch(diff1, diff2, layeredBuffer, opts) { // TODO: do something with large diff too } - const [hunks, patchMarker] = buildHunks(contentChangeDiff, layeredBuffer, layeredBuffer.buffer.getLastRow()); - const patch = new Patch({status, hunks, marker: patchMarker, buffer: layeredBuffer.buffer}); + const [hunks, patchMarker] = buildHunks(contentChangeDiff, layeredBuffer, layeredBuffer.getBuffer().getLastRow()); + const patch = new Patch({status, hunks, marker: patchMarker}); return new FilePatch(oldFile, newFile, patch); } @@ -195,41 +195,18 @@ const CHANGEKIND = { '\\': NoNewline, }; -function initializeBuffer() { - const buffer = new TextBuffer(); - buffer.retain(); - - const layers = ['patch', 'hunk', 'unchanged', 'addition', 'deletion', 'noNewline'].reduce((obj, key) => { - obj[key] = buffer.addMarkerLayer(); - return obj; - }, {}); - - return {buffer, layers}; -} - -function buildHunks(diff, {buffer, layers}, insertRow) { - const layersByKind = new Map([ - [Unchanged, layers.unchanged], - [Addition, layers.addition], - [Deletion, layers.deletion], - [NoNewline, layers.noNewline], - ]); +function buildHunks(diff, layeredBuffer, insertRow, existingMarker = null) { const hunks = []; - const afterMarkers = []; - for (const layerName in layers) { - const layer = layers[layerName]; - afterMarkers.push(...layer.findMarkers({startPosition: [insertRow, 0]})); - } - const patchStartRow = insertRow; let bufferRow = patchStartRow; let nextLineLength = 0; if (diff.hunks.length === 0) { - const patchMarker = layers.patch.markPosition( + const patchMarker = layeredBuffer.markPosition( + Patch.layerName, [patchStartRow, 0], - {invalidate: 'never', exclusive: false}, + {invalidate: 'never', exclusive: true}, ); return [hunks, patchMarker]; @@ -251,7 +228,8 @@ function buildHunks(diff, {buffer, layers}, insertRow) { regions.push( new LastChangeKind( - layersByKind.get(LastChangeKind).markRange( + layeredBuffer.markRange( + LastChangeKind.layerName, [[currentRangeStart, 0], [bufferRow - 1, lastLineLength]], {invalidate: 'never', exclusive: false}, ), @@ -263,7 +241,7 @@ function buildHunks(diff, {buffer, layers}, insertRow) { for (const lineText of hunkData.lines) { const bufferLine = lineText.slice(1) + '\n'; nextLineLength = lineText.length - 1; - buffer.insert([bufferRow, 0], bufferLine); + layeredBuffer.getBuffer().insert([bufferRow, 0], bufferLine); const ChangeKind = CHANGEKIND[lineText[0]]; if (ChangeKind === undefined) { @@ -286,7 +264,8 @@ function buildHunks(diff, {buffer, layers}, insertRow) { oldRowCount: hunkData.oldLineCount, newRowCount: hunkData.newLineCount, sectionHeading: hunkData.heading, - marker: layers.hunk.markRange( + marker: layeredBuffer.markRange( + Hunk.layerName, [[bufferStartRow, 0], [bufferRow - 1, nextLineLength]], {invalidate: 'never', exclusive: false}, ), @@ -294,14 +273,15 @@ function buildHunks(diff, {buffer, layers}, insertRow) { })); } - const patchMarker = layers.patch.markRange( - [[patchStartRow, 0], [bufferRow - 1, nextLineLength]], - {invalidate: 'never', exclusive: false}, - ); - - // Correct any markers that used to start at the insertion point. - for (const marker of afterMarkers) { - marker.setTailPosition([bufferRow, 0]); + let patchMarker = existingMarker; + if (patchMarker) { + patchMarker.setRange([[patchStartRow, 0], [bufferRow - 1, nextLineLength]], {exclusive: false}); + } else { + patchMarker = layeredBuffer.markRange( + Patch.layerName, + [[patchStartRow, 0], [bufferRow - 1, nextLineLength]], + {invalidate: 'never', exclusive: false}, + ); } return [hunks, patchMarker]; diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 84cab39002..074f86f7f3 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -1,4 +1,6 @@ export default class Hunk { + static layerName = 'hunk'; + constructor({ oldStartRow, newStartRow, diff --git a/lib/models/patch/layered-buffer.js b/lib/models/patch/layered-buffer.js new file mode 100644 index 0000000000..2265ae7899 --- /dev/null +++ b/lib/models/patch/layered-buffer.js @@ -0,0 +1,43 @@ +import {TextBuffer} from 'atom'; + +const LAYER_NAMES = ['unchanged', 'addition', 'deletion', 'nonewline', 'hunk', 'patch']; + +export default class LayeredBuffer { + constructor() { + this.buffer = new TextBuffer(); + this.layers = LAYER_NAMES.reduce((map, layerName) => { + map[layerName] = this.buffer.addMarkerLayer(); + return map; + }, {}); + } + + getBuffer() { + return this.buffer; + } + + getInsertionPoint() { + return this.buffer.getEndPosition(); + } + + getLayer(layerName) { + return this.layers[layerName]; + } + + findMarkers(layerName, ...args) { + return this.layers[layerName].findMarkers(...args); + } + + markPosition(layerName, ...args) { + return this.layers[layerName].markPosition(...args); + } + + markRange(layerName, ...args) { + return this.layers[layerName].markRange(...args); + } + + clearAllLayers() { + for (const layerName of LAYER_NAMES) { + this.layers[layerName].clear(); + } + } +} diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 0490c5fd12..3e5a85c602 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,20 +1,17 @@ -import {TextBuffer, Range} from 'atom'; +import {Range} from 'atom'; import {RBTree} from 'bintrees'; +import LayeredBuffer from './layered-buffer'; import {TOO_LARGE, COLLAPSED} from './patch'; export default class MultiFilePatch { - constructor({buffer, layers, filePatches}) { - this.buffer = buffer || null; - - this.patchLayer = layers && layers.patch; - this.hunkLayer = layers && layers.hunk; - this.unchangedLayer = layers && layers.unchanged; - this.additionLayer = layers && layers.addition; - this.deletionLayer = layers && layers.deletion; - this.noNewlineLayer = layers && layers.noNewline; + static createNull() { + return new this({layeredBuffer: new LayeredBuffer(), filePatches: []}); + } - this.filePatches = filePatches || []; + constructor({layeredBuffer, filePatches}) { + this.layeredBuffer = layeredBuffer; + this.filePatches = filePatches; this.filePatchesByMarker = new Map(); this.filePatchesByPath = new Map(); @@ -48,45 +45,41 @@ export default class MultiFilePatch { clone(opts = {}) { return new this.constructor({ - buffer: opts.buffer !== undefined ? opts.buffer : this.getBuffer(), - layers: opts.layers !== undefined ? opts.layers : { - patch: this.getPatchLayer(), - hunk: this.getHunkLayer(), - unchanged: this.getUnchangedLayer(), - addition: this.getAdditionLayer(), - deletion: this.getDeletionLayer(), - noNewline: this.getNoNewlineLayer(), - }, + layeredBuffer: opts.layeredBuffer !== undefined ? opts.layeredBuffer : this.getLayeredBuffer(), filePatches: opts.filePatches !== undefined ? opts.filePatches : this.getFilePatches(), }); } + getLayeredBuffer() { + return this.layeredBuffer; + } + getBuffer() { - return this.buffer; + return this.getLayeredBuffer().getBuffer(); } getPatchLayer() { - return this.patchLayer; + return this.getLayeredBuffer().getLayer('patch'); } getHunkLayer() { - return this.hunkLayer; + return this.getLayeredBuffer().getLayer('hunk'); } getUnchangedLayer() { - return this.unchangedLayer; + return this.getLayeredBuffer().getLayer('unchanged'); } getAdditionLayer() { - return this.additionLayer; + return this.getLayeredBuffer().getLayer('addition'); } getDeletionLayer() { - return this.deletionLayer; + return this.getLayeredBuffer().getLayer('deletion'); } getNoNewlineLayer() { - return this.noNewlineLayer; + return this.getLayeredBuffer().getLayer('nonewline'); } getFilePatches() { @@ -108,7 +101,7 @@ export default class MultiFilePatch { if (bufferRow < 0) { return undefined; } - const [marker] = this.patchLayer.findMarkers({intersectsRow: bufferRow}); + const [marker] = this.layeredBuffer.findMarkers('patch', {intersectsRow: bufferRow}); return this.filePatchesByMarker.get(marker); } @@ -116,16 +109,16 @@ export default class MultiFilePatch { if (bufferRow < 0) { return undefined; } - const [marker] = this.hunkLayer.findMarkers({intersectsRow: bufferRow}); + const [marker] = this.layeredBuffer.findMarkers('hunk', {intersectsRow: bufferRow}); return this.hunksByMarker.get(marker); } getStagePatchForLines(selectedLineSet) { - const nextLayeredBuffer = this.buildLayeredBuffer(); + const nextLayeredBuffer = new LayeredBuffer(); const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { return fp.buildStagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); }); - return this.clone({...nextLayeredBuffer, filePatches: nextFilePatches}); + return this.clone({layeredBuffer: nextLayeredBuffer, filePatches: nextFilePatches}); } getStagePatchForHunk(hunk) { @@ -133,11 +126,11 @@ export default class MultiFilePatch { } getUnstagePatchForLines(selectedLineSet) { - const nextLayeredBuffer = this.buildLayeredBuffer(); + const nextLayeredBuffer = new LayeredBuffer(); const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { return fp.buildUnstagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); }); - return this.clone({...nextLayeredBuffer, filePatches: nextFilePatches}); + return this.clone({layeredBuffer: nextLayeredBuffer, filePatches: nextFilePatches}); } getUnstagePatchForHunk(hunk) { @@ -220,64 +213,29 @@ export default class MultiFilePatch { } adoptBufferFrom(lastMultiFilePatch) { - lastMultiFilePatch.getPatchLayer().clear(); - lastMultiFilePatch.getHunkLayer().clear(); - lastMultiFilePatch.getUnchangedLayer().clear(); - lastMultiFilePatch.getAdditionLayer().clear(); - lastMultiFilePatch.getDeletionLayer().clear(); - lastMultiFilePatch.getNoNewlineLayer().clear(); + const nextLayeredBuffer = lastMultiFilePatch.getLayeredBuffer(); + nextLayeredBuffer.clearAllLayers(); this.filePatchesByMarker.clear(); this.hunksByMarker.clear(); - const nextBuffer = lastMultiFilePatch.getBuffer(); - nextBuffer.setText(this.getBuffer().getText()); + nextLayeredBuffer.getBuffer().setText(this.getBuffer().getText()); for (const filePatch of this.getFilePatches()) { - filePatch.getPatch().reMarkOn(lastMultiFilePatch.getPatchLayer()); + filePatch.getPatch().reMarkOn(nextLayeredBuffer); this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); for (const hunk of filePatch.getHunks()) { - hunk.reMarkOn(lastMultiFilePatch.getHunkLayer()); + hunk.reMarkOn(nextLayeredBuffer); this.hunksByMarker.set(hunk.getMarker(), hunk); for (const region of hunk.getRegions()) { - const target = region.when({ - unchanged: () => lastMultiFilePatch.getUnchangedLayer(), - addition: () => lastMultiFilePatch.getAdditionLayer(), - deletion: () => lastMultiFilePatch.getDeletionLayer(), - nonewline: () => lastMultiFilePatch.getNoNewlineLayer(), - }); - region.reMarkOn(target); + region.reMarkOn(nextLayeredBuffer); } } } - this.patchLayer = lastMultiFilePatch.getPatchLayer(); - this.hunkLayer = lastMultiFilePatch.getHunkLayer(); - this.unchangedLayer = lastMultiFilePatch.getUnchangedLayer(); - this.additionLayer = lastMultiFilePatch.getAdditionLayer(); - this.deletionLayer = lastMultiFilePatch.getDeletionLayer(); - this.noNewlineLayer = lastMultiFilePatch.getNoNewlineLayer(); - - this.buffer = nextBuffer; - } - - buildLayeredBuffer() { - const buffer = new TextBuffer(); - buffer.retain(); - - return { - buffer, - layers: { - patch: buffer.addMarkerLayer(), - hunk: buffer.addMarkerLayer(), - unchanged: buffer.addMarkerLayer(), - addition: buffer.addMarkerLayer(), - deletion: buffer.addMarkerLayer(), - noNewline: buffer.addMarkerLayer(), - }, - }; + this.layeredBuffer = nextLayeredBuffer; } /* @@ -304,7 +262,7 @@ export default class MultiFilePatch { } anyPresent() { - return this.buffer !== null && this.filePatches.some(fp => fp.isPresent()); + return this.layeredBuffer !== null && this.filePatches.some(fp => fp.isPresent()); } didAnyChangeExecutableMode() { @@ -362,7 +320,7 @@ export default class MultiFilePatch { * Construct an apply-able patch String. */ toString() { - return this.filePatches.map(fp => fp.toStringIn(this.buffer)).join(''); + return this.filePatches.map(fp => fp.toStringIn(this.getBuffer())).join(''); } isEqual(other) { diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 99741312ca..b5fc8209e5 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -3,13 +3,27 @@ import {TextBuffer, Range} from 'atom'; import Hunk from './hunk'; import {Unchanged, Addition, Deletion, NoNewline} from './region'; -export const TOO_LARGE = Symbol('too large'); +export const EXPANDED = { + toString() { return 'RenderStatus(expanded)'; }, -export const EXPANDED = Symbol('expanded'); + isVisible() { return true; }, +}; -export const COLLAPSED = Symbol('collapsed'); +export const COLLAPSED = { + toString() { return 'RenderStatus(collapsed)'; }, + + isVisible() { return false; }, +}; + +export const TOO_LARGE = { + toString() { return 'RenderStatus(too-large)'; }, + + isVisible() { return false; }, +}; export default class Patch { + static layerName = 'patch'; + static createNull() { return new NullPatch(); } @@ -61,8 +75,12 @@ export default class Patch { return this.marker.getRange().intersectsRow(row); } - reMarkOn(markable) { - this.marker = markable.markRange(this.getRange(), {invalidate: 'never', exclusive: false}); + reMarkOn(layeredBuffer) { + this.marker = layeredBuffer.markRange( + this.constructor.layerName, + this.getRange(), + {invalidate: 'never', exclusive: false}, + ); } getMaxLineNumberWidth() { @@ -174,9 +192,11 @@ export default class Patch { } } - const buffer = builder.getBuffer(); - const layers = builder.getLayers(); - const marker = layers.patch.markRange([[0, 0], [buffer.getLastRow() - 1, Infinity]]); + const marker = nextLayeredBuffer.markRange( + this.constructor.layerName, + [[0, 0], [nextLayeredBuffer.getBuffer().getLastRow() - 1, Infinity]], + {invalidate: 'never', exclusive: false}, + ); const wholeFile = rowSet.size === this.changedLineCount; const status = this.getStatus() === 'deleted' && !wholeFile ? 'modified' : this.getStatus(); @@ -269,9 +289,11 @@ export default class Patch { status = 'added'; } - const buffer = builder.getBuffer(); - const layers = builder.getLayers(); - const marker = layers.patch.markRange([[0, 0], [buffer.getLastRow(), Infinity]]); + const marker = nextLayeredBuffer.markRange( + this.constructor.layerName, + [[0, 0], [nextLayeredBuffer.getBuffer().getLastRow(), Infinity]], + {invalidate: 'never', exclusive: false}, + ); return this.clone({hunks, status, marker}); } @@ -400,16 +422,7 @@ class NullPatch { class BufferBuilder { constructor(original, originalBaseOffset, nextLayeredBuffer) { this.originalBuffer = original; - - this.buffer = nextLayeredBuffer.buffer; - this.layers = new Map([ - [Unchanged, nextLayeredBuffer.layers.unchanged], - [Addition, nextLayeredBuffer.layers.addition], - [Deletion, nextLayeredBuffer.layers.deletion], - [NoNewline, nextLayeredBuffer.layers.noNewline], - ['hunk', nextLayeredBuffer.layers.hunk], - ['patch', nextLayeredBuffer.layers.patch], - ]); + this.nextLayeredBuffer = nextLayeredBuffer; // The ranges provided to builder methods are expected to be valid within the original buffer. Account for // the position of the Patch within its original TextBuffer, and any existing content already on the next @@ -457,15 +470,15 @@ class BufferBuilder { } latestHunkWasIncluded() { - this.buffer.append(this.hunkBufferText, {normalizeLineEndings: false}); + this.nextLayeredBuffer.getBuffer().append(this.hunkBufferText, {normalizeLineEndings: false}); const regions = this.hunkRegions.map(({RegionKind, range}) => { return new RegionKind( - this.layers.get(RegionKind).markRange(range, {invalidate: 'never', exclusive: false}), + this.nextLayeredBuffer.getLayer(RegionKind.layerName, range, {invalidate: 'never', exclusive: false}), ); }); - const marker = this.layers.get('hunk').markRange(this.hunkRange, {invalidate: 'never', exclusive: false}); + const marker = this.nextLayeredBuffer.markRange('hunk', this.hunkRange, {invalidate: 'never', exclusive: false}); this.hunkBufferText = ''; this.hunkRowCount = 0; @@ -488,18 +501,7 @@ class BufferBuilder { return {regions: [], marker: null}; } - getBuffer() { - return this.buffer; - } - - getLayers() { - return { - patch: this.layers.get('patch'), - hunk: this.layers.get('hunk'), - unchanged: this.layers.get(Unchanged), - addition: this.layers.get(Addition), - deletion: this.layers.get(Deletion), - noNewline: this.layers.get(NoNewline), - }; + getLayeredBuffer() { + return this.nextLayeredBuffer; } } diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index 3afaef844d..d246b911bc 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -125,6 +125,8 @@ class Region { export class Addition extends Region { static origin = '+'; + static layerName = 'addition'; + isAddition() { return true; } @@ -137,6 +139,8 @@ export class Addition extends Region { export class Deletion extends Region { static origin = '-'; + static layerName = 'deletion'; + isDeletion() { return true; } @@ -149,6 +153,8 @@ export class Deletion extends Region { export class Unchanged extends Region { static origin = ' '; + static layerName = 'unchanged'; + isUnchanged() { return true; } @@ -165,6 +171,8 @@ export class Unchanged extends Region { export class NoNewline extends Region { static origin = '\\'; + static layerName = 'nonewline'; + isNoNewline() { return true; } diff --git a/test/builder/patch.js b/test/builder/patch.js index c2e0e830ab..6da24e5f29 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -1,6 +1,6 @@ // Builders for classes related to MultiFilePatches. -import {TextBuffer} from 'atom'; +import LayeredBuffer from '../../lib/models/patch/layered-buffer'; import MultiFilePatch from '../../lib/models/patch/multi-file-patch'; import FilePatch from '../../lib/models/patch/file-patch'; import File, {nullFile} from '../../lib/models/patch/file'; @@ -8,49 +8,29 @@ import Patch from '../../lib/models/patch/patch'; import Hunk from '../../lib/models/patch/hunk'; import {Unchanged, Addition, Deletion, NoNewline} from '../../lib/models/patch/region'; -class LayeredBuffer { - constructor() { - this.buffer = new TextBuffer(); - this.layers = ['patch', 'hunk', 'unchanged', 'addition', 'deletion', 'noNewline'].reduce((layers, name) => { - layers[name] = this.buffer.addMarkerLayer(); - return layers; - }, {}); - } - - getInsertionPoint() { - return this.buffer.getEndPosition(); - } - - getLayer(markerLayerName) { - const layer = this.layers[markerLayerName]; - if (!layer) { - throw new Error(`invalid marker layer name: ${markerLayerName}`); - } - return layer; - } - - appendMarked(markerLayerName, lines) { - const startPosition = this.buffer.getEndPosition(); - const layer = this.getLayer(markerLayerName); - this.buffer.append(lines.join('\n')); - const marker = layer.markRange([startPosition, this.buffer.getEndPosition()], {exclusive: true}); - this.buffer.append('\n'); - return marker; - } +function appendMarked(layeredBuffer, layerName, lines) { + const startPosition = layeredBuffer.getInsertionPoint(); + layeredBuffer.getBuffer().append(lines.join('\n')); + const marker = layeredBuffer.markRange( + layerName, + [startPosition, layeredBuffer.getInsertionPoint()], + {invalidate: 'never', exclusive: false}, + ); + layeredBuffer.getBuffer().append('\n'); + return marker; +} - markFrom(markerLayerName, startPosition) { - const endPosition = this.buffer.getEndPosition().translate([-1, Infinity]); - const layer = this.getLayer(markerLayerName); - return layer.markRange([startPosition, endPosition], {exclusive: true}); - } +function markFrom(layeredBuffer, layerName, startPosition) { + const endPosition = layeredBuffer.getInsertionPoint().translate([-1, Infinity]); + return layeredBuffer.markRange( + layerName, + [startPosition, endPosition], + {invalidate: 'never', exclusive: false}, + ); +} - wrapReturn(object) { - return { - buffer: this.buffer, - layers: this.layers, - ...object, - }; - } +function wrapReturn(layeredBuffer, object) { + return {buffer: layeredBuffer.getBuffer(), layers: layeredBuffer.getLayers(), ...object}; } class MultiFilePatchBuilder { @@ -68,10 +48,9 @@ class MultiFilePatchBuilder { } build() { - return this.layeredBuffer.wrapReturn({ + return wrapReturn(this.layeredBuffer, { multiFilePatch: new MultiFilePatch({ - buffer: this.layeredBuffer.buffer, - layers: this.layeredBuffer.layers, + layeredBuffer: this.layeredBuffer, filePatches: this.filePatches, }), }); @@ -229,9 +208,9 @@ class PatchBuilder { } } - const marker = this.layeredBuffer.markFrom('patch', this.patchStart); + const marker = markFrom(this.layeredBuffer, 'patch', this.patchStart); - return this.layeredBuffer.wrapReturn({ + return wrapReturn(this.layeredBuffer, { patch: new Patch({status: this._status, hunks: this.hunks, marker, renderStatus: this._renderStatus}), }); } @@ -259,22 +238,22 @@ class HunkBuilder { } unchanged(...lines) { - this.regions.push(new Unchanged(this.layeredBuffer.appendMarked('unchanged', lines))); + this.regions.push(new Unchanged(appendMarked(this.layeredBuffer, 'unchanged', lines))); return this; } added(...lines) { - this.regions.push(new Addition(this.layeredBuffer.appendMarked('addition', lines))); + this.regions.push(new Addition(appendMarked(this.layeredBuffer, 'addition', lines))); return this; } deleted(...lines) { - this.regions.push(new Deletion(this.layeredBuffer.appendMarked('deletion', lines))); + this.regions.push(new Deletion(appendMarked(this.layeredBuffer, 'deletion', lines))); return this; } noNewline() { - this.regions.push(new NoNewline(this.layeredBuffer.appendMarked('noNewline', [' No newline at end of file']))); + this.regions.push(new NoNewline(appendMarked(this.layeredBuffer, 'nonewline', [' No newline at end of file']))); return this; } @@ -303,9 +282,9 @@ class HunkBuilder { }), 0); } - const marker = this.layeredBuffer.markFrom('hunk', this.hunkStartPoint); + const marker = markFrom(this.layeredBuffer, 'hunk', this.hunkStartPoint); - return this.layeredBuffer.wrapReturn({ + return wrapReturn(this.layeredBuffer, { hunk: new Hunk({ oldStartRow: this.oldStartRow, oldRowCount: this.oldRowCount, diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index d917d8af36..75949de05e 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -885,7 +885,7 @@ describe('buildFilePatch', function() { ); }); - it('does not interfere with markers from surrounding patches when expanded', function() { + it('does not interfere with markers from surrounding visible patches when expanded', function() { const mfp = buildMultiFilePatch([ { oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100644', status: 'modified', @@ -984,7 +984,7 @@ describe('buildFilePatch', function() { ); }); - it('preserves patch markers when a delayed patch is expanded', function() { + it('does not interfere with markers from surrounding non-visible patches when expanded', function() { const mfp = buildMultiFilePatch([ { oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100644', status: 'modified', From f963e7510a14f6bf7015ad9e7694f852efa23246 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 17 Jan 2019 14:01:46 -0500 Subject: [PATCH 1840/4053] Rename LayeredBuffer to PatchBuffer --- lib/models/patch/builder.js | 46 +++++------ lib/models/patch/multi-file-patch.js | 28 +++---- .../{layered-buffer.js => patch-buffer.js} | 2 +- lib/models/patch/patch.js | 4 +- test/builder/patch.js | 80 +++++++++---------- 5 files changed, 80 insertions(+), 80 deletions(-) rename lib/models/patch/{layered-buffer.js => patch-buffer.js} (96%) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 6a308b2f7d..ab8c81109c 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -1,4 +1,4 @@ -import LayeredBuffer from './layered-buffer'; +import PatchBuffer from './patch-buffer'; import Hunk from './hunk'; import File, {nullFile} from './file'; import Patch, {TOO_LARGE, EXPANDED} from './patch'; @@ -18,26 +18,26 @@ const DEFAULT_OPTIONS = { export function buildFilePatch(diffs, options) { const opts = {...DEFAULT_OPTIONS, ...options}; - const layeredBuffer = new LayeredBuffer(); + const patchBuffer = new PatchBuffer(); let filePatch; if (diffs.length === 0) { filePatch = emptyDiffFilePatch(); } else if (diffs.length === 1) { - filePatch = singleDiffFilePatch(diffs[0], layeredBuffer, opts); + filePatch = singleDiffFilePatch(diffs[0], patchBuffer, opts); } else if (diffs.length === 2) { - filePatch = dualDiffFilePatch(diffs[0], diffs[1], layeredBuffer, opts); + filePatch = dualDiffFilePatch(diffs[0], diffs[1], patchBuffer, opts); } else { throw new Error(`Unexpected number of diffs: ${diffs.length}`); } - return new MultiFilePatch({layeredBuffer, filePatches: [filePatch]}); + return new MultiFilePatch({patchBuffer, filePatches: [filePatch]}); } export function buildMultiFilePatch(diffs, options) { const opts = {...DEFAULT_OPTIONS, ...options}; - const layeredBuffer = new LayeredBuffer(); + const patchBuffer = new PatchBuffer(); const byPath = new Map(); const actions = []; @@ -52,7 +52,7 @@ export function buildMultiFilePatch(diffs, options) { if (otherHalf) { // The second half. Complete the paired diff, or fail if they have unexpected statuses or modes. const [otherDiff, otherIndex] = otherHalf; - actions[otherIndex] = () => dualDiffFilePatch(diff, otherDiff, layeredBuffer, opts); + actions[otherIndex] = () => dualDiffFilePatch(diff, otherDiff, patchBuffer, opts); byPath.delete(thePath); } else { // The first half we've seen. @@ -60,14 +60,14 @@ export function buildMultiFilePatch(diffs, options) { index++; } } else { - actions[index] = () => singleDiffFilePatch(diff, layeredBuffer, opts); + actions[index] = () => singleDiffFilePatch(diff, patchBuffer, opts); index++; } } // Populate unpaired diffs that looked like they could be part of a pair, but weren't. for (const [unpairedDiff, originalIndex] of byPath.values()) { - actions[originalIndex] = () => singleDiffFilePatch(unpairedDiff, layeredBuffer, opts); + actions[originalIndex] = () => singleDiffFilePatch(unpairedDiff, patchBuffer, opts); } const filePatches = actions.map(action => action()); @@ -81,14 +81,14 @@ export function buildMultiFilePatch(diffs, options) { } }); - return new MultiFilePatch({layeredBuffer, filePatches}); + return new MultiFilePatch({patchBuffer, filePatches}); } function emptyDiffFilePatch() { return FilePatch.createNull(); } -function singleDiffFilePatch(diff, layeredBuffer, opts) { +function singleDiffFilePatch(diff, patchBuffer, opts) { const wasSymlink = diff.oldMode === File.modes.SYMLINK; const isSymlink = diff.newMode === File.modes.SYMLINK; @@ -120,8 +120,8 @@ function singleDiffFilePatch(diff, layeredBuffer, opts) { EXPANDED; if (renderStatus === TOO_LARGE) { - const insertRow = layeredBuffer.getBuffer().getLastRow(); - const patchMarker = layeredBuffer.markPosition( + const insertRow = patchBuffer.getBuffer().getLastRow(); + const patchMarker = patchBuffer.markPosition( Patch.layerName, [insertRow, 0], {invalidate: 'never', exclusive: false}, @@ -130,19 +130,19 @@ function singleDiffFilePatch(diff, layeredBuffer, opts) { return FilePatch.createDelayedFilePatch( oldFile, newFile, patchMarker, TOO_LARGE, () => { - const [hunks] = buildHunks(diff, layeredBuffer, insertRow, patchMarker); + const [hunks] = buildHunks(diff, patchBuffer, insertRow, patchMarker); return new Patch({status: diff.status, hunks, marker: patchMarker}); }, ); } else { - const [hunks, patchMarker] = buildHunks(diff, layeredBuffer, layeredBuffer.getBuffer().getLastRow()); + const [hunks, patchMarker] = buildHunks(diff, patchBuffer, patchBuffer.getBuffer().getLastRow()); const patch = new Patch({status: diff.status, hunks, marker: patchMarker}); return new FilePatch(oldFile, newFile, patch); } } -function dualDiffFilePatch(diff1, diff2, layeredBuffer, opts) { +function dualDiffFilePatch(diff1, diff2, patchBuffer, opts) { let modeChangeDiff, contentChangeDiff; if (diff1.oldMode === File.modes.SYMLINK || diff1.newMode === File.modes.SYMLINK) { modeChangeDiff = diff1; @@ -182,7 +182,7 @@ function dualDiffFilePatch(diff1, diff2, layeredBuffer, opts) { // TODO: do something with large diff too } - const [hunks, patchMarker] = buildHunks(contentChangeDiff, layeredBuffer, layeredBuffer.getBuffer().getLastRow()); + const [hunks, patchMarker] = buildHunks(contentChangeDiff, patchBuffer, patchBuffer.getBuffer().getLastRow()); const patch = new Patch({status, hunks, marker: patchMarker}); return new FilePatch(oldFile, newFile, patch); @@ -195,7 +195,7 @@ const CHANGEKIND = { '\\': NoNewline, }; -function buildHunks(diff, layeredBuffer, insertRow, existingMarker = null) { +function buildHunks(diff, patchBuffer, insertRow, existingMarker = null) { const hunks = []; const patchStartRow = insertRow; @@ -203,7 +203,7 @@ function buildHunks(diff, layeredBuffer, insertRow, existingMarker = null) { let nextLineLength = 0; if (diff.hunks.length === 0) { - const patchMarker = layeredBuffer.markPosition( + const patchMarker = patchBuffer.markPosition( Patch.layerName, [patchStartRow, 0], {invalidate: 'never', exclusive: true}, @@ -228,7 +228,7 @@ function buildHunks(diff, layeredBuffer, insertRow, existingMarker = null) { regions.push( new LastChangeKind( - layeredBuffer.markRange( + patchBuffer.markRange( LastChangeKind.layerName, [[currentRangeStart, 0], [bufferRow - 1, lastLineLength]], {invalidate: 'never', exclusive: false}, @@ -241,7 +241,7 @@ function buildHunks(diff, layeredBuffer, insertRow, existingMarker = null) { for (const lineText of hunkData.lines) { const bufferLine = lineText.slice(1) + '\n'; nextLineLength = lineText.length - 1; - layeredBuffer.getBuffer().insert([bufferRow, 0], bufferLine); + patchBuffer.getBuffer().insert([bufferRow, 0], bufferLine); const ChangeKind = CHANGEKIND[lineText[0]]; if (ChangeKind === undefined) { @@ -264,7 +264,7 @@ function buildHunks(diff, layeredBuffer, insertRow, existingMarker = null) { oldRowCount: hunkData.oldLineCount, newRowCount: hunkData.newLineCount, sectionHeading: hunkData.heading, - marker: layeredBuffer.markRange( + marker: patchBuffer.markRange( Hunk.layerName, [[bufferStartRow, 0], [bufferRow - 1, nextLineLength]], {invalidate: 'never', exclusive: false}, @@ -277,7 +277,7 @@ function buildHunks(diff, layeredBuffer, insertRow, existingMarker = null) { if (patchMarker) { patchMarker.setRange([[patchStartRow, 0], [bufferRow - 1, nextLineLength]], {exclusive: false}); } else { - patchMarker = layeredBuffer.markRange( + patchMarker = patchBuffer.markRange( Patch.layerName, [[patchStartRow, 0], [bufferRow - 1, nextLineLength]], {invalidate: 'never', exclusive: false}, diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 3e5a85c602..7d0a9ae8d4 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -1,16 +1,16 @@ import {Range} from 'atom'; import {RBTree} from 'bintrees'; -import LayeredBuffer from './layered-buffer'; +import PatchBuffer from './patch-buffer'; import {TOO_LARGE, COLLAPSED} from './patch'; export default class MultiFilePatch { static createNull() { - return new this({layeredBuffer: new LayeredBuffer(), filePatches: []}); + return new this({patchBuffer: new PatchBuffer(), filePatches: []}); } - constructor({layeredBuffer, filePatches}) { - this.layeredBuffer = layeredBuffer; + constructor({patchBuffer, filePatches}) { + this.patchBuffer = patchBuffer; this.filePatches = filePatches; this.filePatchesByMarker = new Map(); @@ -45,13 +45,13 @@ export default class MultiFilePatch { clone(opts = {}) { return new this.constructor({ - layeredBuffer: opts.layeredBuffer !== undefined ? opts.layeredBuffer : this.getLayeredBuffer(), + patchBuffer: opts.patchBuffer !== undefined ? opts.patchBuffer : this.getLayeredBuffer(), filePatches: opts.filePatches !== undefined ? opts.filePatches : this.getFilePatches(), }); } getLayeredBuffer() { - return this.layeredBuffer; + return this.patchBuffer; } getBuffer() { @@ -101,7 +101,7 @@ export default class MultiFilePatch { if (bufferRow < 0) { return undefined; } - const [marker] = this.layeredBuffer.findMarkers('patch', {intersectsRow: bufferRow}); + const [marker] = this.patchBuffer.findMarkers('patch', {intersectsRow: bufferRow}); return this.filePatchesByMarker.get(marker); } @@ -109,16 +109,16 @@ export default class MultiFilePatch { if (bufferRow < 0) { return undefined; } - const [marker] = this.layeredBuffer.findMarkers('hunk', {intersectsRow: bufferRow}); + const [marker] = this.patchBuffer.findMarkers('hunk', {intersectsRow: bufferRow}); return this.hunksByMarker.get(marker); } getStagePatchForLines(selectedLineSet) { - const nextLayeredBuffer = new LayeredBuffer(); + const nextLayeredBuffer = new PatchBuffer(); const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { return fp.buildStagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); }); - return this.clone({layeredBuffer: nextLayeredBuffer, filePatches: nextFilePatches}); + return this.clone({patchBuffer: nextLayeredBuffer, filePatches: nextFilePatches}); } getStagePatchForHunk(hunk) { @@ -126,11 +126,11 @@ export default class MultiFilePatch { } getUnstagePatchForLines(selectedLineSet) { - const nextLayeredBuffer = new LayeredBuffer(); + const nextLayeredBuffer = new PatchBuffer(); const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { return fp.buildUnstagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); }); - return this.clone({layeredBuffer: nextLayeredBuffer, filePatches: nextFilePatches}); + return this.clone({patchBuffer: nextLayeredBuffer, filePatches: nextFilePatches}); } getUnstagePatchForHunk(hunk) { @@ -235,7 +235,7 @@ export default class MultiFilePatch { } } - this.layeredBuffer = nextLayeredBuffer; + this.patchBuffer = nextLayeredBuffer; } /* @@ -262,7 +262,7 @@ export default class MultiFilePatch { } anyPresent() { - return this.layeredBuffer !== null && this.filePatches.some(fp => fp.isPresent()); + return this.patchBuffer !== null && this.filePatches.some(fp => fp.isPresent()); } didAnyChangeExecutableMode() { diff --git a/lib/models/patch/layered-buffer.js b/lib/models/patch/patch-buffer.js similarity index 96% rename from lib/models/patch/layered-buffer.js rename to lib/models/patch/patch-buffer.js index 2265ae7899..a6f1d0666f 100644 --- a/lib/models/patch/layered-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -2,7 +2,7 @@ import {TextBuffer} from 'atom'; const LAYER_NAMES = ['unchanged', 'addition', 'deletion', 'nonewline', 'hunk', 'patch']; -export default class LayeredBuffer { +export default class PatchBuffer { constructor() { this.buffer = new TextBuffer(); this.layers = LAYER_NAMES.reduce((map, layerName) => { diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index b5fc8209e5..129e23cfd3 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -75,8 +75,8 @@ export default class Patch { return this.marker.getRange().intersectsRow(row); } - reMarkOn(layeredBuffer) { - this.marker = layeredBuffer.markRange( + reMarkOn(patchBuffer) { + this.marker = patchBuffer.markRange( this.constructor.layerName, this.getRange(), {invalidate: 'never', exclusive: false}, diff --git a/test/builder/patch.js b/test/builder/patch.js index 6da24e5f29..cd8f45a79f 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -1,6 +1,6 @@ // Builders for classes related to MultiFilePatches. -import LayeredBuffer from '../../lib/models/patch/layered-buffer'; +import PatchBuffer from '../../lib/models/patch/patch-buffer'; import MultiFilePatch from '../../lib/models/patch/multi-file-patch'; import FilePatch from '../../lib/models/patch/file-patch'; import File, {nullFile} from '../../lib/models/patch/file'; @@ -8,49 +8,49 @@ import Patch from '../../lib/models/patch/patch'; import Hunk from '../../lib/models/patch/hunk'; import {Unchanged, Addition, Deletion, NoNewline} from '../../lib/models/patch/region'; -function appendMarked(layeredBuffer, layerName, lines) { - const startPosition = layeredBuffer.getInsertionPoint(); - layeredBuffer.getBuffer().append(lines.join('\n')); - const marker = layeredBuffer.markRange( +function appendMarked(patchBuffer, layerName, lines) { + const startPosition = patchBuffer.getInsertionPoint(); + patchBuffer.getBuffer().append(lines.join('\n')); + const marker = patchBuffer.markRange( layerName, - [startPosition, layeredBuffer.getInsertionPoint()], + [startPosition, patchBuffer.getInsertionPoint()], {invalidate: 'never', exclusive: false}, ); - layeredBuffer.getBuffer().append('\n'); + patchBuffer.getBuffer().append('\n'); return marker; } -function markFrom(layeredBuffer, layerName, startPosition) { - const endPosition = layeredBuffer.getInsertionPoint().translate([-1, Infinity]); - return layeredBuffer.markRange( +function markFrom(patchBuffer, layerName, startPosition) { + const endPosition = patchBuffer.getInsertionPoint().translate([-1, Infinity]); + return patchBuffer.markRange( layerName, [startPosition, endPosition], {invalidate: 'never', exclusive: false}, ); } -function wrapReturn(layeredBuffer, object) { - return {buffer: layeredBuffer.getBuffer(), layers: layeredBuffer.getLayers(), ...object}; +function wrapReturn(patchBuffer, object) { + return {buffer: patchBuffer.getBuffer(), layers: patchBuffer.getLayers(), ...object}; } class MultiFilePatchBuilder { - constructor(layeredBuffer = null) { - this.layeredBuffer = layeredBuffer; + constructor(patchBuffer = null) { + this.patchBuffer = patchBuffer; this.filePatches = []; } addFilePatch(block = () => {}) { - const filePatch = new FilePatchBuilder(this.layeredBuffer); + const filePatch = new FilePatchBuilder(this.patchBuffer); block(filePatch); this.filePatches.push(filePatch.build().filePatch); return this; } build() { - return wrapReturn(this.layeredBuffer, { + return wrapReturn(this.patchBuffer, { multiFilePatch: new MultiFilePatch({ - layeredBuffer: this.layeredBuffer, + patchBuffer: this.patchBuffer, filePatches: this.filePatches, }), }); @@ -58,13 +58,13 @@ class MultiFilePatchBuilder { } class FilePatchBuilder { - constructor(layeredBuffer = null) { - this.layeredBuffer = layeredBuffer; + constructor(patchBuffer = null) { + this.patchBuffer = patchBuffer; this.oldFile = new File({path: 'file', mode: File.modes.NORMAL}); this.newFile = null; - this.patchBuilder = new PatchBuilder(this.layeredBuffer); + this.patchBuilder = new PatchBuilder(this.patchBuffer); } setOldFile(block) { @@ -118,7 +118,7 @@ class FilePatchBuilder { this.newFile = this.oldFile.clone(); } - return this.layeredBuffer.wrapReturn({ + return this.patchBuffer.wrapReturn({ filePatch: new FilePatch(this.oldFile, this.newFile, patch), }); } @@ -156,14 +156,14 @@ class FileBuilder { } class PatchBuilder { - constructor(layeredBuffer = null) { - this.layeredBuffer = layeredBuffer; + constructor(patchBuffer = null) { + this.patchBuffer = patchBuffer; this._renderStatus = undefined; this._status = 'modified'; this.hunks = []; - this.patchStart = this.layeredBuffer.getInsertionPoint(); + this.patchStart = this.patchBuffer.getInsertionPoint(); this.drift = 0; this.explicitlyEmpty = false; } @@ -183,7 +183,7 @@ class PatchBuilder { } addHunk(block = () => {}) { - const builder = new HunkBuilder(this.layeredBuffer, this.drift); + const builder = new HunkBuilder(this.patchBuffer, this.drift); block(builder); const {hunk, drift} = builder.build(); this.hunks.push(hunk); @@ -208,17 +208,17 @@ class PatchBuilder { } } - const marker = markFrom(this.layeredBuffer, 'patch', this.patchStart); + const marker = markFrom(this.patchBuffer, 'patch', this.patchStart); - return wrapReturn(this.layeredBuffer, { + return wrapReturn(this.patchBuffer, { patch: new Patch({status: this._status, hunks: this.hunks, marker, renderStatus: this._renderStatus}), }); } } class HunkBuilder { - constructor(layeredBuffer = null, drift = 0) { - this.layeredBuffer = layeredBuffer; + constructor(patchBuffer = null, drift = 0) { + this.patchBuffer = patchBuffer; this.drift = drift; this.oldStartRow = 0; @@ -228,7 +228,7 @@ class HunkBuilder { this.sectionHeading = "don't care"; - this.hunkStartPoint = this.layeredBuffer.getInsertionPoint(); + this.hunkStartPoint = this.patchBuffer.getInsertionPoint(); this.regions = []; } @@ -238,22 +238,22 @@ class HunkBuilder { } unchanged(...lines) { - this.regions.push(new Unchanged(appendMarked(this.layeredBuffer, 'unchanged', lines))); + this.regions.push(new Unchanged(appendMarked(this.patchBuffer, 'unchanged', lines))); return this; } added(...lines) { - this.regions.push(new Addition(appendMarked(this.layeredBuffer, 'addition', lines))); + this.regions.push(new Addition(appendMarked(this.patchBuffer, 'addition', lines))); return this; } deleted(...lines) { - this.regions.push(new Deletion(appendMarked(this.layeredBuffer, 'deletion', lines))); + this.regions.push(new Deletion(appendMarked(this.patchBuffer, 'deletion', lines))); return this; } noNewline() { - this.regions.push(new NoNewline(appendMarked(this.layeredBuffer, 'nonewline', [' No newline at end of file']))); + this.regions.push(new NoNewline(appendMarked(this.patchBuffer, 'nonewline', [' No newline at end of file']))); return this; } @@ -282,9 +282,9 @@ class HunkBuilder { }), 0); } - const marker = markFrom(this.layeredBuffer, 'hunk', this.hunkStartPoint); + const marker = markFrom(this.patchBuffer, 'hunk', this.hunkStartPoint); - return wrapReturn(this.layeredBuffer, { + return wrapReturn(this.patchBuffer, { hunk: new Hunk({ oldStartRow: this.oldStartRow, oldRowCount: this.oldRowCount, @@ -300,17 +300,17 @@ class HunkBuilder { } export function multiFilePatchBuilder() { - return new MultiFilePatchBuilder(new LayeredBuffer()); + return new MultiFilePatchBuilder(new PatchBuffer()); } export function filePatchBuilder() { - return new FilePatchBuilder(new LayeredBuffer()); + return new FilePatchBuilder(new PatchBuffer()); } export function patchBuilder() { - return new PatchBuilder(new LayeredBuffer()); + return new PatchBuilder(new PatchBuffer()); } export function hunkBuilder() { - return new HunkBuilder(new LayeredBuffer()); + return new HunkBuilder(new PatchBuffer()); } From 6df1380f609d9784c9348f1cb29ec36fc565f786 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 17 Jan 2019 14:15:07 -0500 Subject: [PATCH 1841/4053] Begin an "atomic" (with respect to markers) Modification API --- lib/models/patch/patch-buffer.js | 37 ++++++++++- test/models/patch/patch-buffer.test.js | 86 ++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 test/models/patch/patch-buffer.test.js diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index a6f1d0666f..1ff655824f 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -1,4 +1,4 @@ -import {TextBuffer} from 'atom'; +import {TextBuffer, Range} from 'atom'; const LAYER_NAMES = ['unchanged', 'addition', 'deletion', 'nonewline', 'hunk', 'patch']; @@ -40,4 +40,39 @@ export default class PatchBuffer { this.layers[layerName].clear(); } } + + createModifier() { + return new Modification(this); + } +} + +class Modification { + constructor(patchBuffer) { + this.patchBuffer = patchBuffer; + this.markerBlueprints = []; + } + + append(text) { + this.patchBuffer.getBuffer().append(text); + } + + appendMarked(text, layerName, markerOpts) { + const start = this.patchBuffer.getBuffer().getEndPosition(); + this.append(text); + const end = this.patchBuffer.getBuffer().getEndPosition(); + this.markerBlueprints.push({layerName, range: new Range(start, end), markerOpts}); + return this; + } + + apply() { + for (const {layerName, range, markerOpts} of this.markerBlueprints) { + const callback = markerOpts.callback; + delete markerOpts.callback; + + const marker = this.patchBuffer.markRange(layerName, range, markerOpts); + if (callback) { + callback(marker); + } + } + } } diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js new file mode 100644 index 0000000000..135f32ba30 --- /dev/null +++ b/test/models/patch/patch-buffer.test.js @@ -0,0 +1,86 @@ +import dedent from 'dedent-js'; + +import PatchBuffer from '../../../lib/models/patch/patch-buffer'; + +describe('PatchBuffer', function() { + let patchBuffer; + + beforeEach(function() { + patchBuffer = new PatchBuffer(); + patchBuffer.getBuffer().setText(TEXT); + }); + + it('has simple accessors', function() { + assert.strictEqual(patchBuffer.getBuffer().getText(), TEXT); + assert.deepEqual(patchBuffer.getInsertionPoint().serialize(), [9, 4]); + }); + + it('creates and finds markers on specified layers', function() { + const patchMarker = patchBuffer.markRange('patch', [[1, 0], [2, 4]]); + const hunkMarker = patchBuffer.markRange('hunk', [[2, 0], [3, 4]]); + + assert.deepEqual(patchBuffer.findMarkers('patch', {}), [patchMarker]); + assert.deepEqual(patchBuffer.findMarkers('hunk', {}), [hunkMarker]); + }); + + it('clears markers from all layers at once', function() { + patchBuffer.markRange('patch', [[0, 0], [0, 4]]); + patchBuffer.markPosition('hunk', [0, 1]); + + patchBuffer.clearAllLayers(); + + assert.lengthOf(patchBuffer.findMarkers('patch', {}), 0); + assert.lengthOf(patchBuffer.findMarkers('hunk', {}), 0); + }); + + describe('deferred-marking modifications', function() { + it('performs multiple modifications and only creates markers at the end', function() { + const modifier = patchBuffer.createModifier(); + const cb0 = sinon.spy(); + const cb1 = sinon.spy(); + + modifier.append('0010\n'); + modifier.appendMarked('0011\n', 'patch', {invalidate: 'never', callback: cb0}); + modifier.append('0012\n'); + modifier.appendMarked('0013\n0014\n', 'hunk', {invalidate: 'surround', callback: cb1}); + + assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` + ${TEXT}0010 + 0011 + 0012 + 0013 + 0014 + + `); + + assert.isFalse(cb0.called); + assert.isFalse(cb1.called); + assert.lengthOf(patchBuffer.findMarkers('patch', {}), 0); + assert.lengthOf(patchBuffer.findMarkers('hunk', {}), 0); + + modifier.apply(); + + assert.lengthOf(patchBuffer.findMarkers('patch', {}), 1); + const [marker0] = patchBuffer.findMarkers('patch', {}); + assert.isTrue(cb0.calledWith(marker0)); + + assert.lengthOf(patchBuffer.findMarkers('hunk', {}), 1); + const [marker1] = patchBuffer.findMarkers('hunk', {}); + assert.isTrue(cb1.calledWith(marker1)); + }); + }); +}); + +const TEXT = dedent` + 0000 + 0001 + 0002 + 0003 + 0004 + 0005 + 0006 + 0007 + 0008 + 0009 + +`; From 3b340999f60a0d7f2973d84cbbab2bc5fe73ab36 Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 17 Jan 2019 12:06:06 -0800 Subject: [PATCH 1842/4053] import LARGE_DIFF_THRESHOLD --- lib/models/patch/builder.js | 2 +- test/models/patch/multi-file-patch.test.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index ab8c81109c..af0814e94e 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -6,7 +6,7 @@ import {Unchanged, Addition, Deletion, NoNewline} from './region'; import FilePatch from './file-patch'; import MultiFilePatch from './multi-file-patch'; -const DEFAULT_OPTIONS = { +export const DEFAULT_OPTIONS = { // Number of lines after which we consider the diff "large" // TODO: Set this based on performance measurements largeDiffThreshold: 100, diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 61fa236f7a..b5e665da38 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -3,6 +3,7 @@ import dedent from 'dedent-js'; import {multiFilePatchBuilder, filePatchBuilder} from '../../builder/patch'; import {TOO_LARGE} from '../../../lib/models/patch/patch'; +import {DEFAULT_OPTIONS} from '../../../lib/models/patch/builder.js'; import MultiFilePatch from '../../../lib/models/patch/multi-file-patch'; import {assertInFilePatch} from '../../helpers'; @@ -709,6 +710,7 @@ describe('MultiFilePatch', function() { describe('isPatchTooLargeOrCollapsed', function() { let multiFilePatch; + const {largeDiffThreshold} = DEFAULT_OPTIONS; beforeEach(function() { multiFilePatch = multiFilePatchBuilder() .addFilePatch(fp => { From 49b406d5000e2e63ce5f4bca30b6e2bbdb68eb1a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 17 Jan 2019 14:59:28 -0500 Subject: [PATCH 1843/4053] Preserve Markers that begin or end at a Modification's insertion point --- lib/models/patch/patch-buffer.js | 57 ++++++++++++++++++++--- test/models/patch/patch-buffer.test.js | 63 +++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 9 deletions(-) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 1ff655824f..aec6b2493d 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -1,4 +1,4 @@ -import {TextBuffer, Range} from 'atom'; +import {TextBuffer, Range, Point} from 'atom'; const LAYER_NAMES = ['unchanged', 'addition', 'deletion', 'nonewline', 'hunk', 'patch']; @@ -41,25 +41,52 @@ export default class PatchBuffer { } } - createModifier() { - return new Modification(this); + createModifierAt(insertionPoint) { + return new Modification(this, Point.fromObject(insertionPoint)); + } + + createModifierAtEnd() { + return this.createModifierAt(this.getInsertionPoint()); } } class Modification { - constructor(patchBuffer) { + constructor(patchBuffer, insertionPoint) { this.patchBuffer = patchBuffer; + this.startPoint = insertionPoint.copy(); + this.insertionPoint = insertionPoint; this.markerBlueprints = []; + + this.markersBefore = new Set(); + this.markersAfter = new Set(); + } + + keepBefore(markers) { + for (const marker of markers) { + if (marker.getRange().end.isEqual(this.startPoint)) { + this.markersBefore.add(marker); + } + } + } + + keepAfter(markers) { + for (const marker of markers) { + if (marker.getRange().end.isEqual(this.startPoint)) { + this.markersAfter.add(marker); + } + } } append(text) { - this.patchBuffer.getBuffer().append(text); + const insertedRange = this.patchBuffer.getBuffer().insert(this.insertionPoint, text); + this.insertionPoint = insertedRange.end; + return this; } appendMarked(text, layerName, markerOpts) { - const start = this.patchBuffer.getBuffer().getEndPosition(); + const start = this.insertionPoint.copy(); this.append(text); - const end = this.patchBuffer.getBuffer().getEndPosition(); + const end = this.insertionPoint.copy(); this.markerBlueprints.push({layerName, range: new Range(start, end), markerOpts}); return this; } @@ -74,5 +101,21 @@ class Modification { callback(marker); } } + + for (const beforeMarker of this.markersBefore) { + if (!beforeMarker.isReversed()) { + beforeMarker.setHeadPosition(this.startPoint); + } else { + beforeMarker.setTailPosition(this.startPoint); + } + } + + for (const afterMarker of this.markersAfter) { + if (!afterMarker.isReversed()) { + afterMarker.setTailPosition(this.insertionPoint); + } else { + afterMarker.setHeadPosition(this.insertionPoint); + } + } } } diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index 135f32ba30..102ed2d409 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -12,7 +12,7 @@ describe('PatchBuffer', function() { it('has simple accessors', function() { assert.strictEqual(patchBuffer.getBuffer().getText(), TEXT); - assert.deepEqual(patchBuffer.getInsertionPoint().serialize(), [9, 4]); + assert.deepEqual(patchBuffer.getInsertionPoint().serialize(), [10, 0]); }); it('creates and finds markers on specified layers', function() { @@ -35,7 +35,7 @@ describe('PatchBuffer', function() { describe('deferred-marking modifications', function() { it('performs multiple modifications and only creates markers at the end', function() { - const modifier = patchBuffer.createModifier(); + const modifier = patchBuffer.createModifierAtEnd(); const cb0 = sinon.spy(); const cb1 = sinon.spy(); @@ -68,6 +68,65 @@ describe('PatchBuffer', function() { const [marker1] = patchBuffer.findMarkers('hunk', {}); assert.isTrue(cb1.calledWith(marker1)); }); + + it('inserts into the middle of an existing buffer', function() { + const modifier = patchBuffer.createModifierAt([4, 2]); + const callback = sinon.spy(); + + modifier.append('aa\nbbbb\n'); + modifier.appendMarked('-patch-\n-patch-\n', 'patch', {callback}); + modifier.appendMarked('-hunk-\ndd', 'hunk', {}); + + assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` + 0000 + 0001 + 0002 + 0003 + 00aa + bbbb + -patch- + -patch- + -hunk- + dd04 + 0005 + 0006 + 0007 + 0008 + 0009 + + `); + + assert.lengthOf(patchBuffer.findMarkers('patch', {}), 0); + assert.lengthOf(patchBuffer.findMarkers('hunk', {}), 0); + assert.isFalse(callback.called); + + modifier.apply(); + + assert.lengthOf(patchBuffer.findMarkers('patch', {}), 1); + const [marker] = patchBuffer.findMarkers('patch', {}); + assert.isTrue(callback.calledWith(marker)); + }); + + it('preserves markers that should be before or after the modification region', function() { + const before0 = patchBuffer.markRange('patch', [[1, 0], [4, 0]]); + const before1 = patchBuffer.markPosition('hunk', [4, 0]); + const after0 = patchBuffer.markPosition('patch', [4, 0]); + + const modifier = patchBuffer.createModifierAt([4, 0]); + modifier.keepBefore([before0, before1]); + modifier.keepAfter([after0]); + + let marker = null; + const callback = m => { marker = m; }; + modifier.appendMarked('A\nB\nC\nD\nE\n', 'addition', {callback}); + + modifier.apply(); + + assert.deepEqual(before0.getRange().serialize(), [[1, 0], [4, 0]]); + assert.deepEqual(before1.getRange().serialize(), [[4, 0], [4, 0]]); + assert.deepEqual(marker.getRange().serialize(), [[4, 0], [9, 0]]); + assert.deepEqual(after0.getRange().serialize(), [[9, 0], [9, 0]]); + }); }); }); From a2d453c41b9742f385dc1a4e03d7c0ad6b4169ca Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 17 Jan 2019 16:12:11 -0500 Subject: [PATCH 1844/4053] PatchBuffer::extract() to slice a Range out and preserve its markers --- lib/models/patch/patch-buffer.js | 27 ++++++++++++++++ test/models/patch/patch-buffer.test.js | 45 ++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index aec6b2493d..d827c97974 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -48,6 +48,33 @@ export default class PatchBuffer { createModifierAtEnd() { return this.createModifierAt(this.getInsertionPoint()); } + + extract(rangeLike) { + const range = Range.fromObject(rangeLike); + const movedMarkersByLayer = LAYER_NAMES.reduce((map, layerName) => { + map[layerName] = this.layers[layerName].findMarkers({containedInRange: range}); + return map; + }, {}); + const markerMap = new Map(); + + const subBuffer = new PatchBuffer(); + subBuffer.getBuffer().setText(this.buffer.getTextInRange(range)); + + for (const layerName of LAYER_NAMES) { + for (const oldMarker of movedMarkersByLayer[layerName]) { + const newMarker = subBuffer.markRange( + layerName, + oldMarker.getRange().translate(range.start.negate()), + oldMarker.getProperties(), + ); + markerMap.set(oldMarker, newMarker); + oldMarker.destroy(); + } + } + + this.buffer.setTextInRange(range, ''); + return subBuffer; + } } class Modification { diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index 102ed2d409..c9759fc197 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -33,6 +33,51 @@ describe('PatchBuffer', function() { assert.lengthOf(patchBuffer.findMarkers('hunk', {}), 0); }); + it('extracts a subset of the buffer and layers as a new LayeredBuffer', function() { + patchBuffer.markRange('patch', [[1, 0], [3, 0]]); // before + patchBuffer.markRange('hunk', [[2, 0], [4, 0]]); // before, ending at the extraction point + patchBuffer.markRange('hunk', [[4, 0], [5, 0]]); // within + patchBuffer.markRange('patch', [[6, 0], [7, 0]]); // within + patchBuffer.markRange('hunk', [[7, 0], [9, 0]]); // after, starting at the extraction point + patchBuffer.markRange('patch', [[8, 0], [10, 0]]); // after + + const subPatchBuffer = patchBuffer.extract([[4, 0], [7, 0]]); + + assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` + 0000 + 0001 + 0002 + 0003 + 0007 + 0008 + 0009 + + `); + assert.deepEqual( + patchBuffer.findMarkers('patch', {}).map(m => m.getRange().serialize()), + [[[1, 0], [3, 0]], [[5, 0], [7, 0]]], + ); + assert.deepEqual( + patchBuffer.findMarkers('hunk', {}).map(m => m.getRange().serialize()), + [[[2, 0], [4, 0]], [[4, 0], [6, 0]]], + ); + + assert.strictEqual(subPatchBuffer.getBuffer().getText(), dedent` + 0004 + 0005 + 0006 + + `); + assert.deepEqual( + subPatchBuffer.findMarkers('hunk', {}).map(m => m.getRange().serialize()), + [[[0, 0], [1, 0]]], + ); + assert.deepEqual( + subPatchBuffer.findMarkers('patch', {}).map(m => m.getRange().serialize()), + [[[2, 0], [3, 0]]], + ); + }); + describe('deferred-marking modifications', function() { it('performs multiple modifications and only creates markers at the end', function() { const modifier = patchBuffer.createModifierAtEnd(); From 34e104af8af8e56bc644cf6eb5d5ac47c4999e7b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 17 Jan 2019 16:29:31 -0500 Subject: [PATCH 1845/4053] Unit test for inserting one PatchBuffer into another --- test/models/patch/patch-buffer.test.js | 55 ++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index c9759fc197..5c9f697031 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -172,6 +172,61 @@ describe('PatchBuffer', function() { assert.deepEqual(marker.getRange().serialize(), [[4, 0], [9, 0]]); assert.deepEqual(after0.getRange().serialize(), [[9, 0], [9, 0]]); }); + + it('appends another PatchBuffer at its insertion point', function() { + const subPatchBuffer = new PatchBuffer(); + subPatchBuffer.getBuffer().setText(dedent` + aaaa + bbbb + cc + `); + + subPatchBuffer.markPosition('patch', [0, 0]); + subPatchBuffer.markRange('hunk', [[0, 0], [1, 4]]); + subPatchBuffer.markRange('addition', [[1, 2], [2, 2]]); + + const mBefore = patchBuffer.markRange('deletion', [[0, 0], [2, 0]]); + const mAfter = patchBuffer.markRange('deletion', [[7, 0], [7, 4]]); + + patchBuffer + .createModifierAt([3, 2]) + .appendPatchBuffer(subPatchBuffer) + .apply(); + + assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` + 0000 + 0001 + 0002 + 00aaaa + bbbb + cc03 + 0004 + 0005 + 0006 + 0007 + 0008 + 0009 + + `); + + assert.deepEqual(mBefore.getRange().serialize(), [[0, 0], [2, 0]]); + assert.deepEqual(mAfter.getRange().serialize(), [[9, 0], [9, 4]]); + + assert.deepEqual( + patchBuffer.findMarkers('patch', {}).map(m => m.getRange().serialize()), + [[[3, 2], [3, 2]]], + ); + + assert.deepEqual( + patchBuffer.findMarkers('hunk', {}).map(m => m.getRange().serialize()), + [[[3, 2], [4, 4]]], + ); + + assert.deepEqual( + patchBuffer.findMarkers('addition', {}).map(m => m.getRange().serialize()), + [[[4, 2], [5, 2]]], + ); + }); }); }); From dfc2fee53c5e4a10337a748297b6f9a96227050a Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 17 Jan 2019 16:08:03 -0800 Subject: [PATCH 1846/4053] actually trigger expanding and collapsing from `FilePatchHeaderView` --- lib/models/patch/file-patch.js | 4 ++-- lib/views/file-patch-header-view.js | 15 +++++++++++++-- lib/views/multi-file-patch-view.js | 6 +++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 362f630ae2..7c76d6a0e9 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -127,7 +127,7 @@ export default class FilePatch { } } - triggerCollapse() { + triggerCollapse = () => { const nextPatch = this.patch.collapsed(); if (nextPatch !== this.patch) { this.patch = nextPatch; @@ -135,7 +135,7 @@ export default class FilePatch { } } - triggerExpand() { + triggerExpand = () => { const nextPatch = this.patch.expanded(); if (nextPatch !== this.patch) { this.patch = nextPatch; diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index a78b931750..ae10db1fde 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -29,9 +29,12 @@ export default class FilePatchHeaderView extends React.Component { // should probably change 'toggleFile' to 'toggleFileStagingStatus' // because the addition of another toggling function makes the old name confusing. toggleFile: PropTypes.func.isRequired, - toggleFileCollapse: PropTypes.func, itemType: ItemTypePropType.isRequired, + + isCollapsed: PropTypes.bool.isRequired, + triggerExpand: PropTypes.func.isRequired, + triggerCollapse: PropTypes.func.isRequired, }; constructor(props) { @@ -53,11 +56,19 @@ export default class FilePatchHeaderView extends React.Component { ); } + togglePatchCollapse = () => { + if (this.props.isCollapsed) { + this.props.triggerExpand(); + } else { + this.props.triggerCollapse(); + } + } + renderCollapseButton() { return ( ); diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 5554ac910c..5bb93e7c7b 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -22,7 +22,7 @@ import CommitDetailItem from '../items/commit-detail-item'; import IssueishDetailItem from '../items/issueish-detail-item'; import File from '../models/patch/file'; -import {TOO_LARGE} from '../models/patch/patch'; +import {TOO_LARGE, COLLAPSED} from '../models/patch/patch'; const executableText = { [File.modes.NORMAL]: 'non executable', @@ -350,6 +350,10 @@ export default class MultiFilePatchView extends React.Component { diveIntoMirrorPatch={() => this.props.diveIntoMirrorPatch(filePatch)} openFile={() => this.didOpenFile({selectedFilePatch: filePatch})} toggleFile={() => this.props.toggleFile(filePatch)} + + isCollapsed={filePatch.getRenderStatus === TOO_LARGE || filePatch.getRenderStatus === COLLAPSED} + triggerCollapse={filePatch.triggerCollapse} + triggerExpand={filePatch.triggerExpand} /> {this.renderSymlinkChangeMeta(filePatch)} {this.renderExecutableModeChangeMeta(filePatch)} From ebdec242ceaf3860f1ebae2c27387e5763f24c11 Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 17 Jan 2019 17:04:27 -0800 Subject: [PATCH 1847/4053] change the chevron icon direction depending on collapsed prop --- lib/views/file-patch-header-view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index ae10db1fde..56ba16c466 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -65,11 +65,12 @@ export default class FilePatchHeaderView extends React.Component { } renderCollapseButton() { + const icon = this.props.isCollapsed ? 'chevron-up' : 'chevron-down'; return ( ); } From 7290720ff5c37440c1276a7fae6b2283d77ef585 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 18 Jan 2019 10:48:16 +0900 Subject: [PATCH 1848/4053] Add inline diff mockup --- docs/feature-requests/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index eb03b8e8e2..3802a51dbb 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -142,7 +142,7 @@ Clicking on a review comment opens a `TextEditor` on the corresponding position If an open `TextEditor` corresponds to a file that has one or more review comments in an open `PullRequestReviewsItem`, gutter and line decorations are added to the lines that match those review comment positions. The "current" one is styled differently to stand out. -> TODO: Illustrate the "review comment here" gutter and line decorations +![inline diff](https://user-images.githubusercontent.com/378023/51360052-68e6ed00-1b0d-11e9-852e-a51cff4d479e.png) Clicking on the gutter icon reveals the `PullRequestReviewsItem` and highlights that review comment as the "current" one, scrolling to it and expanding its review if necessary. From 01b82f0e23cd22fb4afb6c8571a2f789b3a8fd58 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 18 Jan 2019 13:19:32 -0500 Subject: [PATCH 1849/4053] Implement appendPatchBuffer() --- lib/models/patch/patch-buffer.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index d827c97974..7ec517a7a8 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -118,6 +118,28 @@ class Modification { return this; } + appendPatchBuffer(subPatchBuffer) { + const baseOffset = this.insertionPoint.copy(); + this.append(subPatchBuffer.getBuffer().getText()); + + const subMarkerMap = new Map(); + for (const layerName of LAYER_NAMES) { + for (const oldMarker of subPatchBuffer.findMarkers(layerName, {})) { + const startOffset = oldMarker.getRange().start.row === 0 ? baseOffset : [baseOffset.row, 0]; + const endOffset = oldMarker.getRange().end.row === 0 ? baseOffset : [baseOffset.row, 0]; + + const range = oldMarker.getRange().translate(startOffset, endOffset); + const markerOpts = { + ...oldMarker.getProperties(), + callback: newMarker => { subMarkerMap.set(oldMarker, newMarker); }, + }; + this.markerBlueprints.push({layerName, range, markerOpts}); + } + } + + return this; + } + apply() { for (const {layerName, range, markerOpts} of this.markerBlueprints) { const callback = markerOpts.callback; From 2d7c2f465733717e66c7fca9e7d62267f5a35876 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 18 Jan 2019 13:50:14 -0500 Subject: [PATCH 1850/4053] Translate Markers when extracting and appending PatchBuffers midline --- lib/models/patch/patch-buffer.js | 5 ++++- test/models/patch/patch-buffer.test.js | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 7ec517a7a8..d1ed781035 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -51,6 +51,7 @@ export default class PatchBuffer { extract(rangeLike) { const range = Range.fromObject(rangeLike); + const baseOffset = range.start.negate(); const movedMarkersByLayer = LAYER_NAMES.reduce((map, layerName) => { map[layerName] = this.layers[layerName].findMarkers({containedInRange: range}); return map; @@ -62,9 +63,11 @@ export default class PatchBuffer { for (const layerName of LAYER_NAMES) { for (const oldMarker of movedMarkersByLayer[layerName]) { + const startOffset = oldMarker.getRange().start.row === range.start.row ? baseOffset : [baseOffset.row, 0]; + const endOffset = oldMarker.getRange().end.row === range.start.row ? baseOffset : [baseOffset.row, 0]; const newMarker = subBuffer.markRange( layerName, - oldMarker.getRange().translate(range.start.negate()), + oldMarker.getRange().translate(startOffset, endOffset), oldMarker.getProperties(), ); markerMap.set(oldMarker, newMarker); diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index 5c9f697031..5256c1c6c6 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -35,20 +35,20 @@ describe('PatchBuffer', function() { it('extracts a subset of the buffer and layers as a new LayeredBuffer', function() { patchBuffer.markRange('patch', [[1, 0], [3, 0]]); // before - patchBuffer.markRange('hunk', [[2, 0], [4, 0]]); // before, ending at the extraction point - patchBuffer.markRange('hunk', [[4, 0], [5, 0]]); // within - patchBuffer.markRange('patch', [[6, 0], [7, 0]]); // within - patchBuffer.markRange('hunk', [[7, 0], [9, 0]]); // after, starting at the extraction point + patchBuffer.markRange('hunk', [[2, 0], [4, 2]]); // before, ending at the extraction point + patchBuffer.markRange('hunk', [[4, 2], [5, 0]]); // within + patchBuffer.markRange('patch', [[6, 0], [7, 1]]); // within + patchBuffer.markRange('hunk', [[7, 1], [9, 0]]); // after, starting at the extraction point patchBuffer.markRange('patch', [[8, 0], [10, 0]]); // after - const subPatchBuffer = patchBuffer.extract([[4, 0], [7, 0]]); + const subPatchBuffer = patchBuffer.extract([[4, 2], [7, 1]]); assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` 0000 0001 0002 0003 - 0007 + 00007 0008 0009 @@ -59,14 +59,14 @@ describe('PatchBuffer', function() { ); assert.deepEqual( patchBuffer.findMarkers('hunk', {}).map(m => m.getRange().serialize()), - [[[2, 0], [4, 0]], [[4, 0], [6, 0]]], + [[[2, 0], [4, 2]], [[4, 2], [6, 0]]], ); assert.strictEqual(subPatchBuffer.getBuffer().getText(), dedent` - 0004 + 04 0005 0006 - + 0 `); assert.deepEqual( subPatchBuffer.findMarkers('hunk', {}).map(m => m.getRange().serialize()), @@ -74,7 +74,7 @@ describe('PatchBuffer', function() { ); assert.deepEqual( subPatchBuffer.findMarkers('patch', {}).map(m => m.getRange().serialize()), - [[[2, 0], [3, 0]]], + [[[2, 0], [3, 1]]], ); }); From 03e03f342d58df1c664dab704e5ccd7d1026a7e6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 18 Jan 2019 13:55:26 -0500 Subject: [PATCH 1851/4053] Renamings --- lib/models/patch/patch-buffer.js | 12 ++++----- test/models/patch/patch-buffer.test.js | 36 +++++++++++++------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index d1ed781035..4177d33ef0 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -41,15 +41,15 @@ export default class PatchBuffer { } } - createModifierAt(insertionPoint) { - return new Modification(this, Point.fromObject(insertionPoint)); + createInserterAt(insertionPoint) { + return new Inserter(this, Point.fromObject(insertionPoint)); } - createModifierAtEnd() { - return this.createModifierAt(this.getInsertionPoint()); + createInserterAtEnd() { + return this.createInserterAt(this.getInsertionPoint()); } - extract(rangeLike) { + extractPatchBuffer(rangeLike) { const range = Range.fromObject(rangeLike); const baseOffset = range.start.negate(); const movedMarkersByLayer = LAYER_NAMES.reduce((map, layerName) => { @@ -80,7 +80,7 @@ export default class PatchBuffer { } } -class Modification { +class Inserter { constructor(patchBuffer, insertionPoint) { this.patchBuffer = patchBuffer; this.startPoint = insertionPoint.copy(); diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index 5256c1c6c6..3efc44a53c 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -41,7 +41,7 @@ describe('PatchBuffer', function() { patchBuffer.markRange('hunk', [[7, 1], [9, 0]]); // after, starting at the extraction point patchBuffer.markRange('patch', [[8, 0], [10, 0]]); // after - const subPatchBuffer = patchBuffer.extract([[4, 2], [7, 1]]); + const subPatchBuffer = patchBuffer.extractPatchBuffer([[4, 2], [7, 1]]); assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` 0000 @@ -80,14 +80,14 @@ describe('PatchBuffer', function() { describe('deferred-marking modifications', function() { it('performs multiple modifications and only creates markers at the end', function() { - const modifier = patchBuffer.createModifierAtEnd(); + const inserter = patchBuffer.createInserterAtEnd(); const cb0 = sinon.spy(); const cb1 = sinon.spy(); - modifier.append('0010\n'); - modifier.appendMarked('0011\n', 'patch', {invalidate: 'never', callback: cb0}); - modifier.append('0012\n'); - modifier.appendMarked('0013\n0014\n', 'hunk', {invalidate: 'surround', callback: cb1}); + inserter.append('0010\n'); + inserter.appendMarked('0011\n', 'patch', {invalidate: 'never', callback: cb0}); + inserter.append('0012\n'); + inserter.appendMarked('0013\n0014\n', 'hunk', {invalidate: 'surround', callback: cb1}); assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` ${TEXT}0010 @@ -103,7 +103,7 @@ describe('PatchBuffer', function() { assert.lengthOf(patchBuffer.findMarkers('patch', {}), 0); assert.lengthOf(patchBuffer.findMarkers('hunk', {}), 0); - modifier.apply(); + inserter.apply(); assert.lengthOf(patchBuffer.findMarkers('patch', {}), 1); const [marker0] = patchBuffer.findMarkers('patch', {}); @@ -115,12 +115,12 @@ describe('PatchBuffer', function() { }); it('inserts into the middle of an existing buffer', function() { - const modifier = patchBuffer.createModifierAt([4, 2]); + const inserter = patchBuffer.createInserterAt([4, 2]); const callback = sinon.spy(); - modifier.append('aa\nbbbb\n'); - modifier.appendMarked('-patch-\n-patch-\n', 'patch', {callback}); - modifier.appendMarked('-hunk-\ndd', 'hunk', {}); + inserter.append('aa\nbbbb\n'); + inserter.appendMarked('-patch-\n-patch-\n', 'patch', {callback}); + inserter.appendMarked('-hunk-\ndd', 'hunk', {}); assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` 0000 @@ -145,7 +145,7 @@ describe('PatchBuffer', function() { assert.lengthOf(patchBuffer.findMarkers('hunk', {}), 0); assert.isFalse(callback.called); - modifier.apply(); + inserter.apply(); assert.lengthOf(patchBuffer.findMarkers('patch', {}), 1); const [marker] = patchBuffer.findMarkers('patch', {}); @@ -157,15 +157,15 @@ describe('PatchBuffer', function() { const before1 = patchBuffer.markPosition('hunk', [4, 0]); const after0 = patchBuffer.markPosition('patch', [4, 0]); - const modifier = patchBuffer.createModifierAt([4, 0]); - modifier.keepBefore([before0, before1]); - modifier.keepAfter([after0]); + const inserter = patchBuffer.createInserterAt([4, 0]); + inserter.keepBefore([before0, before1]); + inserter.keepAfter([after0]); let marker = null; const callback = m => { marker = m; }; - modifier.appendMarked('A\nB\nC\nD\nE\n', 'addition', {callback}); + inserter.appendMarked('A\nB\nC\nD\nE\n', 'addition', {callback}); - modifier.apply(); + inserter.apply(); assert.deepEqual(before0.getRange().serialize(), [[1, 0], [4, 0]]); assert.deepEqual(before1.getRange().serialize(), [[4, 0], [4, 0]]); @@ -189,7 +189,7 @@ describe('PatchBuffer', function() { const mAfter = patchBuffer.markRange('deletion', [[7, 0], [7, 4]]); patchBuffer - .createModifierAt([3, 2]) + .createInserterAt([3, 2]) .appendPatchBuffer(subPatchBuffer) .apply(); From 400d1b821d94dc49fb844cbd9dd7c4bf9a9e49b2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 18 Jan 2019 13:57:30 -0500 Subject: [PATCH 1852/4053] Rename Inserter methods as "insert..." instead of "append..." --- lib/models/patch/patch-buffer.js | 10 +++++----- test/models/patch/patch-buffer.test.js | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 4177d33ef0..4852742825 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -107,23 +107,23 @@ class Inserter { } } - append(text) { + insert(text) { const insertedRange = this.patchBuffer.getBuffer().insert(this.insertionPoint, text); this.insertionPoint = insertedRange.end; return this; } - appendMarked(text, layerName, markerOpts) { + insertMarked(text, layerName, markerOpts) { const start = this.insertionPoint.copy(); - this.append(text); + this.insert(text); const end = this.insertionPoint.copy(); this.markerBlueprints.push({layerName, range: new Range(start, end), markerOpts}); return this; } - appendPatchBuffer(subPatchBuffer) { + insertPatchBuffer(subPatchBuffer) { const baseOffset = this.insertionPoint.copy(); - this.append(subPatchBuffer.getBuffer().getText()); + this.insert(subPatchBuffer.getBuffer().getText()); const subMarkerMap = new Map(); for (const layerName of LAYER_NAMES) { diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index 3efc44a53c..570143f87d 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -84,10 +84,10 @@ describe('PatchBuffer', function() { const cb0 = sinon.spy(); const cb1 = sinon.spy(); - inserter.append('0010\n'); - inserter.appendMarked('0011\n', 'patch', {invalidate: 'never', callback: cb0}); - inserter.append('0012\n'); - inserter.appendMarked('0013\n0014\n', 'hunk', {invalidate: 'surround', callback: cb1}); + inserter.insert('0010\n'); + inserter.insertMarked('0011\n', 'patch', {invalidate: 'never', callback: cb0}); + inserter.insert('0012\n'); + inserter.insertMarked('0013\n0014\n', 'hunk', {invalidate: 'surround', callback: cb1}); assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` ${TEXT}0010 @@ -118,9 +118,9 @@ describe('PatchBuffer', function() { const inserter = patchBuffer.createInserterAt([4, 2]); const callback = sinon.spy(); - inserter.append('aa\nbbbb\n'); - inserter.appendMarked('-patch-\n-patch-\n', 'patch', {callback}); - inserter.appendMarked('-hunk-\ndd', 'hunk', {}); + inserter.insert('aa\nbbbb\n'); + inserter.insertMarked('-patch-\n-patch-\n', 'patch', {callback}); + inserter.insertMarked('-hunk-\ndd', 'hunk', {}); assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` 0000 @@ -163,7 +163,7 @@ describe('PatchBuffer', function() { let marker = null; const callback = m => { marker = m; }; - inserter.appendMarked('A\nB\nC\nD\nE\n', 'addition', {callback}); + inserter.insertMarked('A\nB\nC\nD\nE\n', 'addition', {callback}); inserter.apply(); @@ -190,7 +190,7 @@ describe('PatchBuffer', function() { patchBuffer .createInserterAt([3, 2]) - .appendPatchBuffer(subPatchBuffer) + .insertPatchBuffer(subPatchBuffer) .apply(); assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` From 37f9ae6cb1f991f5119b7c4767b06ce1750e0783 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 18 Jan 2019 14:13:53 -0500 Subject: [PATCH 1853/4053] Produce Maps of equivalent Markers after extract/insert PatchBuffers --- lib/models/patch/patch-buffer.js | 8 +++-- test/models/patch/patch-buffer.test.js | 44 ++++++++++++++++++-------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 4852742825..19c7979c7d 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -76,7 +76,7 @@ export default class PatchBuffer { } this.buffer.setTextInRange(range, ''); - return subBuffer; + return {patchBuffer: subBuffer, markerMap}; } } @@ -121,7 +121,7 @@ class Inserter { return this; } - insertPatchBuffer(subPatchBuffer) { + insertPatchBuffer(subPatchBuffer, opts) { const baseOffset = this.insertionPoint.copy(); this.insert(subPatchBuffer.getBuffer().getText()); @@ -140,6 +140,10 @@ class Inserter { } } + if (opts.callback) { + opts.callback(subMarkerMap); + } + return this; } diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index 570143f87d..75a7de3b2e 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -34,14 +34,14 @@ describe('PatchBuffer', function() { }); it('extracts a subset of the buffer and layers as a new LayeredBuffer', function() { - patchBuffer.markRange('patch', [[1, 0], [3, 0]]); // before - patchBuffer.markRange('hunk', [[2, 0], [4, 2]]); // before, ending at the extraction point - patchBuffer.markRange('hunk', [[4, 2], [5, 0]]); // within - patchBuffer.markRange('patch', [[6, 0], [7, 1]]); // within - patchBuffer.markRange('hunk', [[7, 1], [9, 0]]); // after, starting at the extraction point - patchBuffer.markRange('patch', [[8, 0], [10, 0]]); // after + const m0 = patchBuffer.markRange('patch', [[1, 0], [3, 0]]); // before + const m1 = patchBuffer.markRange('hunk', [[2, 0], [4, 2]]); // before, ending at the extraction point + const m2 = patchBuffer.markRange('hunk', [[4, 2], [5, 0]]); // within + const m3 = patchBuffer.markRange('patch', [[6, 0], [7, 1]]); // within + const m4 = patchBuffer.markRange('hunk', [[7, 1], [9, 0]]); // after, starting at the extraction point + const m5 = patchBuffer.markRange('patch', [[8, 0], [10, 0]]); // after - const subPatchBuffer = patchBuffer.extractPatchBuffer([[4, 2], [7, 1]]); + const {patchBuffer: subPatchBuffer, markerMap} = patchBuffer.extractPatchBuffer([[4, 2], [7, 1]]); assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` 0000 @@ -61,6 +61,17 @@ describe('PatchBuffer', function() { patchBuffer.findMarkers('hunk', {}).map(m => m.getRange().serialize()), [[[2, 0], [4, 2]], [[4, 2], [6, 0]]], ); + assert.deepEqual(m0.getRange().serialize(), [[1, 0], [3, 0]]); + assert.deepEqual(m1.getRange().serialize(), [[2, 0], [4, 2]]); + assert.isTrue(m2.isDestroyed()); + assert.isTrue(m3.isDestroyed()); + assert.deepEqual(m4.getRange().serialize(), [[4, 2], [6, 0]]); + assert.deepEqual(m5.getRange().serialize(), [[5, 0], [7, 0]]); + + assert.isFalse(markerMap.has(m0)); + assert.isFalse(markerMap.has(m1)); + assert.isFalse(markerMap.has(m4)); + assert.isFalse(markerMap.has(m5)); assert.strictEqual(subPatchBuffer.getBuffer().getText(), dedent` 04 @@ -76,6 +87,8 @@ describe('PatchBuffer', function() { subPatchBuffer.findMarkers('patch', {}).map(m => m.getRange().serialize()), [[[2, 0], [3, 1]]], ); + assert.deepEqual(markerMap.get(m2).getRange().serialize(), [[0, 0], [1, 0]]); + assert.deepEqual(markerMap.get(m3).getRange().serialize(), [[2, 0], [3, 1]]); }); describe('deferred-marking modifications', function() { @@ -181,16 +194,17 @@ describe('PatchBuffer', function() { cc `); - subPatchBuffer.markPosition('patch', [0, 0]); - subPatchBuffer.markRange('hunk', [[0, 0], [1, 4]]); - subPatchBuffer.markRange('addition', [[1, 2], [2, 2]]); + const m0 = subPatchBuffer.markPosition('patch', [0, 0]); + const m1 = subPatchBuffer.markRange('hunk', [[0, 0], [1, 4]]); + const m2 = subPatchBuffer.markRange('addition', [[1, 2], [2, 2]]); const mBefore = patchBuffer.markRange('deletion', [[0, 0], [2, 0]]); const mAfter = patchBuffer.markRange('deletion', [[7, 0], [7, 4]]); + let markerMap; patchBuffer .createInserterAt([3, 2]) - .insertPatchBuffer(subPatchBuffer) + .insertPatchBuffer(subPatchBuffer, {callback: m => { markerMap = m; }}) .apply(); assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` @@ -211,21 +225,25 @@ describe('PatchBuffer', function() { assert.deepEqual(mBefore.getRange().serialize(), [[0, 0], [2, 0]]); assert.deepEqual(mAfter.getRange().serialize(), [[9, 0], [9, 4]]); + assert.isFalse(markerMap.has(mBefore)); + assert.isFalse(markerMap.has(mAfter)); assert.deepEqual( patchBuffer.findMarkers('patch', {}).map(m => m.getRange().serialize()), [[[3, 2], [3, 2]]], ); - assert.deepEqual( patchBuffer.findMarkers('hunk', {}).map(m => m.getRange().serialize()), [[[3, 2], [4, 4]]], ); - assert.deepEqual( patchBuffer.findMarkers('addition', {}).map(m => m.getRange().serialize()), [[[4, 2], [5, 2]]], ); + + assert.deepEqual(markerMap.get(m0).getRange().serialize(), [[3, 2], [3, 2]]); + assert.deepEqual(markerMap.get(m1).getRange().serialize(), [[3, 2], [4, 4]]); + assert.deepEqual(markerMap.get(m2).getRange().serialize(), [[4, 2], [5, 2]]); }); }); }); From 498c7200566a5b5f013bb428b829d8819774a1bd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 18 Jan 2019 15:57:09 -0500 Subject: [PATCH 1854/4053] Inserter::markWhile() to mark all changes made in a block --- lib/models/patch/patch-buffer.js | 14 +++++++++----- test/models/patch/patch-buffer.test.js | 17 +++++++++++++---- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 19c7979c7d..18801b14a1 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -107,6 +107,14 @@ class Inserter { } } + markWhile(layerName, block, markerOpts) { + const start = this.insertionPoint.copy(); + block(); + const end = this.insertionPoint.copy(); + this.markerBlueprints.push({layerName, range: new Range(start, end), markerOpts}); + return this; + } + insert(text) { const insertedRange = this.patchBuffer.getBuffer().insert(this.insertionPoint, text); this.insertionPoint = insertedRange.end; @@ -114,11 +122,7 @@ class Inserter { } insertMarked(text, layerName, markerOpts) { - const start = this.insertionPoint.copy(); - this.insert(text); - const end = this.insertionPoint.copy(); - this.markerBlueprints.push({layerName, range: new Range(start, end), markerOpts}); - return this; + return this.markWhile(layerName, () => this.insert(text), markerOpts); } insertPatchBuffer(subPatchBuffer, opts) { diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index 75a7de3b2e..95c65fe364 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -96,11 +96,14 @@ describe('PatchBuffer', function() { const inserter = patchBuffer.createInserterAtEnd(); const cb0 = sinon.spy(); const cb1 = sinon.spy(); + const cb2 = sinon.spy(); - inserter.insert('0010\n'); - inserter.insertMarked('0011\n', 'patch', {invalidate: 'never', callback: cb0}); - inserter.insert('0012\n'); - inserter.insertMarked('0013\n0014\n', 'hunk', {invalidate: 'surround', callback: cb1}); + inserter.markWhile('addition', () => { + inserter.insert('0010\n'); + inserter.insertMarked('0011\n', 'patch', {invalidate: 'never', callback: cb0}); + inserter.insert('0012\n'); + inserter.insertMarked('0013\n0014\n', 'hunk', {invalidate: 'surround', callback: cb1}); + }, {invalidate: 'never', callback: cb2}); assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` ${TEXT}0010 @@ -113,6 +116,8 @@ describe('PatchBuffer', function() { assert.isFalse(cb0.called); assert.isFalse(cb1.called); + assert.isFalse(cb2.called); + assert.lengthOf(patchBuffer.findMarkers('addition', {}), 0); assert.lengthOf(patchBuffer.findMarkers('patch', {}), 0); assert.lengthOf(patchBuffer.findMarkers('hunk', {}), 0); @@ -125,6 +130,10 @@ describe('PatchBuffer', function() { assert.lengthOf(patchBuffer.findMarkers('hunk', {}), 1); const [marker1] = patchBuffer.findMarkers('hunk', {}); assert.isTrue(cb1.calledWith(marker1)); + + assert.lengthOf(patchBuffer.findMarkers('addition', {}), 1); + const [marker2] = patchBuffer.findMarkers('addition', {}); + assert.isTrue(cb2.calledWith(marker2)); }); it('inserts into the middle of an existing buffer', function() { From 5c8181e44c6f7eac977254b64bd9701c8906046f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 18 Jan 2019 16:45:16 -0500 Subject: [PATCH 1855/4053] Rewrite buildHunks() to use an Inserter --- lib/models/patch/builder.js | 149 +++++++++++++++--------------------- 1 file changed, 63 insertions(+), 86 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index af0814e94e..a2de6603b3 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -195,94 +195,71 @@ const CHANGEKIND = { '\\': NoNewline, }; -function buildHunks(diff, patchBuffer, insertRow, existingMarker = null) { - const hunks = []; - - const patchStartRow = insertRow; - let bufferRow = patchStartRow; - let nextLineLength = 0; - - if (diff.hunks.length === 0) { - const patchMarker = patchBuffer.markPosition( - Patch.layerName, - [patchStartRow, 0], - {invalidate: 'never', exclusive: true}, - ); - - return [hunks, patchMarker]; - } - - for (const hunkData of diff.hunks) { - const bufferStartRow = bufferRow; - - const regions = []; - - let LastChangeKind = null; - let currentRangeStart = bufferRow; - let lastLineLength = 0; - - const finishCurrentRange = () => { - if (currentRangeStart === bufferRow) { - return; - } +function buildHunks(diff, patchBuffer, insertRow) { + const inserter = patchBuffer.createInserterAt([insertRow, 0]); - regions.push( - new LastChangeKind( - patchBuffer.markRange( - LastChangeKind.layerName, - [[currentRangeStart, 0], [bufferRow - 1, lastLineLength]], - {invalidate: 'never', exclusive: false}, - ), - ), - ); - currentRangeStart = bufferRow; - }; - - for (const lineText of hunkData.lines) { - const bufferLine = lineText.slice(1) + '\n'; - nextLineLength = lineText.length - 1; - patchBuffer.getBuffer().insert([bufferRow, 0], bufferLine); - - const ChangeKind = CHANGEKIND[lineText[0]]; - if (ChangeKind === undefined) { - throw new Error(`Unknown diff status character: "${lineText[0]}"`); - } - - if (ChangeKind !== LastChangeKind) { - finishCurrentRange(); - } - - LastChangeKind = ChangeKind; - bufferRow++; - lastLineLength = nextLineLength; + let patchMarker = null; + const hunks = []; + inserter.markWhile(Patch.layerName, () => { + for (const rawHunk of diff.hunks) { + const regions = []; + + inserter.markWhile(Hunk.layerName, () => { + let currentRegionText = ''; + let CurrentRegionKind = null; + + function finishRegion() { + if (currentRegionText === '' || CurrentRegionKind === null) { + return; + } + + inserter.insertMarked(currentRegionText, CurrentRegionKind.layerName, { + invalidate: 'never', + exclusive: false, + callback: regionMarker => { regions.push(new CurrentRegionKind(regionMarker)); }, + }); + } + + for (const rawLine of rawHunk.lines) { + const NextRegionKind = CHANGEKIND[rawLine[0]]; + if (NextRegionKind === undefined) { + throw new Error(`Unknown diff status character: "${rawLine[0]}"`); + } + const nextLine = rawLine.slice(1) + '\n'; + + if (NextRegionKind === CurrentRegionKind) { + currentRegionText += nextLine; + continue; + } else { + finishRegion(); + + CurrentRegionKind = NextRegionKind; + currentRegionText = nextLine; + } + } + finishRegion(); + }, { + invalidate: 'never', + exclusive: false, + callback: hunkMarker => { + hunks.push(new Hunk({ + oldStartRow: rawHunk.oldStartLine, + newStartRow: rawHunk.newStartLine, + oldRowCount: rawHunk.oldLineCount, + newRowCount: rawHunk.newLineCount, + sectionHeading: rawHunk.heading, + marker: hunkMarker, + regions, + })); + }, + }); } - finishCurrentRange(); - - hunks.push(new Hunk({ - oldStartRow: hunkData.oldStartLine, - newStartRow: hunkData.newStartLine, - oldRowCount: hunkData.oldLineCount, - newRowCount: hunkData.newLineCount, - sectionHeading: hunkData.heading, - marker: patchBuffer.markRange( - Hunk.layerName, - [[bufferStartRow, 0], [bufferRow - 1, nextLineLength]], - {invalidate: 'never', exclusive: false}, - ), - regions, - })); - } - - let patchMarker = existingMarker; - if (patchMarker) { - patchMarker.setRange([[patchStartRow, 0], [bufferRow - 1, nextLineLength]], {exclusive: false}); - } else { - patchMarker = patchBuffer.markRange( - Patch.layerName, - [[patchStartRow, 0], [bufferRow - 1, nextLineLength]], - {invalidate: 'never', exclusive: false}, - ); - } + }, { + invalidate: 'never', + exclusive: false, + callback: marker => { patchMarker = marker; }, + }); + inserter.apply(); return [hunks, patchMarker]; } From 1a74e83b02712812ce1cd776d7ffa99860765d74 Mon Sep 17 00:00:00 2001 From: annthurium Date: Tue, 22 Jan 2019 11:45:55 -0800 Subject: [PATCH 1856/4053] [wip] make patch model use `nextLayeredBuffer` not sure why getBuffer() sometimes does not work, but nextLayeredBuffer.buffer does... --- lib/models/patch/patch.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 129e23cfd3..d5c5982ccd 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -192,9 +192,9 @@ export default class Patch { } } - const marker = nextLayeredBuffer.markRange( + const marker = nextLayeredBuffer.buffer.markRange( this.constructor.layerName, - [[0, 0], [nextLayeredBuffer.getBuffer().getLastRow() - 1, Infinity]], + [[0, 0], [nextLayeredBuffer.buffer.getLastRow() - 1, Infinity]], {invalidate: 'never', exclusive: false}, ); @@ -427,7 +427,7 @@ class BufferBuilder { // The ranges provided to builder methods are expected to be valid within the original buffer. Account for // the position of the Patch within its original TextBuffer, and any existing content already on the next // TextBuffer. - this.offset = this.buffer.getLastRow() - originalBaseOffset; + this.offset = this.nextLayeredBuffer.buffer.getLastRow() - originalBaseOffset; this.hunkBufferText = ''; this.hunkRowCount = 0; @@ -470,15 +470,13 @@ class BufferBuilder { } latestHunkWasIncluded() { - this.nextLayeredBuffer.getBuffer().append(this.hunkBufferText, {normalizeLineEndings: false}); + this.nextLayeredBuffer.buffer.append(this.hunkBufferText, {normalizeLineEndings: false}); const regions = this.hunkRegions.map(({RegionKind, range}) => { - return new RegionKind( - this.nextLayeredBuffer.getLayer(RegionKind.layerName, range, {invalidate: 'never', exclusive: false}), - ); + return new RegionKind(this.nextLayeredBuffer.layers[RegionKind.layerName]); }); - const marker = this.nextLayeredBuffer.markRange('hunk', this.hunkRange, {invalidate: 'never', exclusive: false}); + const marker = this.nextLayeredBuffer.buffer.markRange('hunk', this.hunkRange, {invalidate: 'never', exclusive: false}); this.hunkBufferText = ''; this.hunkRowCount = 0; From 510c88766daaaa511f7e7e9f688b3ebfdbecc1ff Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 15:25:02 -0500 Subject: [PATCH 1857/4053] PatchBuffer::inspect() to debug marker placement --- lib/models/patch/patch-buffer.js | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 18801b14a1..e5dca106e7 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -1,4 +1,5 @@ import {TextBuffer, Range, Point} from 'atom'; +import {inspect} from 'util'; const LAYER_NAMES = ['unchanged', 'addition', 'deletion', 'nonewline', 'hunk', 'patch']; @@ -78,6 +79,49 @@ export default class PatchBuffer { this.buffer.setTextInRange(range, ''); return {patchBuffer: subBuffer, markerMap}; } + + inspect() { + let inspectString = ''; + + const increasingMarkers = []; + for (const layerName of LAYER_NAMES) { + for (const marker of this.findMarkers(layerName, {})) { + increasingMarkers.push({layerName, point: marker.getRange().start, start: true, id: marker.id}); + increasingMarkers.push({layerName, point: marker.getRange().end, end: true, id: marker.id}); + } + } + increasingMarkers.sort((a, b) => { + const cmp = a.point.compare(b.point); + if (cmp !== 0) { + return cmp; + } else if (a.start && b.start) { + return 0; + } else if (a.start && !b.start) { + return -1; + } else if (!a.start && b.start) { + return 1; + } else { + return 0; + } + }); + + let inspectPoint = Point.fromObject([0, 0]); + for (const marker of increasingMarkers) { + if (!marker.point.isEqual(inspectPoint)) { + inspectString += inspect(this.buffer.getTextInRange([inspectPoint, marker.point])) + '\n'; + } + + if (marker.start) { + inspectString += ` start ${marker.layerName}@${marker.id}\n`; + } else if (marker.end) { + inspectString += ` end ${marker.layerName}@${marker.id}\n`; + } + + inspectPoint = marker.point; + } + + return inspectString; + } } class Inserter { From 18ff065dfbe6935499a8e531ff67065149078812 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 15:34:10 -0500 Subject: [PATCH 1858/4053] PatchBuffer::findAllMarkers() to query markers across all layers --- lib/models/patch/patch-buffer.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index e5dca106e7..834b3e785a 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -28,6 +28,13 @@ export default class PatchBuffer { return this.layers[layerName].findMarkers(...args); } + findAllMarkers(...args) { + return LAYER_NAMES.reduce((arr, layerName) => { + arr.push(...this.findMarkers(layerName, ...args)); + return arr; + }, []); + } + markPosition(layerName, ...args) { return this.layers[layerName].markPosition(...args); } From 5283be0ba884d4b628133b32d058c6c4fcbc2ef3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 15:34:24 -0500 Subject: [PATCH 1859/4053] Clip insertion point to valid buffer positions --- lib/models/patch/patch-buffer.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 834b3e785a..2ce96fe416 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -133,9 +133,11 @@ export default class PatchBuffer { class Inserter { constructor(patchBuffer, insertionPoint) { + const clipped = patchBuffer.getBuffer().clipPosition(insertionPoint); + this.patchBuffer = patchBuffer; - this.startPoint = insertionPoint.copy(); - this.insertionPoint = insertionPoint; + this.startPoint = clipped.copy(); + this.insertionPoint = clipped.copy(); this.markerBlueprints = []; this.markersBefore = new Set(); From c56becb6b1f56cc263a182fa45d441e04b7e37e3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 15:34:30 -0500 Subject: [PATCH 1860/4053] Fluent APIs --- lib/models/patch/patch-buffer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 2ce96fe416..936e9a7735 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -150,6 +150,7 @@ class Inserter { this.markersBefore.add(marker); } } + return this; } keepAfter(markers) { @@ -158,6 +159,7 @@ class Inserter { this.markersAfter.add(marker); } } + return this; } markWhile(layerName, block, markerOpts) { From d6e5947b5cf7e38f0556e7cee374806e279cd833 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 15:34:53 -0500 Subject: [PATCH 1861/4053] No need to manually touch up patch/hunk markers --- lib/models/patch/builder.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index a2de6603b3..bcf95bd6fb 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -71,16 +71,6 @@ export function buildMultiFilePatch(diffs, options) { } const filePatches = actions.map(action => action()); - - // Fix markers for patches with no hunks. - // Head position was moved everytime lines were appended. - filePatches.forEach(filePatch => { - if (filePatch.getHunks().length === 0) { - const marker = filePatch.getMarker(); - marker.setHeadPosition(marker.getTailPosition()); - } - }); - return new MultiFilePatch({patchBuffer, filePatches}); } From 4c39a4bb83e5f535a36bb69800702596bfc9c9c2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 15:36:12 -0500 Subject: [PATCH 1862/4053] Insert newlines *between* consecutive patches, hunks, regions --- lib/models/patch/builder.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index bcf95bd6fb..4caf56db52 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -189,11 +189,28 @@ function buildHunks(diff, patchBuffer, insertRow) { const inserter = patchBuffer.createInserterAt([insertRow, 0]); let patchMarker = null; + let firstHunk = true; const hunks = []; + + // Separate multiple patches on the same buffer with an unmarked newline + if (patchBuffer.getBuffer().getLength() > 0) { + console.log('inserting newline to separate Patches'); + inserter.insert('\n'); + } + inserter.markWhile(Patch.layerName, () => { for (const rawHunk of diff.hunks) { + let firstRegion = true; const regions = []; + // Separate hunks with an unmarked newline + if (firstHunk) { + firstHunk = false; + } else { + console.log('inserting newline to separate Hunks'); + inserter.insert('\n'); + } + inserter.markWhile(Hunk.layerName, () => { let currentRegionText = ''; let CurrentRegionKind = null; @@ -203,6 +220,14 @@ function buildHunks(diff, patchBuffer, insertRow) { return; } + // Separate regions with an unmarked newline + if (firstRegion) { + firstRegion = false; + } else { + console.log('inserting newline to separate Regions'); + inserter.insert('\n'); + } + inserter.insertMarked(currentRegionText, CurrentRegionKind.layerName, { invalidate: 'never', exclusive: false, @@ -215,9 +240,12 @@ function buildHunks(diff, patchBuffer, insertRow) { if (NextRegionKind === undefined) { throw new Error(`Unknown diff status character: "${rawLine[0]}"`); } - const nextLine = rawLine.slice(1) + '\n'; + const nextLine = rawLine.slice(1); if (NextRegionKind === CurrentRegionKind) { + if (currentRegionText !== '') { + currentRegionText += '\n'; + } currentRegionText += nextLine; continue; } else { From 62951be35ecd5f24a47dda299ad14ec887c17f8b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 15:36:41 -0500 Subject: [PATCH 1863/4053] Need IIFEs for callbacks created within loops --- lib/models/patch/builder.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 4caf56db52..5a57832296 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -231,7 +231,9 @@ function buildHunks(diff, patchBuffer, insertRow) { inserter.insertMarked(currentRegionText, CurrentRegionKind.layerName, { invalidate: 'never', exclusive: false, - callback: regionMarker => { regions.push(new CurrentRegionKind(regionMarker)); }, + callback: (function(_regions, _CurrentRegionKind) { + return regionMarker => { _regions.push(new _CurrentRegionKind(regionMarker)); }; + })(regions, CurrentRegionKind), }); } @@ -259,17 +261,19 @@ function buildHunks(diff, patchBuffer, insertRow) { }, { invalidate: 'never', exclusive: false, - callback: hunkMarker => { - hunks.push(new Hunk({ - oldStartRow: rawHunk.oldStartLine, - newStartRow: rawHunk.newStartLine, - oldRowCount: rawHunk.oldLineCount, - newRowCount: rawHunk.newLineCount, - sectionHeading: rawHunk.heading, - marker: hunkMarker, - regions, - })); - }, + callback: (function(_hunks, _rawHunk, _regions) { + return hunkMarker => { + _hunks.push(new Hunk({ + oldStartRow: _rawHunk.oldStartLine, + newStartRow: _rawHunk.newStartLine, + oldRowCount: _rawHunk.oldLineCount, + newRowCount: _rawHunk.newLineCount, + sectionHeading: _rawHunk.heading, + marker: hunkMarker, + regions: _regions, + })); + }; + })(hunks, rawHunk, regions), }); } }, { From 0187f4c2cec163430e34fea5a7b6a88a43ff8ac1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 15:37:04 -0500 Subject: [PATCH 1864/4053] buildHunks() always inserts at the end --- lib/models/patch/builder.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 5a57832296..555f54a173 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -120,12 +120,12 @@ function singleDiffFilePatch(diff, patchBuffer, opts) { return FilePatch.createDelayedFilePatch( oldFile, newFile, patchMarker, TOO_LARGE, () => { - const [hunks] = buildHunks(diff, patchBuffer, insertRow, patchMarker); + const [hunks] = buildHunks(diff, patchBuffer); return new Patch({status: diff.status, hunks, marker: patchMarker}); }, ); } else { - const [hunks, patchMarker] = buildHunks(diff, patchBuffer, patchBuffer.getBuffer().getLastRow()); + const [hunks, patchMarker] = buildHunks(diff, patchBuffer); const patch = new Patch({status: diff.status, hunks, marker: patchMarker}); return new FilePatch(oldFile, newFile, patch); @@ -172,7 +172,7 @@ function dualDiffFilePatch(diff1, diff2, patchBuffer, opts) { // TODO: do something with large diff too } - const [hunks, patchMarker] = buildHunks(contentChangeDiff, patchBuffer, patchBuffer.getBuffer().getLastRow()); + const [hunks, patchMarker] = buildHunks(contentChangeDiff, patchBuffer); const patch = new Patch({status, hunks, marker: patchMarker}); return new FilePatch(oldFile, newFile, patch); @@ -185,8 +185,9 @@ const CHANGEKIND = { '\\': NoNewline, }; -function buildHunks(diff, patchBuffer, insertRow) { - const inserter = patchBuffer.createInserterAt([insertRow, 0]); +function buildHunks(diff, patchBuffer) { + const inserter = patchBuffer.createInserterAtEnd() + .keepBefore(patchBuffer.findAllMarkers({endPosition: patchBuffer.getInsertionPoint()})); let patchMarker = null; let firstHunk = true; From 07f3c88a7d4de3873c6b61980801c4552eb35e4c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 15:37:52 -0500 Subject: [PATCH 1865/4053] :fire: trailing newline --- test/models/patch/builder.test.js | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 75949de05e..a8cd12b2db 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -79,7 +79,7 @@ describe('buildFilePatch', function() { const bufferText = 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\nline-6\nline-7\nline-8\nline-9\nline-10\n' + - 'line-11\nline-12\nline-13\nline-14\nline-15\nline-16\nline-17\nline-18\n'; + 'line-11\nline-12\nline-13\nline-14\nline-15\nline-16\nline-17\nline-18'; assert.strictEqual(buffer.getText(), bufferText); assertInPatch(p, buffer).hunks( @@ -113,7 +113,7 @@ describe('buildFilePatch', function() { {kind: 'unchanged', string: ' line-13\n', range: [[13, 0], [13, 7]]}, {kind: 'deletion', string: '-line-14\n-line-15\n', range: [[14, 0], [15, 7]]}, {kind: 'addition', string: '+line-16\n+line-17\n', range: [[16, 0], [17, 7]]}, - {kind: 'unchanged', string: ' line-18\n', range: [[18, 0], [18, 7]]}, + {kind: 'unchanged', string: ' line-18', range: [[18, 0], [18, 7]]}, ], }, ); @@ -229,7 +229,7 @@ describe('buildFilePatch', function() { assert.isFalse(p.getNewFile().isPresent()); assert.strictEqual(p.getPatch().getStatus(), 'deleted'); - const bufferText = 'line-0\nline-1\nline-2\nline-3\n\n'; + const bufferText = 'line-0\nline-1\nline-2\nline-3\n'; assert.strictEqual(buffer.getText(), bufferText); assertInPatch(p, buffer).hunks( @@ -238,7 +238,7 @@ describe('buildFilePatch', function() { endRow: 4, header: '@@ -1,5 +0,0 @@', regions: [ - {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n-line-3\n-\n', range: [[0, 0], [4, 0]]}, + {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n-line-3\n-', range: [[0, 0], [4, 0]]}, ], }, ); @@ -276,7 +276,7 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getNewMode(), '100755'); assert.strictEqual(p.getPatch().getStatus(), 'added'); - const bufferText = 'line-0\nline-1\nline-2\n'; + const bufferText = 'line-0\nline-1\nline-2'; assert.strictEqual(buffer.getText(), bufferText); assertInPatch(p, buffer).hunks( @@ -285,7 +285,7 @@ describe('buildFilePatch', function() { endRow: 2, header: '@@ -0,0 +1,3 @@', regions: [ - {kind: 'addition', string: '+line-0\n+line-1\n+line-2\n', range: [[0, 0], [2, 6]]}, + {kind: 'addition', string: '+line-0\n+line-1\n+line-2', range: [[0, 0], [2, 6]]}, ], }, ); @@ -319,7 +319,7 @@ describe('buildFilePatch', function() { assert.lengthOf(multiFilePatch.getFilePatches(), 1); const [p] = multiFilePatch.getFilePatches(); const buffer = multiFilePatch.getBuffer(); - assert.strictEqual(buffer.getText(), 'line-0\nline-1\n No newline at end of file\n'); + assert.strictEqual(buffer.getText(), 'line-0\nline-1\n No newline at end of file'); assertInPatch(p, buffer).hunks({ startRow: 0, @@ -328,7 +328,7 @@ describe('buildFilePatch', function() { regions: [ {kind: 'addition', string: '+line-0\n', range: [[0, 0], [0, 6]]}, {kind: 'deletion', string: '-line-1\n', range: [[1, 0], [1, 6]]}, - {kind: 'nonewline', string: '\\ No newline at end of file\n', range: [[2, 0], [2, 26]]}, + {kind: 'nonewline', string: '\\ No newline at end of file', range: [[2, 0], [2, 26]]}, ], }); }); @@ -383,13 +383,13 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getNewSymlink(), 'the-destination'); assert.strictEqual(p.getStatus(), 'deleted'); - assert.strictEqual(buffer.getText(), 'line-0\nline-1\n'); + assert.strictEqual(buffer.getText(), 'line-0\nline-1'); assertInPatch(p, buffer).hunks({ startRow: 0, endRow: 1, header: '@@ -0,0 +0,2 @@', regions: [ - {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 6]]}, + {kind: 'addition', string: '+line-0\n+line-1', range: [[0, 0], [1, 6]]}, ], }); }); @@ -442,13 +442,13 @@ describe('buildFilePatch', function() { assert.isNull(p.getNewSymlink()); assert.strictEqual(p.getStatus(), 'added'); - assert.strictEqual(buffer.getText(), 'line-0\nline-1\n'); + assert.strictEqual(buffer.getText(), 'line-0\nline-1'); assertInPatch(p, buffer).hunks({ startRow: 0, endRow: 1, header: '@@ -0,2 +0,0 @@', regions: [ - {kind: 'deletion', string: '-line-0\n-line-1\n', range: [[0, 0], [1, 6]]}, + {kind: 'deletion', string: '-line-0\n-line-1', range: [[0, 0], [1, 6]]}, ], }); }); @@ -500,13 +500,13 @@ describe('buildFilePatch', function() { assert.strictEqual(p.getNewSymlink(), 'the-destination'); assert.strictEqual(p.getStatus(), 'deleted'); - assert.strictEqual(buffer.getText(), 'line-0\nline-1\n'); + assert.strictEqual(buffer.getText(), 'line-0\nline-1'); assertInPatch(p, buffer).hunks({ startRow: 0, endRow: 1, header: '@@ -0,0 +0,2 @@', regions: [ - {kind: 'addition', string: '+line-0\n+line-1\n', range: [[0, 0], [1, 6]]}, + {kind: 'addition', string: '+line-0\n+line-1', range: [[0, 0], [1, 6]]}, ], }); }); @@ -599,7 +599,7 @@ describe('buildFilePatch', function() { mp.getBuffer().getText(), 'line-0\nline-1\nline-2\nline-3\nline-4\nline-5\nline-6\n' + 'line-5\nline-6\nline-7\nline-8\n' + - 'line-0\nline-1\nline-2\n', + 'line-0\nline-1\nline-2', ); assert.strictEqual(mp.getFilePatches()[0].getOldPath(), 'first'); @@ -637,7 +637,7 @@ describe('buildFilePatch', function() { assertInFilePatch(mp.getFilePatches()[2], buffer).hunks( { startRow: 11, endRow: 13, header: '@@ -1,0 +1,3 @@', regions: [ - {kind: 'addition', string: '+line-0\n+line-1\n+line-2\n', range: [[11, 0], [13, 6]]}, + {kind: 'addition', string: '+line-0\n+line-1\n+line-2', range: [[11, 0], [13, 6]]}, ], }, ); From f4ea1a2e6155274bcbe4c1cbb4ac759a475896bd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 15:41:03 -0500 Subject: [PATCH 1866/4053] Remove console.logs :eyes: --- lib/models/patch/builder.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 555f54a173..b993d74214 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -195,7 +195,6 @@ function buildHunks(diff, patchBuffer) { // Separate multiple patches on the same buffer with an unmarked newline if (patchBuffer.getBuffer().getLength() > 0) { - console.log('inserting newline to separate Patches'); inserter.insert('\n'); } @@ -208,7 +207,6 @@ function buildHunks(diff, patchBuffer) { if (firstHunk) { firstHunk = false; } else { - console.log('inserting newline to separate Hunks'); inserter.insert('\n'); } @@ -225,7 +223,6 @@ function buildHunks(diff, patchBuffer) { if (firstRegion) { firstRegion = false; } else { - console.log('inserting newline to separate Regions'); inserter.insert('\n'); } From 57c03e16bb5681373ab92acb89c5b7fdd1bb02c8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 15:50:29 -0500 Subject: [PATCH 1867/4053] Mark zero-length patches --- lib/models/patch/builder.js | 4 ++-- test/models/patch/builder.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index b993d74214..df38c7807e 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -193,8 +193,8 @@ function buildHunks(diff, patchBuffer) { let firstHunk = true; const hunks = []; - // Separate multiple patches on the same buffer with an unmarked newline - if (patchBuffer.getBuffer().getLength() > 0) { + // Separate multiple non-empty patches on the same buffer with an unmarked newline + if (patchBuffer.getBuffer().getLength() > 0 && diff.hunks.length > 0) { inserter.insert('\n'); } diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index a8cd12b2db..3555a3fc48 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -740,7 +740,7 @@ describe('buildFilePatch', function() { assert.strictEqual(fp3.getNewPath(), 'third'); assertInFilePatch(fp3, buffer).hunks({ startRow: 7, endRow: 9, header: '@@ -1,3 +1,0 @@', regions: [ - {kind: 'deletion', string: '-line-0\n-line-1\n-line-2\n', range: [[7, 0], [9, 6]]}, + {kind: 'deletion', string: '-line-0\n-line-1\n-line-2', range: [[7, 0], [9, 6]]}, ], }); }); @@ -794,7 +794,7 @@ describe('buildFilePatch', function() { assert.strictEqual(mp.getFilePatches()[1].getOldPath(), 'second'); assert.deepEqual(mp.getFilePatches()[1].getHunks(), []); - assert.deepEqual(mp.getFilePatches()[1].getMarker().getRange().serialize(), [[7, 0], [7, 0]]); + assert.deepEqual(mp.getFilePatches()[1].getMarker().getRange().serialize(), [[6, 6], [6, 6]]); assert.strictEqual(mp.getFilePatches()[2].getOldPath(), 'third'); assert.deepEqual(mp.getFilePatches()[2].getMarker().getRange().serialize(), [[7, 0], [10, 6]]); From 869ec0ebacc76148cd610392dcc349a8b0bdb927 Mon Sep 17 00:00:00 2001 From: annthurium Date: Tue, 22 Jan 2019 16:09:50 -0800 Subject: [PATCH 1868/4053] [wip] make Patch model tests use a PatchBuffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sometimes `Region.marker` is a `Marker` (as it should be) and sometimes it’s a `MarkerLayer` (wtf). I think Patch.clone() is the problem but needs more investigation. Pushing what I've got so others can take a look tomorrow. --- lib/models/patch/patch.js | 8 ++++---- test/models/patch/patch.test.js | 11 ++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index d5c5982ccd..ede2804b97 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -192,9 +192,9 @@ export default class Patch { } } - const marker = nextLayeredBuffer.buffer.markRange( + const marker = nextLayeredBuffer.markRange( this.constructor.layerName, - [[0, 0], [nextLayeredBuffer.buffer.getLastRow() - 1, Infinity]], + [[0, 0], [nextLayeredBuffer.getBuffer().getLastRow() - 1, Infinity]], {invalidate: 'never', exclusive: false}, ); @@ -427,7 +427,7 @@ class BufferBuilder { // The ranges provided to builder methods are expected to be valid within the original buffer. Account for // the position of the Patch within its original TextBuffer, and any existing content already on the next // TextBuffer. - this.offset = this.nextLayeredBuffer.buffer.getLastRow() - originalBaseOffset; + this.offset = this.nextLayeredBuffer.getBuffer().getLastRow() - originalBaseOffset; this.hunkBufferText = ''; this.hunkRowCount = 0; @@ -476,7 +476,7 @@ class BufferBuilder { return new RegionKind(this.nextLayeredBuffer.layers[RegionKind.layerName]); }); - const marker = this.nextLayeredBuffer.buffer.markRange('hunk', this.hunkRange, {invalidate: 'never', exclusive: false}); + const marker = this.nextLayeredBuffer.markRange('hunk', this.hunkRange, {invalidate: 'never', exclusive: false}); this.hunkBufferText = ''; this.hunkRowCount = 0; diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index b5989ab3fe..a543deec8f 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -1,11 +1,12 @@ import {TextBuffer} from 'atom'; import Patch from '../../../lib/models/patch/patch'; +import PatchBuffer from '../../../lib/models/patch/patch-buffer'; import Hunk from '../../../lib/models/patch/hunk'; import {Unchanged, Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; import {assertInPatch} from '../../helpers'; -describe('Patch', function() { +describe.only('Patch', function() { it('has some standard accessors', function() { const buffer = new TextBuffer({text: 'bufferText'}); const layers = buildLayers(buffer); @@ -165,9 +166,7 @@ describe('Patch', function() { let stageLayeredBuffer; beforeEach(function() { - const stageBuffer = new TextBuffer(); - const stageLayers = buildLayers(stageBuffer); - stageLayeredBuffer = {buffer: stageBuffer, layers: stageLayers}; + stageLayeredBuffer = new PatchBuffer(); }); it('creates a patch that applies selected lines from only the first hunk', function() { @@ -372,9 +371,7 @@ describe('Patch', function() { let unstageLayeredBuffer; beforeEach(function() { - const unstageBuffer = new TextBuffer(); - const unstageLayers = buildLayers(unstageBuffer); - unstageLayeredBuffer = {buffer: unstageBuffer, layers: unstageLayers}; + unstageLayeredBuffer = new PatchBuffer(); }); it('creates a patch that updates the index to unapply selected lines from a single hunk', function() { From d1fcf707d55e97b2f498270bb19b99372d009661 Mon Sep 17 00:00:00 2001 From: annthurium Date: Tue, 22 Jan 2019 16:11:40 -0800 Subject: [PATCH 1869/4053] :fire: .only --- test/models/patch/patch.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index a543deec8f..8307d832b7 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -6,7 +6,7 @@ import Hunk from '../../../lib/models/patch/hunk'; import {Unchanged, Addition, Deletion, NoNewline} from '../../../lib/models/patch/region'; import {assertInPatch} from '../../helpers'; -describe.only('Patch', function() { +describe('Patch', function() { it('has some standard accessors', function() { const buffer = new TextBuffer({text: 'bufferText'}); const layers = buildLayers(buffer); From d13d1849d82d2bfa097adaf929b172b25e6c4688 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 21:42:23 -0500 Subject: [PATCH 1870/4053] DelayedPatch :point_right: HiddenPatch --- test/models/patch/builder.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 3555a3fc48..1f279c5c32 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -802,7 +802,7 @@ describe('buildFilePatch', function() { }); describe('with a large diff', function() { - it('creates a DelayedPatch when the diff is "too large"', function() { + it('creates a HiddenPatch when the diff is "too large"', function() { const mfp = buildMultiFilePatch([ { oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100755', status: 'modified', @@ -847,7 +847,7 @@ describe('buildFilePatch', function() { ); }); - it('re-parse a DelayedPatch as a Patch', function() { + it('re-parse a HiddenPatch as a Patch', function() { const mfp = buildMultiFilePatch([ { oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100644', status: 'modified', @@ -1028,7 +1028,7 @@ describe('buildFilePatch', function() { assert.deepEqual(fp2.getMarker().getRange().serialize(), [[4, 0], [4, 0]]); }); - it('does not create a DelayedPatch when the patch has been explicitly expanded', function() { + it('does not create a HiddenPatch when the patch has been explicitly expanded', function() { const mfp = buildMultiFilePatch([ { oldPath: 'big/file.txt', oldMode: '100644', newPath: 'big/file.txt', newMode: '100755', status: 'modified', From d25db25239f98660849b63332818f1fbbacac2af Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 21:42:43 -0500 Subject: [PATCH 1871/4053] More trailing newlines --- test/models/patch/builder.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 1f279c5c32..d47ccdff1e 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -841,7 +841,7 @@ describe('buildFilePatch', function() { { startRow: 0, endRow: 1, header: '@@ -1,1 +1,2 @@', regions: [ {kind: 'unchanged', string: ' line-4\n', range: [[0, 0], [0, 6]]}, - {kind: 'addition', string: '+line-5\n', range: [[1, 0], [1, 6]]}, + {kind: 'addition', string: '+line-5', range: [[1, 0], [1, 6]]}, ], }, ); @@ -879,7 +879,7 @@ describe('buildFilePatch', function() { {kind: 'unchanged', string: ' line-0\n', range: [[0, 0], [0, 6]]}, {kind: 'addition', string: '+line-1\n', range: [[1, 0], [1, 6]]}, {kind: 'deletion', string: '-line-2\n', range: [[2, 0], [2, 6]]}, - {kind: 'unchanged', string: ' line-3\n', range: [[3, 0], [3, 6]]}, + {kind: 'unchanged', string: ' line-3', range: [[3, 0], [3, 6]]}, ], }, ); @@ -940,7 +940,7 @@ describe('buildFilePatch', function() { {kind: 'unchanged', string: ' line-0\n', range: [[4, 0], [4, 6]]}, {kind: 'addition', string: '+line-1\n', range: [[5, 0], [5, 6]]}, {kind: 'deletion', string: '-line-2\n', range: [[6, 0], [6, 6]]}, - {kind: 'unchanged', string: ' line-3\n', range: [[7, 0], [7, 6]]}, + {kind: 'unchanged', string: ' line-3', range: [[7, 0], [7, 6]]}, ], }, ); @@ -978,7 +978,7 @@ describe('buildFilePatch', function() { {kind: 'unchanged', string: ' line-0\n', range: [[9, 0], [9, 6]]}, {kind: 'addition', string: '+line-1\n', range: [[10, 0], [10, 6]]}, {kind: 'deletion', string: '-line-2\n', range: [[11, 0], [11, 6]]}, - {kind: 'unchanged', string: ' line-3\n', range: [[12, 0], [12, 6]]}, + {kind: 'unchanged', string: ' line-3', range: [[12, 0], [12, 6]]}, ], }, ); @@ -1054,7 +1054,7 @@ describe('buildFilePatch', function() { {kind: 'unchanged', string: ' line-0\n', range: [[0, 0], [0, 6]]}, {kind: 'addition', string: '+line-1\n', range: [[1, 0], [1, 6]]}, {kind: 'deletion', string: '-line-2\n', range: [[2, 0], [2, 6]]}, - {kind: 'unchanged', string: ' line-3\n', range: [[3, 0], [3, 6]]}, + {kind: 'unchanged', string: ' line-3', range: [[3, 0], [3, 6]]}, ], }, ); From 92a113087c05a53ec3194be57aa1f4ddbe3c83fa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 21:44:08 -0500 Subject: [PATCH 1872/4053] updateMarkers methods to consume marker Maps --- lib/models/patch/file-patch.js | 8 ++------ lib/models/patch/hunk.js | 7 +++++++ lib/models/patch/patch.js | 7 +++++++ lib/models/patch/region.js | 4 ++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 7c76d6a0e9..677e8a4e3e 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -119,12 +119,8 @@ export default class FilePatch { return this.getPatch().getHunks(); } - triggerDelayedRender() { - const nextPatch = this.patch.parseFn(this.patch); - if (nextPatch !== this.patch) { - this.patch = nextPatch; - this.didChangeRenderStatus(); - } + updateMarkers(map) { + return this.patch.updateMarkers(map); } triggerCollapse = () => { diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 074f86f7f3..6922203bb9 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -141,6 +141,13 @@ export default class Hunk { this.marker = markable.markRange(this.getRange(), {invalidate: 'never', exclusive: false}); } + updateMarkers(map) { + this.marker = map.get(this.marker) || this.marker; + for (const region of this.regions) { + region.updateMarkers(map); + } + } + toStringIn(buffer) { return this.getRegions().reduce((str, region) => str + region.toStringIn(buffer), this.getHeader() + '\n'); } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index ede2804b97..3bc53902cc 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -83,6 +83,13 @@ export default class Patch { ); } + updateMarkers(map) { + this.marker = map.get(this.marker) || this.marker; + for (const hunk of this.hunks) { + hunk.updateMarkers(map); + } + } + getMaxLineNumberWidth() { const lastHunk = this.hunks[this.hunks.length - 1]; return lastHunk ? lastHunk.getMaxLineNumberWidth() : 0; diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index d246b911bc..ea30b4b024 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -111,6 +111,10 @@ class Region { this.marker = markable.markRange(this.getRange(), {invalidate: 'never', exclusive: false}); } + updateMarkers(map) { + this.marker = map.get(this.marker) || this.marker; + } + toStringIn(buffer) { const raw = buffer.getTextInRange(this.getRange()); return this.constructor.origin + raw.replace(/\r?\n/g, '$&' + this.constructor.origin) + From 70ea3024605130ae4849d805ba2fe91d72020f2f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 21:45:08 -0500 Subject: [PATCH 1873/4053] Implement HiddenPatch --- lib/models/patch/file-patch.js | 4 +-- lib/models/patch/patch.js | 48 ++++++++++++++-------------------- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 677e8a4e3e..923f0b039d 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -9,8 +9,8 @@ export default class FilePatch { return new this(nullFile, nullFile, Patch.createNull()); } - static createDelayedFilePatch(oldFile, newFile, marker, renderStatus, parseFn) { - return new this(oldFile, newFile, Patch.createDelayedPatch(marker, renderStatus, parseFn)); + static createHiddenFilePatch(oldFile, newFile, marker, renderStatus, showFn) { + return new this(oldFile, newFile, Patch.createHiddenPatch(marker, renderStatus, showFn)); } constructor(oldFile, newFile, patch) { diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 3bc53902cc..e734b130b5 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -28,22 +28,16 @@ export default class Patch { return new NullPatch(); } - static createDelayedPatch(marker, renderStatus, parseFn) { - return new this({status: null, hunks: [], marker, renderStatus, parseFn}); + static createHiddenPatch(marker, renderStatus, showFn) { + return new HiddenPatch(marker, renderStatus, showFn); } - constructor({status, hunks, marker, renderStatus, parseFn}) { + constructor({status, hunks, marker}) { this.status = status; this.hunks = hunks; this.marker = marker; - this.renderStatus = renderStatus || EXPANDED; this.changedLineCount = this.getHunks().reduce((acc, hunk) => acc + hunk.changedLineCount(), 0); - - if (parseFn) { - // Override the prototype's method - this.parseFn = parseFn; - } } getStatus() { @@ -100,24 +94,11 @@ export default class Patch { status: opts.status !== undefined ? opts.status : this.getStatus(), hunks: opts.hunks !== undefined ? opts.hunks : this.getHunks(), marker: opts.marker !== undefined ? opts.marker : this.getMarker(), - renderStatus: opts.renderStatus !== undefined ? opts.renderStatus : this.getRenderStatus(), }); } - collapsed() { - if (this.getRenderStatus() === COLLAPSED) { - return this; } - - return this.clone({renderStatus: COLLAPSED}); - } - - expanded() { - if (this.getRenderStatus() === EXPANDED) { - return this; } - - return this.clone({renderStatus: EXPANDED}); } buildStagePatchForLines(originalBuffer, nextLayeredBuffer, rowSet) { @@ -329,11 +310,24 @@ export default class Patch { } getRenderStatus() { - return this.renderStatus; + return EXPANDED; + } +} + +class HiddenPatch extends Patch { + constructor(marker, renderStatus, showFn) { + super({status: null, hunks: [], marker}); + + this.renderStatus = renderStatus; + this.show = showFn; } - parseFn() { - return this; + getInsertionPoint() { + return this.getRange().end; + } + + getRenderStatus() { + return this.renderStatus; } } @@ -420,10 +414,6 @@ class NullPatch { getRenderStatus() { return EXPANDED; } - - parseFn() { - return this; - } } class BufferBuilder { From 24515a1033cd037c349deaf0aafdeb00bce96941 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 21:45:30 -0500 Subject: [PATCH 1874/4053] Get starting and ending Marker sets --- lib/models/patch/patch.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index e734b130b5..3a2e1e1367 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -97,8 +97,32 @@ export default class Patch { }); } + /* Return the set of Markers owned by this Patch that butt up against the patch's beginning. */ + getStartingMarkers() { + const markers = [this.marker]; + if (this.hunks.length > 0) { + const firstHunk = this.hunks[0]; + markers.push(firstHunk.getMarker()); + if (firstHunk.getRegions().length > 0) { + const firstRegion = firstHunk.getRegions()[0]; + markers.push(firstRegion.getMarker()); + } } + return markers; + } + + /* Return the set of Markers owned by this Patch that end at the patch's end position. */ + getEndingMarkers() { + const markers = [this.marker]; + if (this.hunks.length > 0) { + const lastHunk = this.hunks[this.hunks.length - 1]; + markers.push(lastHunk.getMarker()); + if (lastHunk.getRegions().length > 0) { + const lastRegion = lastHunk.getRegions()[lastHunk.getRegions().length - 1]; + markers.push(lastRegion.getMarker()); + } } + return markers; } buildStagePatchForLines(originalBuffer, nextLayeredBuffer, rowSet) { From f27f5ab8509b05e70aaccf374c5256ea3a756e9c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 21:45:56 -0500 Subject: [PATCH 1875/4053] Create a HiddenFilePatch for diff sets that are too large --- lib/models/patch/builder.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index df38c7807e..04164d89d1 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -110,18 +110,19 @@ function singleDiffFilePatch(diff, patchBuffer, opts) { EXPANDED; if (renderStatus === TOO_LARGE) { - const insertRow = patchBuffer.getBuffer().getLastRow(); const patchMarker = patchBuffer.markPosition( Patch.layerName, - [insertRow, 0], + patchBuffer.getBuffer().getEndPosition(), {invalidate: 'never', exclusive: false}, ); - return FilePatch.createDelayedFilePatch( + return FilePatch.createHiddenFilePatch( oldFile, newFile, patchMarker, TOO_LARGE, () => { - const [hunks] = buildHunks(diff, patchBuffer); - return new Patch({status: diff.status, hunks, marker: patchMarker}); + const subPatchBuffer = new PatchBuffer(); + const [hunks, nextPatchMarker] = buildHunks(diff, patchBuffer); + const nextPatch = new Patch({status: diff.status, hunks, marker: nextPatchMarker}); + return {patch: nextPatch, patchBuffer: subPatchBuffer}; }, ); } else { From 36ac87cd0af4d9e54f71902aca67a3c4874a1608 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 21:46:28 -0500 Subject: [PATCH 1876/4053] Move collapsing and expanding to MFP --- lib/models/patch/file-patch.js | 53 ++++++++++++++++++++++------ lib/models/patch/multi-file-patch.js | 18 +++++++++- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 923f0b039d..aec7896cb9 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -1,7 +1,7 @@ import {Emitter} from 'event-kit'; import {nullFile} from './file'; -import Patch from './patch'; +import Patch, {COLLAPSED} from './patch'; import {toGitPathSep} from '../../helpers'; export default class FilePatch { @@ -123,20 +123,43 @@ export default class FilePatch { return this.patch.updateMarkers(map); } - triggerCollapse = () => { - const nextPatch = this.patch.collapsed(); - if (nextPatch !== this.patch) { - this.patch = nextPatch; - this.didChangeRenderStatus(); + triggerCollapseIn(patchBuffer) { + if (!this.patch.getRenderStatus().isVisible()) { + return false; } + + const {patchBuffer: subPatchBuffer, markerMap} = patchBuffer.extractPatchBuffer(this.patch.getRange()); + + const oldPatch = this.patch; + this.patch = Patch.createHiddenPatch(oldPatch.getMarker(), COLLAPSED, () => { + const [marker] = subPatchBuffer.findMarkers(Patch.layerName, {}); + const newPatch = oldPatch.clone({marker}); + + return {patch: newPatch, patchBuffer: subPatchBuffer}; + }); + this.updateMarkers(markerMap); + + this.didChangeRenderStatus(); + return true; } - triggerExpand = () => { - const nextPatch = this.patch.expanded(); - if (nextPatch !== this.patch) { - this.patch = nextPatch; - this.didChangeRenderStatus(); + triggerExpandIn(patchBuffer, {before, after}) { + if (this.patch.getRenderStatus().isVisible()) { + return false; } + + const {patch: nextPatch, patchBuffer: subPatchBuffer} = this.patch.show(); + + patchBuffer + .createInserterAt(this.patch.getInsertionPoint()) + .keepBefore(before) + .keepAfter(after) + .insertPatchBuffer(subPatchBuffer, {callback: map => this.updateMarkers(map)}) + .apply(); + + this.patch = nextPatch; + this.didChangeRenderStatus(); + return true; } didChangeRenderStatus() { @@ -155,6 +178,14 @@ export default class FilePatch { ); } + getStartingMarkers() { + return this.patch.getStartingMarkers(); + } + + getEndingMarkers() { + return this.patch.getEndingMarkers(); + } + buildStagePatchForLines(originalBuffer, nextLayeredBuffer, selectedLineSet) { let newFile = this.getNewFile(); if (this.getStatus() === 'deleted') { diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 7d0a9ae8d4..9c0f738bfe 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -98,7 +98,7 @@ export default class MultiFilePatch { } getFilePatchAt(bufferRow) { - if (bufferRow < 0) { + if (bufferRow < 0 || bufferRow > this.patchBuffer.getBuffer().getLastRow()) { return undefined; } const [marker] = this.patchBuffer.findMarkers('patch', {intersectsRow: bufferRow}); @@ -301,6 +301,22 @@ export default class MultiFilePatch { return false; } + collapseFilePatch(filePatch) { + filePatch.triggerCollapseIn(this.patchBuffer); + } + + expandFilePatch(filePatch) { + const range = filePatch.getMarker().getRange(); + + const beforeFilePatch = this.getFilePatchAt(range.start.row - 1); + const before = beforeFilePatch ? beforeFilePatch.getEndingMarkers() : []; + + const afterFilePatch = this.getFilePatchAt(range.end.row + 1); + const after = afterFilePatch ? afterFilePatch.getStartingMarkers() : []; + + filePatch.triggerExpandIn(this.patchBuffer, {before, after}); + } + isPatchTooLargeOrCollapsed = filePatchPath => { const patch = this.filePatchesByPath.get(filePatchPath); if (!patch) { From 4bba2f41570bf1e2e277c84a350238b7cc5173b2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 21:46:42 -0500 Subject: [PATCH 1877/4053] Use getRenderStatus() methods --- lib/views/multi-file-patch-view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 5bb93e7c7b..bab2e28737 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -22,7 +22,7 @@ import CommitDetailItem from '../items/commit-detail-item'; import IssueishDetailItem from '../items/issueish-detail-item'; import File from '../models/patch/file'; -import {TOO_LARGE, COLLAPSED} from '../models/patch/patch'; +import {TOO_LARGE} from '../models/patch/patch'; const executableText = { [File.modes.NORMAL]: 'non executable', @@ -351,7 +351,7 @@ export default class MultiFilePatchView extends React.Component { openFile={() => this.didOpenFile({selectedFilePatch: filePatch})} toggleFile={() => this.props.toggleFile(filePatch)} - isCollapsed={filePatch.getRenderStatus === TOO_LARGE || filePatch.getRenderStatus === COLLAPSED} + isCollapsed={!filePatch.getRenderStatus().isVisible()} triggerCollapse={filePatch.triggerCollapse} triggerExpand={filePatch.triggerExpand} /> From 5ca5f1207fc17d47b5901639670be9a8651fdb57 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 22 Jan 2019 21:47:00 -0500 Subject: [PATCH 1878/4053] Use MFP-level expand and collapse methods --- test/models/patch/builder.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index d47ccdff1e..1f300a7854 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -869,7 +869,7 @@ describe('buildFilePatch', function() { assert.deepEqual(fp.getStartRange().serialize(), [[0, 0], [0, 0]]); assertInFilePatch(fp).hunks(); - fp.triggerDelayedRender(); + mfp.expandFilePatch(fp); assert.strictEqual(fp.getRenderStatus(), EXPANDED); assert.deepEqual(fp.getMarker().getRange().serialize(), [[0, 0], [3, 6]]); @@ -945,7 +945,7 @@ describe('buildFilePatch', function() { }, ); - fp1.triggerDelayedRender(); + mfp.expandFilePatch(fp1); assert.strictEqual(fp0.getRenderStatus(), EXPANDED); assertInFilePatch(fp0, mfp.getBuffer()).hunks( @@ -1021,7 +1021,7 @@ describe('buildFilePatch', function() { assert.deepEqual(fp1.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); assert.deepEqual(fp2.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); - fp1.triggerDelayedRender(); + mfp.expandFilePatch(fp1); assert.deepEqual(fp0.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); assert.deepEqual(fp1.getMarker().getRange().serialize(), [[0, 0], [3, 6]]); From fae0a50bfa9f38e547fed08636303f28b5a23ed9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 12:00:48 -0500 Subject: [PATCH 1879/4053] Show specific marker layers in PatchBuffer::inspect() --- lib/models/patch/patch-buffer.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 936e9a7735..8858fd4f21 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -87,11 +87,16 @@ export default class PatchBuffer { return {patchBuffer: subBuffer, markerMap}; } - inspect() { + inspect(opts = {}) { + const options = { + layerNames: LAYER_NAMES, + ...opts, + }; + let inspectString = ''; const increasingMarkers = []; - for (const layerName of LAYER_NAMES) { + for (const layerName of options.layerNames) { for (const marker of this.findMarkers(layerName, {})) { increasingMarkers.push({layerName, point: marker.getRange().start, start: true, id: marker.id}); increasingMarkers.push({layerName, point: marker.getRange().end, end: true, id: marker.id}); From 219a4093d8b547d0c1563a4c4f7d25345f8951e1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 13:07:05 -0500 Subject: [PATCH 1880/4053] Implement inspect() methods on patch model classes --- lib/models/patch/file-patch.js | 28 ++++++++++++++ lib/models/patch/hunk.js | 22 +++++++++++ lib/models/patch/multi-file-patch.js | 14 +++++++ lib/models/patch/patch.js | 56 ++++++++++++++++++++++++++++ lib/models/patch/region.js | 17 +++++++++ 5 files changed, 137 insertions(+) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index aec7896cb9..d2d57a3c51 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -271,6 +271,34 @@ export default class FilePatch { } } + /* + * Construct a String containing diagnostic information about the internal state of this FilePatch. + */ + inspect(opts = {}) { + const options = { + indent: 0, + ...opts, + }; + + let indentation = ''; + for (let i = 0; i < options.indent; i++) { + indentation += ' '; + } + + let inspectString = `${indentation}(FilePatch `; + if (this.getOldPath() !== this.getNewPath()) { + inspectString += `oldPath=${this.getOldPath()} newPath=${this.getNewPath()}`; + } else { + inspectString += `path=${this.getPath()}`; + } + inspectString += '\n'; + + inspectString += this.patch.inspect({indent: options.indent + 2}); + + inspectString += `${indentation})\n`; + return inspectString; + } + getHeaderString() { const fromPath = this.getOldPath() || this.getNewPath(); const toPath = this.getNewPath() || this.getOldPath(); diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 6922203bb9..c9302e6aa5 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -151,4 +151,26 @@ export default class Hunk { toStringIn(buffer) { return this.getRegions().reduce((str, region) => str + region.toStringIn(buffer), this.getHeader() + '\n'); } + + /* + * Construct a String containing internal diagnostic information. + */ + inspect(opts = {}) { + const options = { + indent: 0, + ...opts, + }; + + let indentation = ''; + for (let i = 0; i < options.indent; i++) { + indentation += ' '; + } + + let inspectString = `${indentation}(Hunk marker=${this.marker.id}\n`; + for (const region of this.regions) { + inspectString += region.inspect({indent: options.indent + 2}); + } + inspectString += `${indentation})\n`; + return inspectString; + } } diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 9c0f738bfe..611a4639df 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -339,6 +339,20 @@ export default class MultiFilePatch { return this.filePatches.map(fp => fp.toStringIn(this.getBuffer())).join(''); } + /* + * Construct a string of diagnostic information useful for debugging. + */ + inspect(opts = {}) { + let inspectString = '(MultiFilePatch'; + inspectString += ` filePatchesByMarker=(${Array.from(this.filePatchesByMarker.keys(), m => m.id).join(', ')})`; + inspectString += ` hunksByMarker=(${Array.from(this.hunksByMarker.keys(), m => m.id).join(', ')})\n`; + for (const filePatch of this.filePatches) { + inspectString += filePatch.inspect({indent: 2}); + } + inspectString += ')\n'; + return inspectString; + } + isEqual(other) { return this.toString() === other.toString(); } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 3a2e1e1367..0e9c681de1 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -329,6 +329,28 @@ export default class Patch { return this.getHunks().reduce((str, hunk) => str + hunk.toStringIn(buffer), ''); } + /* + * Construct a String containing internal diagnostic information. + */ + inspect(opts = {}) { + const options = { + indent: 0, + ...opts, + }; + + let indentation = ''; + for (let i = 0; i < options.indent; i++) { + indentation += ' '; + } + + let inspectString = `${indentation}(Patch marker=${this.marker.id}\n`; + for (const hunk of this.hunks) { + inspectString += hunk.inspect({indent: options.indent + 2}); + } + inspectString += `${indentation})\n`; + return inspectString; + } + isPresent() { return true; } @@ -353,6 +375,23 @@ class HiddenPatch extends Patch { getRenderStatus() { return this.renderStatus; } + + /* + * Construct a String containing internal diagnostic information. + */ + inspect(opts = {}) { + const options = { + indent: 0, + ...opts, + }; + + let indentation = ''; + for (let i = 0; i < options.indent; i++) { + indentation += ' '; + } + + return `${indentation}(HiddenPatch marker=${this.marker.id})\n`; + } } class NullPatch { @@ -431,6 +470,23 @@ class NullPatch { return ''; } + /* + * Construct a String containing internal diagnostic information. + */ + inspect(opts = {}) { + const options = { + indent: 0, + ...opts, + }; + + let indentation = ''; + for (let i = 0; i < options.indent; i++) { + indentation += ' '; + } + + return `${indentation}(NullPatch)\n`; + } + isPresent() { return false; } diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index ea30b4b024..43bf03519a 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -121,6 +121,23 @@ class Region { buffer.lineEndingForRow(this.getRange().end.row); } + /* + * Construct a String containing internal diagnostic information. + */ + inspect(opts = {}) { + const options = { + indent: 0, + ...opts, + }; + + let indentation = ''; + for (let i = 0; i < options.indent; i++) { + indentation += ' '; + } + + return `${indentation}(${this.constructor.name} marker=${this.marker.id})\n`; + } + isChange() { return true; } From 9767a871066704701b98e5a0a4acd0905e906b3b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 13:31:42 -0500 Subject: [PATCH 1881/4053] destroyMarkers() calls to... destroy... the markers --- lib/models/patch/hunk.js | 7 +++++++ lib/models/patch/patch.js | 7 +++++++ lib/models/patch/region.js | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index c9302e6aa5..c8b69aef57 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -148,6 +148,13 @@ export default class Hunk { } } + destroyMarkers() { + this.marker.destroy(); + for (const region of this.regions) { + region.destroyMarkers(); + } + } + toStringIn(buffer) { return this.getRegions().reduce((str, region) => str + region.toStringIn(buffer), this.getHeader() + '\n'); } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 0e9c681de1..b111ddd314 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -77,6 +77,13 @@ export default class Patch { ); } + destroyMarkers() { + this.marker.destroy(); + for (const hunk of this.hunks) { + hunk.destroyMarkers(); + } + } + updateMarkers(map) { this.marker = map.get(this.marker) || this.marker; for (const hunk of this.hunks) { diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index 43bf03519a..4d0a14e200 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -115,6 +115,10 @@ class Region { this.marker = map.get(this.marker) || this.marker; } + destroyMarkers() { + this.marker.destroy(); + } + toStringIn(buffer) { const raw = buffer.getTextInRange(this.getRange()); return this.constructor.origin + raw.replace(/\r?\n/g, '$&' + this.constructor.origin) + From b1d7ff5b70fcfdf4180f894c0d7ce3537d258774 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 13:32:08 -0500 Subject: [PATCH 1882/4053] Build hunk data for expanding too-large patches on the correct buffer --- lib/models/patch/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 04164d89d1..396ab72601 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -120,7 +120,7 @@ function singleDiffFilePatch(diff, patchBuffer, opts) { oldFile, newFile, patchMarker, TOO_LARGE, () => { const subPatchBuffer = new PatchBuffer(); - const [hunks, nextPatchMarker] = buildHunks(diff, patchBuffer); + const [hunks, nextPatchMarker] = buildHunks(diff, subPatchBuffer); const nextPatch = new Patch({status: diff.status, hunks, marker: nextPatchMarker}); return {patch: nextPatch, patchBuffer: subPatchBuffer}; }, From fc3d89c602ba73411ab3e416f47dbba697391b49 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 13:32:37 -0500 Subject: [PATCH 1883/4053] Fix up Marker-keyed maps in MFP after expand or collapse --- lib/models/patch/multi-file-patch.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 611a4639df..8d1dfe3b4d 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -302,10 +302,26 @@ export default class MultiFilePatch { } collapseFilePatch(filePatch) { + this.filePatchesByMarker.delete(filePatch.getMarker()); + for (const hunk of filePatch.getHunks()) { + this.hunksByMarker.delete(hunk.getMarker()); + } + filePatch.triggerCollapseIn(this.patchBuffer); + + this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); + // This hunk collection should be empty, but let's iterate anyway just in case filePatch was already collapsed + for (const hunk of filePatch.getHunks()) { + this.hunksByMarker.set(hunk.getMarker(), hunk); + } } expandFilePatch(filePatch) { + this.filePatchesByMarker.delete(filePatch.getMarker()); + for (const hunk of filePatch.getHunks()) { + this.hunksByMarker.delete(hunk.getMarker()); + } + const range = filePatch.getMarker().getRange(); const beforeFilePatch = this.getFilePatchAt(range.start.row - 1); @@ -315,6 +331,11 @@ export default class MultiFilePatch { const after = afterFilePatch ? afterFilePatch.getStartingMarkers() : []; filePatch.triggerExpandIn(this.patchBuffer, {before, after}); + + this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); + for (const hunk of filePatch.getHunks()) { + this.hunksByMarker.set(hunk.getMarker(), hunk); + } } isPatchTooLargeOrCollapsed = filePatchPath => { From e1a1a5cd07363212874cf336b3a47381b0eba78a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 13:33:01 -0500 Subject: [PATCH 1884/4053] NullPatch implementations of adjacent-marker methods --- lib/models/patch/patch.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index b111ddd314..5880e67da2 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -461,6 +461,14 @@ class NullPatch { } } + getStartingMarkers() { + return []; + } + + getEndingMarkers() { + return []; + } + buildStagePatchForLines() { return this; } From 37c53cae1b4758684b53172fd3beee8591c22f90 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 13:33:34 -0500 Subject: [PATCH 1885/4053] Defer insertPatchBuffer()'s callback until the marker map is populated --- lib/models/patch/patch-buffer.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 8858fd4f21..761eec0df7 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -144,6 +144,7 @@ class Inserter { this.startPoint = clipped.copy(); this.insertionPoint = clipped.copy(); this.markerBlueprints = []; + this.markerMapCallbacks = []; this.markersBefore = new Set(); this.markersAfter = new Set(); @@ -205,7 +206,7 @@ class Inserter { } if (opts.callback) { - opts.callback(subMarkerMap); + this.markerMapCallbacks.push({markerMap: subMarkerMap, callback: opts.callback}); } return this; @@ -222,6 +223,10 @@ class Inserter { } } + for (const {markerMap, callback} of this.markerMapCallbacks) { + callback(markerMap); + } + for (const beforeMarker of this.markersBefore) { if (!beforeMarker.isReversed()) { beforeMarker.setHeadPosition(this.startPoint); From 9d174a3f5b16020a6336a5a4d53ab2d76c77c0e4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 13:34:45 -0500 Subject: [PATCH 1886/4053] More collapse and expand work --- lib/models/patch/file-patch.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index d2d57a3c51..9196e29c6b 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -128,16 +128,16 @@ export default class FilePatch { return false; } - const {patchBuffer: subPatchBuffer, markerMap} = patchBuffer.extractPatchBuffer(this.patch.getRange()); - const oldPatch = this.patch; - this.patch = Patch.createHiddenPatch(oldPatch.getMarker(), COLLAPSED, () => { - const [marker] = subPatchBuffer.findMarkers(Patch.layerName, {}); - const newPatch = oldPatch.clone({marker}); - - return {patch: newPatch, patchBuffer: subPatchBuffer}; + const position = oldPatch.getRange().start.copy(); + const {patchBuffer: subPatchBuffer, markerMap} = patchBuffer.extractPatchBuffer(oldPatch.getRange()); + oldPatch.destroyMarkers(); + oldPatch.updateMarkers(markerMap); + + const patchMarker = patchBuffer.markPosition(Patch.layerName, position, {invalidate: 'never', exclude: true}); + this.patch = Patch.createHiddenPatch(patchMarker, COLLAPSED, () => { + return {patch: oldPatch, patchBuffer: subPatchBuffer}; }); - this.updateMarkers(markerMap); this.didChangeRenderStatus(); return true; @@ -149,14 +149,17 @@ export default class FilePatch { } const {patch: nextPatch, patchBuffer: subPatchBuffer} = this.patch.show(); + const atStart = this.patch.getInsertionPoint().isEqual([0, 0]); patchBuffer .createInserterAt(this.patch.getInsertionPoint()) .keepBefore(before) .keepAfter(after) - .insertPatchBuffer(subPatchBuffer, {callback: map => this.updateMarkers(map)}) + .insert(atStart ? '' : '\n') + .insertPatchBuffer(subPatchBuffer, {callback: map => nextPatch.updateMarkers(map)}) .apply(); + this.patch.destroyMarkers(); this.patch = nextPatch; this.didChangeRenderStatus(); return true; From a0ae01ca9f617d7e206a95bde4468966fc935e9f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 14:03:44 -0500 Subject: [PATCH 1887/4053] Find adjacent FilePatches by array index instead of markers --- lib/models/patch/multi-file-patch.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 8d1dfe3b4d..f47f673c8e 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -317,18 +317,36 @@ export default class MultiFilePatch { } expandFilePatch(filePatch) { + const index = this.filePatches.indexOf(filePatch); + this.filePatchesByMarker.delete(filePatch.getMarker()); for (const hunk of filePatch.getHunks()) { this.hunksByMarker.delete(hunk.getMarker()); } - const range = filePatch.getMarker().getRange(); + const before = []; + let beforeIndex = index - 1; + while (beforeIndex >= 0) { + const beforeFilePatch = this.filePatches[beforeIndex]; + before.push(...beforeFilePatch.getEndingMarkers()); + + if (!beforeFilePatch.getMarker().getRange().isEmpty()) { + break; + } + beforeIndex--; + } - const beforeFilePatch = this.getFilePatchAt(range.start.row - 1); - const before = beforeFilePatch ? beforeFilePatch.getEndingMarkers() : []; + const after = []; + let afterIndex = index + 1; + while (afterIndex < this.filePatches.length) { + const afterFilePatch = this.filePatches[afterIndex]; + after.push(...afterFilePatch.getStartingMarkers()); - const afterFilePatch = this.getFilePatchAt(range.end.row + 1); - const after = afterFilePatch ? afterFilePatch.getStartingMarkers() : []; + if (!afterFilePatch.getMarker().getRange().isEmpty()) { + break; + } + afterIndex++; + } filePatch.triggerExpandIn(this.patchBuffer, {before, after}); From cba6f0578a14e3bd9675837873b7671a3987dfca Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 14:03:50 -0500 Subject: [PATCH 1888/4053] Remove unused argument --- lib/models/patch/multi-file-patch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index f47f673c8e..b9f7f7572f 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -381,7 +381,7 @@ export default class MultiFilePatch { /* * Construct a string of diagnostic information useful for debugging. */ - inspect(opts = {}) { + inspect() { let inspectString = '(MultiFilePatch'; inspectString += ` filePatchesByMarker=(${Array.from(this.filePatchesByMarker.keys(), m => m.id).join(', ')})`; inspectString += ` hunksByMarker=(${Array.from(this.hunksByMarker.keys(), m => m.id).join(', ')})\n`; From 715f10a3823238ab4de29749dfe0c9e72c6b1953 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 14:04:02 -0500 Subject: [PATCH 1889/4053] Touch up expected marker range --- test/models/patch/builder.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 1f300a7854..956a348ec8 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -1025,7 +1025,7 @@ describe('buildFilePatch', function() { assert.deepEqual(fp0.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); assert.deepEqual(fp1.getMarker().getRange().serialize(), [[0, 0], [3, 6]]); - assert.deepEqual(fp2.getMarker().getRange().serialize(), [[4, 0], [4, 0]]); + assert.deepEqual(fp2.getMarker().getRange().serialize(), [[3, 6], [3, 6]]); }); it('does not create a HiddenPatch when the patch has been explicitly expanded', function() { From cff0d2929d8fab24f620a74cf10e06e3a3be6aaa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 14:05:32 -0500 Subject: [PATCH 1890/4053] Correct expected Hunk header --- test/models/patch/builder.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 956a348ec8..9b0ca9f957 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -1050,7 +1050,7 @@ describe('buildFilePatch', function() { assert.deepEqual(fp.getMarker().getRange().serialize(), [[0, 0], [3, 6]]); assertInFilePatch(fp, mfp.getBuffer()).hunks( { - startRow: 0, endRow: 3, header: '@@ -1,1 +1,2 @@', regions: [ + startRow: 0, endRow: 3, header: '@@ -1,3 +1,3 @@', regions: [ {kind: 'unchanged', string: ' line-0\n', range: [[0, 0], [0, 6]]}, {kind: 'addition', string: '+line-1\n', range: [[1, 0], [1, 6]]}, {kind: 'deletion', string: '-line-2\n', range: [[2, 0], [2, 6]]}, From 2d7c81a74a6e1c9b82b3e473edd7fdf8ab1839e9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 15:49:50 -0500 Subject: [PATCH 1891/4053] Build what-the-diff output instead of just model objects --- test/builder/patch.js | 269 ++++++++++++++++++++++-------------------- 1 file changed, 143 insertions(+), 126 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index cd8f45a79f..bc02cf6afd 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -1,93 +1,74 @@ // Builders for classes related to MultiFilePatches. -import PatchBuffer from '../../lib/models/patch/patch-buffer'; -import MultiFilePatch from '../../lib/models/patch/multi-file-patch'; -import FilePatch from '../../lib/models/patch/file-patch'; -import File, {nullFile} from '../../lib/models/patch/file'; -import Patch from '../../lib/models/patch/patch'; -import Hunk from '../../lib/models/patch/hunk'; -import {Unchanged, Addition, Deletion, NoNewline} from '../../lib/models/patch/region'; - -function appendMarked(patchBuffer, layerName, lines) { - const startPosition = patchBuffer.getInsertionPoint(); - patchBuffer.getBuffer().append(lines.join('\n')); - const marker = patchBuffer.markRange( - layerName, - [startPosition, patchBuffer.getInsertionPoint()], - {invalidate: 'never', exclusive: false}, - ); - patchBuffer.getBuffer().append('\n'); - return marker; -} - -function markFrom(patchBuffer, layerName, startPosition) { - const endPosition = patchBuffer.getInsertionPoint().translate([-1, Infinity]); - return patchBuffer.markRange( - layerName, - [startPosition, endPosition], - {invalidate: 'never', exclusive: false}, - ); -} - -function wrapReturn(patchBuffer, object) { - return {buffer: patchBuffer.getBuffer(), layers: patchBuffer.getLayers(), ...object}; -} +import {buildMultiFilePatch} from '../../lib/models/patch/builder'; +import File from '../../lib/models/patch/file'; class MultiFilePatchBuilder { - constructor(patchBuffer = null) { - this.patchBuffer = patchBuffer; - - this.filePatches = []; + constructor() { + this.rawFilePatches = []; } addFilePatch(block = () => {}) { - const filePatch = new FilePatchBuilder(this.patchBuffer); + const filePatch = new FilePatchBuilder(); block(filePatch); - this.filePatches.push(filePatch.build().filePatch); + this.rawFilePatches.push(filePatch.build().raw); return this; } - build() { - return wrapReturn(this.patchBuffer, { - multiFilePatch: new MultiFilePatch({ - patchBuffer: this.patchBuffer, - filePatches: this.filePatches, - }), - }); + build(opts = {}) { + const raw = this.rawFilePatches; + const multiFilePatch = buildMultiFilePatch(raw, opts); + return {raw, multiFilePatch}; } } class FilePatchBuilder { - constructor(patchBuffer = null) { - this.patchBuffer = patchBuffer; - - this.oldFile = new File({path: 'file', mode: File.modes.NORMAL}); - this.newFile = null; + constructor() { + this._oldPath = 'file'; + this._oldMode = File.modes.NORMAL; + this._oldSymlink = null; + this._newPath = null; + this._newMode = null; + this._newSymlink = null; - this.patchBuilder = new PatchBuilder(this.patchBuffer); + this.patchBuilder = new PatchBuilder(); } setOldFile(block) { const file = new FileBuilder(); block(file); - this.oldFile = file.build().file; + const rawFile = file.build(); + this._oldPath = rawFile.path; + this._oldMode = rawFile.mode; + if (rawFile.symlink) { + this._oldSymlink = rawFile.symlink; + } return this; } nullOldFile() { - this.oldFile = nullFile; + this._oldPath = null; + this._oldMode = null; + this._oldSymlink = null; return this; } setNewFile(block) { const file = new FileBuilder(); block(file); - this.newFile = file.build().file; + const rawFile = file.build(); + this._newPath = rawFile.path; + this._newMode = rawFile.mode; + if (rawFile.symlink) { + this._newSymlink = rawFile.symlink; + } return this; } nullNewFile() { - this.newFile = nullFile; + this._newPath = null; + this._newMode = null; + this._newSymlink = null; return this; } @@ -101,26 +82,54 @@ class FilePatchBuilder { return this; } - renderStatus(...args) { - this.patchBuilder.renderStatus(...args); - return this; - } - empty() { this.patchBuilder.empty(); return this; } - build() { - const {patch} = this.patchBuilder.build(); + build(opts = {}) { + const {raw: rawPatch} = this.patchBuilder.build(); + + if (this._newPath === null) { + this._newPath = this._oldPath; + } - if (this.newFile === null) { - this.newFile = this.oldFile.clone(); + if (this._newMode === null) { + this._newMode = this._oldMode; } - return this.patchBuffer.wrapReturn({ - filePatch: new FilePatch(this.oldFile, this.newFile, patch), - }); + if (this._oldSymlink !== null || this._newSymlink !== null) { + if (rawPatch.hunks.length > 0) { + throw new Error('Cannot have both a symlink target and hunk content'); + } + + const lines = []; + if (this._oldSymlink !== null) { + lines.push(` ${this._oldSymlink}`); + } + if (this._oldSymlink !== null && this._newSymlink !== null) { + lines.push(' --'); + } + if (this._newSymlink !== null) { + lines.push(` ${this._newSymlink}`); + } + } + + const raw = { + oldPath: this._oldPath, + oldMode: this._oldMode, + newPath: this._newPath, + newMode: this._newMode, + ...rawPatch, + }; + + const mfp = buildMultiFilePatch([raw], opts); + const [filePatch] = mfp.getFilePatches(); + + return { + raw, + filePatch, + }; } } @@ -142,37 +151,27 @@ class FileBuilder { } executable() { - return this.mode('100755'); + return this.mode(File.modes.EXECUTABLE); } symlinkTo(destinationPath) { this._symlink = destinationPath; - return this.mode('120000'); + return this.mode(File.modes.SYMLINK); } build() { - return {file: new File({path: this._path, mode: this._mode, symlink: this._symlink})}; + return {path: this._path, mode: this._mode, symlink: this._symlink}; } } class PatchBuilder { - constructor(patchBuffer = null) { - this.patchBuffer = patchBuffer; - - this._renderStatus = undefined; + constructor() { this._status = 'modified'; - this.hunks = []; - - this.patchStart = this.patchBuffer.getInsertionPoint(); + this.rawHunks = []; this.drift = 0; this.explicitlyEmpty = false; } - renderStatus(status) { - this._renderStatus = status; - return this; - } - status(st) { if (['modified', 'added', 'deleted'].indexOf(st) === -1) { throw new Error(`Unrecognized status: ${st} (must be 'modified', 'added' or 'deleted')`); @@ -183,10 +182,10 @@ class PatchBuilder { } addHunk(block = () => {}) { - const builder = new HunkBuilder(this.patchBuffer, this.drift); + const builder = new HunkBuilder(this.drift); block(builder); - const {hunk, drift} = builder.build(); - this.hunks.push(hunk); + const {raw, drift} = builder.build(); + this.rawHunks.push(raw); this.drift = drift; return this; } @@ -197,7 +196,7 @@ class PatchBuilder { } build() { - if (this.hunks.length === 0 && !this.explicitlyEmpty) { + if (this.rawHunks.length === 0 && !this.explicitlyEmpty) { if (this._status === 'modified') { this.addHunk(hunk => hunk.oldRow(1).unchanged('0000').added('0001').deleted('0002').unchanged('0003')); this.addHunk(hunk => hunk.oldRow(10).unchanged('0004').added('0005').deleted('0006').unchanged('0007')); @@ -208,17 +207,27 @@ class PatchBuilder { } } - const marker = markFrom(this.patchBuffer, 'patch', this.patchStart); + const raw = { + status: this._status, + hunks: this.rawHunks, + }; - return wrapReturn(this.patchBuffer, { - patch: new Patch({status: this._status, hunks: this.hunks, marker, renderStatus: this._renderStatus}), - }); + const mfp = buildMultiFilePatch([{ + oldPath: 'file', + oldMode: File.modes.NORMAL, + newPath: 'file', + newMode: File.modes.NORMAL, + ...raw, + }]); + const [filePatch] = mfp.getFilePatches(); + const patch = filePatch.getPatch(); + + return {raw, patch}; } } class HunkBuilder { - constructor(patchBuffer = null, drift = 0) { - this.patchBuffer = patchBuffer; + constructor(drift = 0) { this.drift = drift; this.oldStartRow = 0; @@ -228,8 +237,7 @@ class HunkBuilder { this.sectionHeading = "don't care"; - this.hunkStartPoint = this.patchBuffer.getInsertionPoint(); - this.regions = []; + this.lines = []; } oldRow(rowNumber) { @@ -238,36 +246,38 @@ class HunkBuilder { } unchanged(...lines) { - this.regions.push(new Unchanged(appendMarked(this.patchBuffer, 'unchanged', lines))); + for (const line of lines) { + this.lines.push(` ${line}`); + } return this; } added(...lines) { - this.regions.push(new Addition(appendMarked(this.patchBuffer, 'addition', lines))); + for (const line of lines) { + this.lines.push(`+${line}`); + } return this; } deleted(...lines) { - this.regions.push(new Deletion(appendMarked(this.patchBuffer, 'deletion', lines))); + for (const line of lines) { + this.lines.push(`-${line}`); + } return this; } noNewline() { - this.regions.push(new NoNewline(appendMarked(this.patchBuffer, 'nonewline', [' No newline at end of file']))); + this.lines.push('\\ No newline at end of file'); return this; } build() { - if (this.regions.length === 0) { + if (this.lines.length === 0) { this.unchanged('0000').added('0001').deleted('0002').unchanged('0003'); } if (this.oldRowCount === null) { - this.oldRowCount = this.regions.reduce((count, region) => region.when({ - unchanged: () => count + region.bufferRowCount(), - deletion: () => count + region.bufferRowCount(), - default: () => count, - }), 0); + this.oldRowCount = this.lines.filter(line => /^[ -]/.test(line)).length; } if (this.newStartRow === null) { @@ -275,42 +285,49 @@ class HunkBuilder { } if (this.newRowCount === null) { - this.newRowCount = this.regions.reduce((count, region) => region.when({ - unchanged: () => count + region.bufferRowCount(), - addition: () => count + region.bufferRowCount(), - default: () => count, - }), 0); + this.newRowCount = this.lines.filter(line => /^[ +]/.test(line)).length; } - const marker = markFrom(this.patchBuffer, 'hunk', this.hunkStartPoint); - - return wrapReturn(this.patchBuffer, { - hunk: new Hunk({ - oldStartRow: this.oldStartRow, - oldRowCount: this.oldRowCount, - newStartRow: this.newStartRow, - newRowCount: this.newRowCount, - sectionHeading: this.sectionHeading, - marker, - regions: this.regions, - }), + const raw = { + oldStartLine: this.oldStartRow, + oldLineCount: this.oldRowCount, + newStartLine: this.newStartRow, + newLineCount: this.newRowCount, + heading: this.sectionHeading, + lines: this.lines, + }; + + const mfp = buildMultiFilePatch([{ + oldPath: 'file', + oldMode: File.modes.NORMAL, + newPath: 'file', + newMode: File.modes.NORMAL, + status: 'modified', + hunks: [raw], + }]); + const [fp] = mfp.getFilePatches(); + const [hunk] = fp.getHunks(); + + return { + raw, + hunk, drift: this.drift + this.newRowCount - this.oldRowCount, - }); + }; } } export function multiFilePatchBuilder() { - return new MultiFilePatchBuilder(new PatchBuffer()); + return new MultiFilePatchBuilder(); } export function filePatchBuilder() { - return new FilePatchBuilder(new PatchBuffer()); + return new FilePatchBuilder(); } export function patchBuilder() { - return new PatchBuilder(new PatchBuffer()); + return new PatchBuilder(); } export function hunkBuilder() { - return new HunkBuilder(new PatchBuffer()); + return new HunkBuilder(); } From 537fd0efccaa5ca32d4a47e09a79f18ea8c86925 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 16:19:25 -0500 Subject: [PATCH 1892/4053] Fix building symlink related FilePatches --- test/builder/patch.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index bc02cf6afd..dbe3f22d27 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -41,6 +41,7 @@ class FilePatchBuilder { this._oldPath = rawFile.path; this._oldMode = rawFile.mode; if (rawFile.symlink) { + this.empty(); this._oldSymlink = rawFile.symlink; } return this; @@ -60,6 +61,7 @@ class FilePatchBuilder { this._newPath = rawFile.path; this._newMode = rawFile.mode; if (rawFile.symlink) { + this.empty(); this._newSymlink = rawFile.symlink; } return this; @@ -103,16 +105,18 @@ class FilePatchBuilder { throw new Error('Cannot have both a symlink target and hunk content'); } - const lines = []; + const hb = new HunkBuilder(); if (this._oldSymlink !== null) { - lines.push(` ${this._oldSymlink}`); + hb.unchanged(this._oldSymlink); } if (this._oldSymlink !== null && this._newSymlink !== null) { - lines.push(' --'); + hb.unchanged('--'); } if (this._newSymlink !== null) { - lines.push(` ${this._newSymlink}`); + hb.unchanged(this._newSymlink); } + + rawPatch.hunks = [hb.build().raw]; } const raw = { From 6685330b042a644b5061cf35ee895321b31a12e4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 16:19:46 -0500 Subject: [PATCH 1893/4053] ::reMarkOn() accepts a PatchBuffer --- lib/models/patch/hunk.js | 6 +++++- lib/models/patch/region.js | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index c8b69aef57..8bff265dd4 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -138,7 +138,11 @@ export default class Hunk { } reMarkOn(markable) { - this.marker = markable.markRange(this.getRange(), {invalidate: 'never', exclusive: false}); + this.marker = markable.markRange( + this.constructor.layerName, + this.getRange(), + {invalidate: 'never', exclusive: false}, + ); } updateMarkers(map) { diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index 4d0a14e200..c4835d4e83 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -108,7 +108,11 @@ class Region { } reMarkOn(markable) { - this.marker = markable.markRange(this.getRange(), {invalidate: 'never', exclusive: false}); + this.marker = markable.markRange( + this.constructor.layerName, + this.getRange(), + {invalidate: 'never', exclusive: false}, + ); } updateMarkers(map) { From 2cd40d3527624151f4f13c980a4404f65c7e3ce1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 23 Jan 2019 16:20:52 -0500 Subject: [PATCH 1894/4053] Adapt MultiFilePatch tests to model changes --- test/models/patch/multi-file-patch.test.js | 65 ++++++++++------------ 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index b5e665da38..ae2c472137 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -2,16 +2,14 @@ import dedent from 'dedent-js'; import {multiFilePatchBuilder, filePatchBuilder} from '../../builder/patch'; -import {TOO_LARGE} from '../../../lib/models/patch/patch'; import {DEFAULT_OPTIONS} from '../../../lib/models/patch/builder.js'; import MultiFilePatch from '../../../lib/models/patch/multi-file-patch'; import {assertInFilePatch} from '../../helpers'; - describe('MultiFilePatch', function() { - it('creates an empty patch when constructed with no arguments', function() { - const empty = new MultiFilePatch({}); + it('creates an empty patch', function() { + const empty = MultiFilePatch.createNull(); assert.isFalse(empty.anyPresent()); assert.lengthOf(empty.getFilePatches(), 0); }); @@ -52,17 +50,17 @@ describe('MultiFilePatch', function() { assert.strictEqual(dup.getFilePatches(), original.getFilePatches()); }); - it('creates a copy with a new buffer and layer set', function() { - const {buffer, layers} = multiFilePatchBuilder().build(); - const dup = original.clone({buffer, layers}); - - assert.strictEqual(dup.getBuffer(), buffer); - assert.strictEqual(dup.getPatchLayer(), layers.patch); - assert.strictEqual(dup.getHunkLayer(), layers.hunk); - assert.strictEqual(dup.getUnchangedLayer(), layers.unchanged); - assert.strictEqual(dup.getAdditionLayer(), layers.addition); - assert.strictEqual(dup.getDeletionLayer(), layers.deletion); - assert.strictEqual(dup.getNoNewlineLayer(), layers.noNewline); + it('creates a copy with a new PatchBuffer', function() { + const {multiFilePatch} = multiFilePatchBuilder().build(); + const dup = original.clone({patchBuffer: multiFilePatch.getLayeredBuffer()}); + + assert.strictEqual(dup.getBuffer(), multiFilePatch.getBuffer()); + assert.strictEqual(dup.getPatchLayer(), multiFilePatch.getPatchLayer()); + assert.strictEqual(dup.getHunkLayer(), multiFilePatch.getHunkLayer()); + assert.strictEqual(dup.getUnchangedLayer(), multiFilePatch.getUnchangedLayer()); + assert.strictEqual(dup.getAdditionLayer(), multiFilePatch.getAdditionLayer()); + assert.strictEqual(dup.getDeletionLayer(), multiFilePatch.getDeletionLayer()); + assert.strictEqual(dup.getNoNewlineLayer(), multiFilePatch.getNoNewlineLayer()); assert.strictEqual(dup.getFilePatches(), original.getFilePatches()); }); @@ -272,12 +270,11 @@ describe('MultiFilePatch', function() { +1;1;1 -1;1;2 1;1;3 - `); }); it('adopts a buffer from a previous patch', function() { - const {multiFilePatch: lastMultiPatch, buffer: lastBuffer, layers: lastLayers} = multiFilePatchBuilder() + const {multiFilePatch: lastMultiPatch} = multiFilePatchBuilder() .addFilePatch(fp => { fp.setOldFile(f => f.path('A0.txt')); fp.addHunk(h => h.unchanged('a0').added('a1').deleted('a2').unchanged('a3')); @@ -293,7 +290,7 @@ describe('MultiFilePatch', function() { }) .build(); - const {multiFilePatch: nextMultiPatch, buffer: nextBuffer, layers: nextLayers} = multiFilePatchBuilder() + const {multiFilePatch: nextMultiPatch} = multiFilePatchBuilder() .addFilePatch(fp => { fp.setOldFile(f => f.path('B0.txt')); fp.addHunk(h => h.unchanged('b0', 'b1').added('b2').unchanged('b3', 'b4')); @@ -309,20 +306,19 @@ describe('MultiFilePatch', function() { }) .build(); - assert.notStrictEqual(nextBuffer, lastBuffer); - assert.notStrictEqual(nextLayers, lastLayers); + assert.notStrictEqual(nextMultiPatch.getBuffer(), lastMultiPatch.getBuffer()); nextMultiPatch.adoptBufferFrom(lastMultiPatch); - assert.strictEqual(nextMultiPatch.getBuffer(), lastBuffer); - assert.strictEqual(nextMultiPatch.getPatchLayer(), lastLayers.patch); - assert.strictEqual(nextMultiPatch.getHunkLayer(), lastLayers.hunk); - assert.strictEqual(nextMultiPatch.getUnchangedLayer(), lastLayers.unchanged); - assert.strictEqual(nextMultiPatch.getAdditionLayer(), lastLayers.addition); - assert.strictEqual(nextMultiPatch.getDeletionLayer(), lastLayers.deletion); - assert.strictEqual(nextMultiPatch.getNoNewlineLayer(), lastLayers.noNewline); + assert.strictEqual(nextMultiPatch.getBuffer(), lastMultiPatch.getBuffer()); + assert.strictEqual(nextMultiPatch.getPatchLayer(), lastMultiPatch.getPatchLayer()); + assert.strictEqual(nextMultiPatch.getHunkLayer(), lastMultiPatch.getHunkLayer()); + assert.strictEqual(nextMultiPatch.getUnchangedLayer(), lastMultiPatch.getUnchangedLayer()); + assert.strictEqual(nextMultiPatch.getAdditionLayer(), lastMultiPatch.getAdditionLayer()); + assert.strictEqual(nextMultiPatch.getDeletionLayer(), lastMultiPatch.getDeletionLayer()); + assert.strictEqual(nextMultiPatch.getNoNewlineLayer(), lastMultiPatch.getNoNewlineLayer()); - assert.deepEqual(lastBuffer.getText(), dedent` + assert.deepEqual(nextMultiPatch.getBuffer().getText(), dedent` b0 b1 b2 @@ -337,29 +333,28 @@ describe('MultiFilePatch', function() { b11 b12 No newline at end of file - `); const assertMarkedLayerRanges = (layer, ranges) => { assert.deepEqual(layer.getMarkers().map(m => m.getRange().serialize()), ranges); }; - assertMarkedLayerRanges(lastLayers.patch, [ + assertMarkedLayerRanges(nextMultiPatch.getPatchLayer(), [ [[0, 0], [4, 2]], [[5, 0], [7, 2]], [[8, 0], [13, 26]], ]); - assertMarkedLayerRanges(lastLayers.hunk, [ + assertMarkedLayerRanges(nextMultiPatch.getHunkLayer(), [ [[0, 0], [4, 2]], [[5, 0], [7, 2]], [[8, 0], [11, 3]], [[12, 0], [13, 26]], ]); - assertMarkedLayerRanges(lastLayers.unchanged, [ + assertMarkedLayerRanges(nextMultiPatch.getUnchangedLayer(), [ [[0, 0], [1, 2]], [[3, 0], [4, 2]], [[5, 0], [6, 2]], [[8, 0], [9, 2]], [[11, 0], [11, 3]], ]); - assertMarkedLayerRanges(lastLayers.addition, [ + assertMarkedLayerRanges(nextMultiPatch.getAdditionLayer(), [ [[2, 0], [2, 2]], [[7, 0], [7, 2]], ]); - assertMarkedLayerRanges(lastLayers.deletion, [ + assertMarkedLayerRanges(nextMultiPatch.getDeletionLayer(), [ [[10, 0], [10, 3]], [[12, 0], [12, 3]], ]); - assertMarkedLayerRanges(lastLayers.noNewline, [ + assertMarkedLayerRanges(nextMultiPatch.getNoNewlineLayer(), [ [[13, 0], [13, 26]], ]); From 5af6a872e0a106ac997bc806f1c597a176395fc9 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 24 Jan 2019 09:03:44 -0500 Subject: [PATCH 1895/4053] Distinguish between unset and null files --- test/builder/patch.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index dbe3f22d27..f27429cb92 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -3,6 +3,8 @@ import {buildMultiFilePatch} from '../../lib/models/patch/builder'; import File from '../../lib/models/patch/file'; +const UNSET = Symbol('unset'); + class MultiFilePatchBuilder { constructor() { this.rawFilePatches = []; @@ -27,8 +29,9 @@ class FilePatchBuilder { this._oldPath = 'file'; this._oldMode = File.modes.NORMAL; this._oldSymlink = null; - this._newPath = null; - this._newMode = null; + + this._newPath = UNSET; + this._newMode = UNSET; this._newSymlink = null; this.patchBuilder = new PatchBuilder(); @@ -92,11 +95,11 @@ class FilePatchBuilder { build(opts = {}) { const {raw: rawPatch} = this.patchBuilder.build(); - if (this._newPath === null) { + if (this._newPath === UNSET) { this._newPath = this._oldPath; } - if (this._newMode === null) { + if (this._newMode === UNSET) { this._newMode = this._oldMode; } From 8bab5ed828debc4ee41d73c31f1d9babd0700f28 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 24 Jan 2019 09:04:34 -0500 Subject: [PATCH 1896/4053] Set file patch status on symlink pairs --- test/views/multi-file-patch-view.test.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index d3436debf5..f56be37719 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -1034,7 +1034,13 @@ describe('MultiFilePatchView', function() { const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(fp => { + fp.status('deleted'); fp.setOldFile(f => f.path('f0').symlinkTo('elsewhere')); + fp.nullNewFile(); + }) + .addFilePatch(fp => { + fp.status('added'); + fp.nullOldFile(); fp.setNewFile(f => f.path('f0')); tenLineHunk(fp); }) @@ -1043,12 +1049,25 @@ describe('MultiFilePatchView', function() { tenLineHunk(fp); }) .addFilePatch(fp => { - fp.setNewFile(f => f.path('f2')); + fp.status('deleted'); fp.setOldFile(f => f.path('f2').symlinkTo('somewhere')); + fp.nullNewFile(); + }) + .addFilePatch(fp => { + fp.status('added'); + fp.nullOldFile(); + fp.setNewFile(f => f.path('f2')); tenLineHunk(fp); }) .addFilePatch(fp => { + fp.status('deleted'); fp.setOldFile(f => f.path('f3').symlinkTo('unchanged')); + fp.nullNewFile(); + }) + .addFilePatch(fp => { + fp.status('added'); + fp.nullOldFile(); + fp.setNewFile(f => f.path('f3')); tenLineHunk(fp); }) .addFilePatch(fp => { From 97154cbeb7ef8c69b14ccdcd356f7f1dca5dba7f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 24 Jan 2019 09:12:31 -0500 Subject: [PATCH 1897/4053] fp3 isn't supposed to be a symlink change? --- test/views/multi-file-patch-view.test.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index f56be37719..bf775699f5 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -1060,14 +1060,7 @@ describe('MultiFilePatchView', function() { tenLineHunk(fp); }) .addFilePatch(fp => { - fp.status('deleted'); - fp.setOldFile(f => f.path('f3').symlinkTo('unchanged')); - fp.nullNewFile(); - }) - .addFilePatch(fp => { - fp.status('added'); - fp.nullOldFile(); - fp.setNewFile(f => f.path('f3')); + fp.setOldFile(f => f.path('f3')); tenLineHunk(fp); }) .addFilePatch(fp => { From 5b35a4f5da3d2473e85c683b62925e3eaf52e3fb Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 24 Jan 2019 09:12:42 -0500 Subject: [PATCH 1898/4053] Use MFP-level collapse and expand functions --- lib/views/multi-file-patch-view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index bab2e28737..3fa7418f6a 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -352,8 +352,8 @@ export default class MultiFilePatchView extends React.Component { toggleFile={() => this.props.toggleFile(filePatch)} isCollapsed={!filePatch.getRenderStatus().isVisible()} - triggerCollapse={filePatch.triggerCollapse} - triggerExpand={filePatch.triggerExpand} + triggerCollapse={() => this.props.multiFilePatch.collapseFilePatch(filePatch)} + triggerExpand={() => this.props.multiFilePatch.expandFilePatch(filePatch)} /> {this.renderSymlinkChangeMeta(filePatch)} {this.renderExecutableModeChangeMeta(filePatch)} From ecb49ead4937e509e5fbeb0eba503e8d59c79532 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 24 Jan 2019 09:31:13 -0500 Subject: [PATCH 1899/4053] Retain the TextBuffer within a PatchBuffer --- lib/models/patch/patch-buffer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 761eec0df7..82a1c5d40f 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -6,6 +6,8 @@ const LAYER_NAMES = ['unchanged', 'addition', 'deletion', 'nonewline', 'hunk', ' export default class PatchBuffer { constructor() { this.buffer = new TextBuffer(); + this.buffer.retain(); + this.layers = LAYER_NAMES.reduce((map, layerName) => { map[layerName] = this.buffer.addMarkerLayer(); return map; From e6a74b011fdd9f61788eb8f30664f41adec94c8e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 24 Jan 2019 09:31:23 -0500 Subject: [PATCH 1900/4053] NullPatch::reMarkOn() accepts a PatchBuffer --- lib/models/patch/patch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 5880e67da2..93a6c66a87 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -436,7 +436,7 @@ class NullPatch { } reMarkOn(markable) { - this.marker = markable.markRange(this.getRange(), {invalidate: 'never', exclusive: false}); + this.marker = markable.markRange(Patch.layerName, this.getRange(), {invalidate: 'never', exclusive: false}); } getMaxLineNumberWidth() { From d3fbe3602425aee830dfb7a5e3f7ae7b7d26e93a Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 25 Jan 2019 15:43:01 +0100 Subject: [PATCH 1901/4053] fix filePatches not iterable error when filepatches have not arrived yet --- lib/models/patch/multi-file-patch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index b9f7f7572f..ef38185615 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -11,7 +11,7 @@ export default class MultiFilePatch { constructor({patchBuffer, filePatches}) { this.patchBuffer = patchBuffer; - this.filePatches = filePatches; + this.filePatches = filePatches || []; this.filePatchesByMarker = new Map(); this.filePatchesByPath = new Map(); From ecebd4649ed72742a23c59418c17755ea5e67091 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 25 Jan 2019 15:54:35 +0100 Subject: [PATCH 1902/4053] move chevron to the left to match the old design from #1863 --- lib/views/file-patch-header-view.js | 4 ++-- styles/file-patch-view.less | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 56ba16c466..93c4cdd9cf 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -48,8 +48,8 @@ export default class FilePatchHeaderView extends React.Component { return (
    - {this.renderTitle()} {this.renderCollapseButton()} + {this.renderTitle()} {this.renderButtonGroup()}
    @@ -65,7 +65,7 @@ export default class FilePatchHeaderView extends React.Component { } renderCollapseButton() { - const icon = this.props.isCollapsed ? 'chevron-up' : 'chevron-down'; + const icon = this.props.isCollapsed ? 'chevron-right' : 'chevron-down'; return (

    diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 58902b4a09..66ce84aac0 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -64,6 +64,22 @@ margin-right: @component-padding; } + &-message { + font-family: @font-family; + text-align: center; + margin-bottom: 0; + } + + &-showDiffButton { + background: transparent; + border: none; + color: @text-color-highlight; + font-size: 18px; + &:hover { + color: @syntax-text-color + } + } + &-collapseButton { background: transparent; border: none; From 80a11009bc789a49850f1c06a4c5d63505a697dd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 28 Jan 2019 15:22:32 -0500 Subject: [PATCH 1924/4053] Rename "layeredBuffer" variables to "patchBuffer" --- lib/models/patch/file-patch.js | 8 ++++---- lib/models/patch/multi-file-patch.js | 26 ++++++++++++------------ lib/models/patch/patch.js | 30 ++++++++++++++-------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 468802f633..658bc1ba39 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -198,7 +198,7 @@ export default class FilePatch { return this.patch.getEndingMarkers(); } - buildStagePatchForLines(originalBuffer, nextLayeredBuffer, selectedLineSet) { + buildStagePatchForLines(originalBuffer, nextPatchBuffer, selectedLineSet) { let newFile = this.getNewFile(); if (this.getStatus() === 'deleted') { if ( @@ -215,13 +215,13 @@ export default class FilePatch { const patch = this.patch.buildStagePatchForLines( originalBuffer, - nextLayeredBuffer, + nextPatchBuffer, selectedLineSet, ); return this.clone({newFile, patch}); } - buildUnstagePatchForLines(originalBuffer, nextLayeredBuffer, selectedLineSet) { + buildUnstagePatchForLines(originalBuffer, nextPatchBuffer, selectedLineSet) { const nonNullFile = this.getNewFile().isPresent() ? this.getNewFile() : this.getOldFile(); let oldFile = this.getNewFile(); let newFile = nonNullFile; @@ -249,7 +249,7 @@ export default class FilePatch { const patch = this.patch.buildUnstagePatchForLines( originalBuffer, - nextLayeredBuffer, + nextPatchBuffer, selectedLineSet, ); return this.clone({oldFile, newFile, patch}); diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index ef38185615..e86f75c09c 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -114,11 +114,11 @@ export default class MultiFilePatch { } getStagePatchForLines(selectedLineSet) { - const nextLayeredBuffer = new PatchBuffer(); + const nextPatchBuffer = new PatchBuffer(); const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { - return fp.buildStagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); + return fp.buildStagePatchForLines(this.getBuffer(), nextPatchBuffer, selectedLineSet); }); - return this.clone({patchBuffer: nextLayeredBuffer, filePatches: nextFilePatches}); + return this.clone({patchBuffer: nextPatchBuffer, filePatches: nextFilePatches}); } getStagePatchForHunk(hunk) { @@ -126,11 +126,11 @@ export default class MultiFilePatch { } getUnstagePatchForLines(selectedLineSet) { - const nextLayeredBuffer = new PatchBuffer(); + const nextPatchBuffer = new PatchBuffer(); const nextFilePatches = this.getFilePatchesContaining(selectedLineSet).map(fp => { - return fp.buildUnstagePatchForLines(this.getBuffer(), nextLayeredBuffer, selectedLineSet); + return fp.buildUnstagePatchForLines(this.getBuffer(), nextPatchBuffer, selectedLineSet); }); - return this.clone({patchBuffer: nextLayeredBuffer, filePatches: nextFilePatches}); + return this.clone({patchBuffer: nextPatchBuffer, filePatches: nextFilePatches}); } getUnstagePatchForHunk(hunk) { @@ -213,29 +213,29 @@ export default class MultiFilePatch { } adoptBufferFrom(lastMultiFilePatch) { - const nextLayeredBuffer = lastMultiFilePatch.getLayeredBuffer(); - nextLayeredBuffer.clearAllLayers(); + const nextPatchBuffer = lastMultiFilePatch.getLayeredBuffer(); + nextPatchBuffer.clearAllLayers(); this.filePatchesByMarker.clear(); this.hunksByMarker.clear(); - nextLayeredBuffer.getBuffer().setText(this.getBuffer().getText()); + nextPatchBuffer.getBuffer().setText(this.getBuffer().getText()); for (const filePatch of this.getFilePatches()) { - filePatch.getPatch().reMarkOn(nextLayeredBuffer); + filePatch.getPatch().reMarkOn(nextPatchBuffer); this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); for (const hunk of filePatch.getHunks()) { - hunk.reMarkOn(nextLayeredBuffer); + hunk.reMarkOn(nextPatchBuffer); this.hunksByMarker.set(hunk.getMarker(), hunk); for (const region of hunk.getRegions()) { - region.reMarkOn(nextLayeredBuffer); + region.reMarkOn(nextPatchBuffer); } } } - this.patchBuffer = nextLayeredBuffer; + this.patchBuffer = nextPatchBuffer; } /* diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 93a6c66a87..4bdb3b754d 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -132,9 +132,9 @@ export default class Patch { return markers; } - buildStagePatchForLines(originalBuffer, nextLayeredBuffer, rowSet) { + buildStagePatchForLines(originalBuffer, nextPatchBuffer, rowSet) { const originalBaseOffset = this.getMarker().getRange().start.row; - const builder = new BufferBuilder(originalBuffer, originalBaseOffset, nextLayeredBuffer); + const builder = new BufferBuilder(originalBuffer, originalBaseOffset, nextPatchBuffer); const hunks = []; let newRowDelta = 0; @@ -211,9 +211,9 @@ export default class Patch { } } - const marker = nextLayeredBuffer.markRange( + const marker = nextPatchBuffer.markRange( this.constructor.layerName, - [[0, 0], [nextLayeredBuffer.getBuffer().getLastRow() - 1, Infinity]], + [[0, 0], [nextPatchBuffer.getBuffer().getLastRow() - 1, Infinity]], {invalidate: 'never', exclusive: false}, ); @@ -222,9 +222,9 @@ export default class Patch { return this.clone({hunks, status, marker}); } - buildUnstagePatchForLines(originalBuffer, nextLayeredBuffer, rowSet) { + buildUnstagePatchForLines(originalBuffer, nextPatchBuffer, rowSet) { const originalBaseOffset = this.getMarker().getRange().start.row; - const builder = new BufferBuilder(originalBuffer, originalBaseOffset, nextLayeredBuffer); + const builder = new BufferBuilder(originalBuffer, originalBaseOffset, nextPatchBuffer); const hunks = []; let newRowDelta = 0; @@ -308,9 +308,9 @@ export default class Patch { status = 'added'; } - const marker = nextLayeredBuffer.markRange( + const marker = nextPatchBuffer.markRange( this.constructor.layerName, - [[0, 0], [nextLayeredBuffer.getBuffer().getLastRow(), Infinity]], + [[0, 0], [nextPatchBuffer.getBuffer().getLastRow(), Infinity]], {invalidate: 'never', exclusive: false}, ); @@ -512,14 +512,14 @@ class NullPatch { } class BufferBuilder { - constructor(original, originalBaseOffset, nextLayeredBuffer) { + constructor(original, originalBaseOffset, nextPatchBuffer) { this.originalBuffer = original; - this.nextLayeredBuffer = nextLayeredBuffer; + this.nextPatchBuffer = nextPatchBuffer; // The ranges provided to builder methods are expected to be valid within the original buffer. Account for // the position of the Patch within its original TextBuffer, and any existing content already on the next // TextBuffer. - this.offset = this.nextLayeredBuffer.getBuffer().getLastRow() - originalBaseOffset; + this.offset = this.nextPatchBuffer.getBuffer().getLastRow() - originalBaseOffset; this.hunkBufferText = ''; this.hunkRowCount = 0; @@ -562,13 +562,13 @@ class BufferBuilder { } latestHunkWasIncluded() { - this.nextLayeredBuffer.buffer.append(this.hunkBufferText, {normalizeLineEndings: false}); + this.nextPatchBuffer.buffer.append(this.hunkBufferText, {normalizeLineEndings: false}); const regions = this.hunkRegions.map(({RegionKind, range}) => { - return new RegionKind(this.nextLayeredBuffer.layers[RegionKind.layerName]); + return new RegionKind(this.nextPatchBuffer.layers[RegionKind.layerName]); }); - const marker = this.nextLayeredBuffer.markRange('hunk', this.hunkRange, {invalidate: 'never', exclusive: false}); + const marker = this.nextPatchBuffer.markRange('hunk', this.hunkRange, {invalidate: 'never', exclusive: false}); this.hunkBufferText = ''; this.hunkRowCount = 0; @@ -592,6 +592,6 @@ class BufferBuilder { } getLayeredBuffer() { - return this.nextLayeredBuffer; + return this.nextPatchBuffer; } } From d7755798801c725e4b984e47062ccd7bf6bd9984 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 28 Jan 2019 15:34:42 -0500 Subject: [PATCH 1925/4053] Create Regions with a Marker instead of a MarkerLayer --- lib/models/patch/patch.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 4bdb3b754d..5f4612d383 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -565,7 +565,12 @@ class BufferBuilder { this.nextPatchBuffer.buffer.append(this.hunkBufferText, {normalizeLineEndings: false}); const regions = this.hunkRegions.map(({RegionKind, range}) => { - return new RegionKind(this.nextPatchBuffer.layers[RegionKind.layerName]); + const regionMarker = this.nextPatchBuffer.markRange( + RegionKind.layerName, + range, + {invalidate: 'never', exclusive: false}, + ); + return new RegionKind(regionMarker); }); const marker = this.nextPatchBuffer.markRange('hunk', this.hunkRange, {invalidate: 'never', exclusive: false}); From 3f6672a06f72755f9c2f1015d58086bbae33870d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 28 Jan 2019 15:35:00 -0500 Subject: [PATCH 1926/4053] Query the correct MarkerLayers in tests --- test/models/patch/patch.test.js | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 8307d832b7..09648d39a2 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -268,13 +268,9 @@ describe('Patch', function() { patch.buildStagePatchForLines(originalBuffer, stageLayeredBuffer, new Set([1, 5, 15, 16, 17, 25])); const layerRanges = [ - ['hunk', stageLayeredBuffer.layers.hunk], - ['unchanged', stageLayeredBuffer.layers.unchanged], - ['addition', stageLayeredBuffer.layers.addition], - ['deletion', stageLayeredBuffer.layers.deletion], - ['noNewline', stageLayeredBuffer.layers.noNewline], - ].reduce((obj, [key, layer]) => { - obj[key] = layer.getMarkers().map(marker => marker.getRange().serialize()); + Hunk.layerName, Unchanged.layerName, Addition.layerName, Deletion.layerName, NoNewline.layerName, + ].reduce((obj, layerName) => { + obj[layerName] = stageLayeredBuffer.findMarkers(layerName, {}).map(marker => marker.getRange().serialize()); return obj; }, {}); @@ -301,7 +297,7 @@ describe('Patch', function() { [[1, 0], [1, 4]], [[11, 0], [12, 4]], ], - noNewline: [ + nonewline: [ [[17, 0], [17, 26]], ], }); @@ -463,13 +459,9 @@ describe('Patch', function() { const {patch, buffer: originalBuffer} = buildPatchFixture(); patch.buildUnstagePatchForLines(originalBuffer, unstageLayeredBuffer, new Set([1, 4, 5, 16, 17, 20, 25])); const layerRanges = [ - ['hunk', unstageLayeredBuffer.layers.hunk], - ['unchanged', unstageLayeredBuffer.layers.unchanged], - ['addition', unstageLayeredBuffer.layers.addition], - ['deletion', unstageLayeredBuffer.layers.deletion], - ['noNewline', unstageLayeredBuffer.layers.noNewline], - ].reduce((obj, [key, layer]) => { - obj[key] = layer.getMarkers().map(marker => marker.getRange().serialize()); + Hunk.layerName, Unchanged.layerName, Addition.layerName, Deletion.layerName, NoNewline.layerName, + ].reduce((obj, layerName) => { + obj[layerName] = unstageLayeredBuffer.findMarkers(layerName, {}).map(marker => marker.getRange().serialize()); return obj; }, {}); @@ -500,7 +492,7 @@ describe('Patch', function() { [[15, 0], [15, 4]], [[18, 0], [18, 4]], ], - noNewline: [ + nonewline: [ [[19, 0], [19, 26]], ], }); From 691c4757fa9768085552283f069570bdc15e1d1a Mon Sep 17 00:00:00 2001 From: annthurium Date: Mon, 28 Jan 2019 13:27:50 -0800 Subject: [PATCH 1927/4053] don't show collapse / expand buttons for `ChangedFileItem` --- lib/views/file-patch-header-view.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 93c4cdd9cf..8aa1e7ec4e 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -65,6 +65,9 @@ export default class FilePatchHeaderView extends React.Component { } renderCollapseButton() { + if (this.props.itemType === ChangedFileItem) { + return null; + } const icon = this.props.isCollapsed ? 'chevron-right' : 'chevron-down'; return (
    @@ -55,13 +59,13 @@ export default class GithubLoginView extends React.Component { renderTokenInput() { return (
    -

    - Step 1: Visit github.atom.io/login to generate - an authentication token. -

    -

    - Step 2: Enter the token below: -

    +
    +
      +
    1. Visit github.atom.io/login to generate + an authentication token.
    2. +
    3. Enter the token below:
    4. +
    + -
    - - -
    +
      +
    • + +
    • +
    • + +
    • +
    ); } diff --git a/styles/github-login-view.less b/styles/github-login-view.less index 974ff37e4f..1124fc4971 100644 --- a/styles/github-login-view.less +++ b/styles/github-login-view.less @@ -1,40 +1,43 @@ -.github-GithubLoginView-Container { - height: 100%; - display: flex; -} - .github-GithubLoginView { - height: 100%; + flex: 1; display: flex; + flex-direction: column; + font-size: 1.25em; .github-GithubLoginView-Subview { flex: 1; display: flex; flex-direction: column; - align-self: center; - align-items: center; - padding: 20px; + justify-content: center; + text-align: center; + padding: @component-padding * 3; - > button, > input { - margin: @component-padding; + > * { + margin: @component-padding 0; } - } - p { - text-align: center; - font-size: @font-size * 1.25; - margin: 0; - - &:first-child { - margin-bottom: 20px; + button { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; } - a { - color: @text-color-info; + ol { + text-align: left; + padding: @component-padding 0; + + a { + color: @text-color-info; + } } - } - input[type=text] { - width: 100%; + ul { + list-style: none; + padding-left: 0; + + li:not(:last-child) { + margin-bottom: @component-padding; + } + } } } diff --git a/styles/github-tab.less b/styles/github-tab.less index 507de3e3d7..91bd36b2f3 100644 --- a/styles/github-tab.less +++ b/styles/github-tab.less @@ -43,10 +43,13 @@ button { width: 100%; - margin-bottom: 10px; overflow: hidden; text-overflow: ellipsis; } + + :not(:last-child) { + margin-bottom: 10px; + } } &-LargeIcon:before { From 890f08ed273ce7e27ce1abdd0b6c4d30dac944a6 Mon Sep 17 00:00:00 2001 From: Robert Rossmann Date: Thu, 31 Jan 2019 14:15:53 +0100 Subject: [PATCH 1988/4053] Align all modals with button controls to the pane's bottom --- lib/views/git-tab-view.js | 4 ++-- styles/git-tab.less | 10 ++++++++-- styles/github-login-view.less | 2 +- styles/github-tab.less | 2 ++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/views/git-tab-view.js b/lib/views/git-tab-view.js index 514aa080a9..f055ea52d3 100644 --- a/lib/views/git-tab-view.js +++ b/lib/views/git-tab-view.js @@ -88,7 +88,7 @@ export default class GitTabView extends React.Component { if (this.props.repository.isTooLarge()) { return (
    -
    +

    Too many changes

    @@ -102,7 +102,7 @@ export default class GitTabView extends React.Component { !isValidWorkdir(this.props.repository.getWorkingDirectoryPath())) { return (
    -
    +

    Unsupported directory

    diff --git a/styles/git-tab.less b/styles/git-tab.less index 4ac04a96d5..db02accbe0 100644 --- a/styles/git-tab.less +++ b/styles/git-tab.less @@ -21,7 +21,9 @@ color: @text-color-subtle; } - &.no-repository { + &.no-repository, + &.too-many-changes, + &.unsupported-directory { display: flex; justify-content: center; font-size: 1.25em; @@ -32,12 +34,16 @@ margin: @component-padding 0; } - .btn.btn-primary { + button { overflow: hidden; text-overflow: ellipsis; } } + &.no-repository { + justify-content: flex-end; + } + &-LargeIcon:before { margin-right: 0; margin-bottom: 30px; diff --git a/styles/github-login-view.less b/styles/github-login-view.less index 1124fc4971..a1378dd7c5 100644 --- a/styles/github-login-view.less +++ b/styles/github-login-view.less @@ -8,7 +8,7 @@ flex: 1; display: flex; flex-direction: column; - justify-content: center; + justify-content: flex-end; text-align: center; padding: @component-padding * 3; diff --git a/styles/github-tab.less b/styles/github-tab.less index 91bd36b2f3..abee88c90e 100644 --- a/styles/github-tab.less +++ b/styles/github-tab.less @@ -36,6 +36,8 @@ } .github-RemoteSelector { + justify-content: flex-end; + ul { list-style: none; padding-left: 0; From 926d377d590c860f8cf13e89b792be746884c62e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 31 Jan 2019 09:55:55 -0500 Subject: [PATCH 1989/4053] Test case that expands trailing patches --- test/models/patch/multi-file-patch.test.js | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 363e431b01..79f65b7ab9 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -1024,6 +1024,33 @@ describe('MultiFilePatch', function() { assertInFilePatch(fp3, multiFilePatch.getBuffer()).hunks(hunk({index: 3, start: 12, last: true})); }); + it('collapses and expands the final two file patches', function() { + multiFilePatch.collapseFilePatch(fp3); + multiFilePatch.collapseFilePatch(fp2); + + assert.strictEqual(multiFilePatch.getBuffer().getText(), patchTextForIndexes([0, 1])); + assertInFilePatch(fp0, multiFilePatch.getBuffer()).hunks(hunk({index: 0, start: 0})); + assertInFilePatch(fp1, multiFilePatch.getBuffer()).hunks(hunk({index: 1, start: 4, last: true})); + assertInFilePatch(fp2, multiFilePatch.getBuffer()).hunks(); + assertInFilePatch(fp3, multiFilePatch.getBuffer()).hunks(); + + multiFilePatch.expandFilePatch(fp3); + + assert.strictEqual(multiFilePatch.getBuffer().getText(), patchTextForIndexes([0, 1, 3])); + assertInFilePatch(fp0, multiFilePatch.getBuffer()).hunks(hunk({index: 0, start: 0})); + assertInFilePatch(fp1, multiFilePatch.getBuffer()).hunks(hunk({index: 1, start: 4})); + assertInFilePatch(fp2, multiFilePatch.getBuffer()).hunks(); + assertInFilePatch(fp3, multiFilePatch.getBuffer()).hunks(hunk({index: 3, start: 8, last: true})); + + multiFilePatch.expandFilePatch(fp2); + + assert.strictEqual(multiFilePatch.getBuffer().getText(), patchTextForIndexes([0, 1, 2, 3])); + assertInFilePatch(fp0, multiFilePatch.getBuffer()).hunks(hunk({index: 0, start: 0})); + assertInFilePatch(fp1, multiFilePatch.getBuffer()).hunks(hunk({index: 1, start: 4})); + assertInFilePatch(fp2, multiFilePatch.getBuffer()).hunks(hunk({index: 2, start: 8})); + assertInFilePatch(fp3, multiFilePatch.getBuffer()).hunks(hunk({index: 3, start: 12, last: true})); + }); + describe('when all patches are collapsed', function() { beforeEach(function() { multiFilePatch.collapseFilePatch(fp0); From 569189cc12f90c270e2241dbddcfcb3ce48ab972 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 31 Jan 2019 13:46:17 -0500 Subject: [PATCH 1990/4053] {exclusive: true} changes the way edits move a marker's head/tail --- test/models/patch/patch-buffer.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index 1d553b11bc..ca4ea294f6 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -258,9 +258,9 @@ describe('PatchBuffer', function() { }); it('preserves markers that should be before or after the modification region', function() { - const before0 = patchBuffer.markRange('patch', [[1, 0], [4, 0]]); - const before1 = patchBuffer.markPosition('hunk', [4, 0]); - const after0 = patchBuffer.markPosition('patch', [4, 0]); + const before0 = patchBuffer.markRange('patch', [[1, 0], [4, 0]], {exclusive: true}); + const before1 = patchBuffer.markRange('hunk', [[4, 0], [4, 0]], {exclusive: true}); + const after0 = patchBuffer.markPosition('patch', [4, 0], {exclusive: true}); const inserter = patchBuffer.createInserterAt([4, 0]); inserter.keepBefore([before0, before1]); From 9777d298ef912ca2f76296fc54f6828d2fade41a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 31 Jan 2019 13:47:01 -0500 Subject: [PATCH 1991/4053] Fix up both head and tail of zero-length markers at Inserter boundaries --- lib/models/patch/patch-buffer.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 284c93cd55..87990e93b4 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -248,18 +248,34 @@ class Inserter { } for (const beforeMarker of this.markersBefore) { + const isEmpty = beforeMarker.getRange().isEmpty(); + if (!beforeMarker.isReversed()) { beforeMarker.setHeadPosition(this.startPoint); + if (isEmpty) { + beforeMarker.setTailPosition(this.startPoint); + } } else { beforeMarker.setTailPosition(this.startPoint); + if (isEmpty) { + beforeMarker.setHeadPosition(this.startPoint); + } } } for (const afterMarker of this.markersAfter) { + const isEmpty = afterMarker.getRange().isEmpty(); + if (!afterMarker.isReversed()) { afterMarker.setTailPosition(this.insertionPoint); + if (isEmpty) { + afterMarker.setHeadPosition(this.insertionPoint); + } } else { afterMarker.setHeadPosition(this.insertionPoint); + if (isEmpty) { + afterMarker.setTailPosition(this.insertionPoint); + } } } } From 06f5080dc705eb9f9bae1c37e69b9d5c67c4d90f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 31 Jan 2019 13:47:43 -0500 Subject: [PATCH 1992/4053] Position zero-length markers after an inserted separator newline --- lib/models/patch/file-patch.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index c0fb9b76da..b474fa1a00 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -173,11 +173,31 @@ export default class FilePatch { // (so it isn't also the end of the buffer). Insert a newline *after* the expanding patch when inserting anywhere // but the buffer's end. + if (atEnd && !atStart) { + const beforeNewline = []; + const afterNewline = after.slice(); + + for (const marker of before) { + if (marker.getRange().isEmpty()) { + afterNewline.push(marker); + } else { + beforeNewline.push(marker); + } + } + + patchBuffer + .createInserterAt(this.patch.getInsertionPoint()) + .keepBefore(beforeNewline) + .keepAfter(afterNewline) + .insert('\n') + .apply(); + } + + patchBuffer .createInserterAt(this.patch.getInsertionPoint()) .keepBefore(before) .keepAfter(after) - .insert(atEnd && !atStart ? '\n' : '') .insertPatchBuffer(subPatchBuffer, {callback: map => nextPatch.updateMarkers(map)}) .insert(!atEnd ? '\n' : '') .apply(); From aad548a09c9fd1ea7f4fbb258af815d6b4b57822 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 31 Jan 2019 14:50:37 -0500 Subject: [PATCH 1993/4053] Use a hideEmptiness prop to prevent TextEditors from displaying a blank line --- lib/atom/atom-text-editor.js | 23 +++++++++++++++++++- styles/atom-text-editor.less | 5 +++++ test/atom/atom-text-editor.test.js | 35 ++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index 8d180480ac..f3333e64db 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -22,6 +22,8 @@ const editorCreationProps = { ...editorUpdateProps, }; +const EMPTY_CLASS = 'github-AtomTextEditor-empty'; + export const TextEditorContext = React.createContext(); export default class AtomTextEditor extends React.Component { @@ -34,7 +36,8 @@ export default class AtomTextEditor extends React.Component { didAddSelection: PropTypes.func, didChangeSelectionRange: PropTypes.func, didDestroySelection: PropTypes.func, - observeSelections: PropTypes.func, + + hideEmptiness: PropTypes.bool, refModel: RefHolderPropType, @@ -46,6 +49,8 @@ export default class AtomTextEditor extends React.Component { didAddSelection: () => {}, didChangeSelectionRange: () => {}, didDestroySelection: () => {}, + + hideEmptiness: false, } constructor(props) { @@ -81,8 +86,13 @@ export default class AtomTextEditor extends React.Component { this.subs.add( editor.onDidChangeCursorPosition(this.props.didChangeCursorPosition), editor.observeSelections(this.observeSelections), + editor.onDidChange(this.observeEmptiness), ); + if (editor.isEmpty() && this.props.hideEmptiness) { + editor.getElement().classList.add(EMPTY_CLASS); + } + return null; }); } @@ -90,6 +100,7 @@ export default class AtomTextEditor extends React.Component { componentDidUpdate(prevProps) { const modelProps = extractProps(this.props, editorUpdateProps); this.getRefModel().map(editor => editor.update(modelProps)); + this.observeEmptiness(); } componentWillUnmount() { @@ -110,6 +121,16 @@ export default class AtomTextEditor extends React.Component { this.props.didAddSelection(selection); } + observeEmptiness = () => { + this.getRefModel().map(editor => { + if (editor.isEmpty() && this.props.hideEmptiness) { + this.refElement.map(element => element.classList.add(EMPTY_CLASS)); + } else { + this.refElement.map(element => element.classList.remove(EMPTY_CLASS)); + } + }); + } + contains(element) { return this.refElement.map(e => e.contains(element)).getOr(false); } diff --git a/styles/atom-text-editor.less b/styles/atom-text-editor.less index 3fe58fecf7..8bd338ad98 100644 --- a/styles/atom-text-editor.less +++ b/styles/atom-text-editor.less @@ -3,3 +3,8 @@ width: 100%; height: 100%; } + +// Conceal the automatically added blank line element within TextEditors that we want to actually be empty +.github-AtomTextEditor-empty .line { + display: none; +} diff --git a/test/atom/atom-text-editor.test.js b/test/atom/atom-text-editor.test.js index a2d8cb7e78..194cd5d9b0 100644 --- a/test/atom/atom-text-editor.test.js +++ b/test/atom/atom-text-editor.test.js @@ -221,6 +221,41 @@ describe('AtomTextEditor', function() { }); }); + describe('hideEmptiness', function() { + it('adds the github-AtomTextEditor-empty class when constructed with an empty TextBuffer', function() { + const emptyBuffer = new TextBuffer(); + + const wrapper = mount(); + const element = wrapper.instance().refElement.get(); + + assert.isTrue(element.classList.contains('github-AtomTextEditor-empty')); + }); + + it('removes the github-AtomTextEditor-empty class when constructed with a non-empty TextBuffer', function() { + const nonEmptyBuffer = new TextBuffer({text: 'nonempty\n'}); + + const wrapper = mount(); + const element = wrapper.instance().refElement.get(); + + assert.isFalse(element.classList.contains('github-AtomTextEditor-empty')); + }); + + it('adds and removes the github-AtomTextEditor-empty class as its TextBuffer becomes empty and non-empty', function() { + const buffer = new TextBuffer({text: 'nonempty\n...to start with\n'}); + + const wrapper = mount(); + const element = wrapper.instance().refElement.get(); + + assert.isFalse(element.classList.contains('github-AtomTextEditor-empty')); + + buffer.setText(''); + assert.isTrue(element.classList.contains('github-AtomTextEditor-empty')); + + buffer.setText('asdf\n'); + assert.isFalse(element.classList.contains('github-AtomTextEditor-empty')); + }); + }); + it('detects DOM node membership', function() { const wrapper = mount( , From 3d601cb5bff947aa6a4db98a1bbc5bba169a2056 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 31 Jan 2019 14:50:54 -0500 Subject: [PATCH 1994/4053] Set hideEmptiness on the TextEditor in the MFP view --- lib/views/multi-file-patch-view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 1e13c76487..9b9380bf1b 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -279,7 +279,8 @@ export default class MultiFilePatchView extends React.Component { didAddSelection={this.didAddSelection} didChangeSelectionRange={this.didChangeSelectionRange} didDestroySelection={this.didDestroySelection} - refModel={this.refEditor}> + refModel={this.refEditor} + hideEmptiness={true}> Date: Thu, 31 Jan 2019 16:17:54 -0500 Subject: [PATCH 1995/4053] Test all permutations of collapsing and expanding on an MFP PatchBuffer --- test/models/patch/multi-file-patch.test.js | 83 +++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 79f65b7ab9..db6502f846 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -1155,7 +1155,88 @@ describe('MultiFilePatch', function() { }); }); - it('is deterministic regardless of the order in which collapse and expand operations are performed'); + it('is deterministic regardless of the order in which collapse and expand operations are performed', function() { + this.timeout(60000); + + const patches = multiFilePatch.getFilePatches(); + + function expectVisibleAfter(ops, i) { + return ops.reduce((visible, op) => (op.index === i ? op.visibleAfter : visible), true); + } + + const operations = []; + for (let i = 0; i < patches.length; i++) { + operations.push({ + index: i, + visibleAfter: false, + name: `collapse fp${i}`, + canHappenAfter: ops => expectVisibleAfter(ops, i), + action: () => multiFilePatch.collapseFilePatch(patches[i]), + }); + + operations.push({ + index: i, + visibleAfter: true, + name: `expand fp${i}`, + canHappenAfter: ops => !expectVisibleAfter(ops, i), + action: () => multiFilePatch.expandFilePatch(patches[i]), + }); + } + + const operationSequences = []; + + function generateSequencesAfter(prefix) { + const possible = operations + .filter(op => !prefix.includes(op)) + .filter(op => op.canHappenAfter(prefix)); + if (possible.length === 0) { + operationSequences.push(prefix); + } else { + for (const next of possible) { + generateSequencesAfter([...prefix, next]); + } + } + } + generateSequencesAfter([]); + + for (const sequence of operationSequences) { + // Uncomment to see which sequence is causing problems + // console.log(sequence.map(op => op.name).join(' -> ')); + + // Reset to the all-expanded state + multiFilePatch.expandFilePatch(fp0); + multiFilePatch.expandFilePatch(fp1); + multiFilePatch.expandFilePatch(fp2); + multiFilePatch.expandFilePatch(fp3); + + // Perform the operations + for (const operation of sequence) { + operation.action(); + } + + // Ensure the TextBuffer and Markers are in the expected states + const visibleIndexes = []; + for (let i = 0; i < patches.length; i++) { + if (patches[i].getRenderStatus().isVisible()) { + visibleIndexes.push(i); + } + } + const lastVisibleIndex = Math.max(...visibleIndexes); + + assert.strictEqual(multiFilePatch.getBuffer().getText(), patchTextForIndexes(visibleIndexes)); + + let start = 0; + for (let i = 0; i < patches.length; i++) { + const patchAssertions = assertInFilePatch(patches[i], multiFilePatch.getBuffer()); + if (patches[i].getRenderStatus().isVisible()) { + patchAssertions.hunks(hunk({index: i, start, last: lastVisibleIndex === i})); + start += 4; + } else { + patchAssertions.hunks(); + } + } + } + }); }); }); }); From 723fd446e12ceec5b473c8a2ce653b15cb3fc383 Mon Sep 17 00:00:00 2001 From: Robert Rossmann Date: Fri, 1 Feb 2019 10:20:25 +0100 Subject: [PATCH 1996/4053] Be more specific about margins on buttons on Remote Selector view --- styles/github-tab.less | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/styles/github-tab.less b/styles/github-tab.less index abee88c90e..4b81b2977a 100644 --- a/styles/github-tab.less +++ b/styles/github-tab.less @@ -41,6 +41,10 @@ ul { list-style: none; padding-left: 0; + + li:not(:last-child) { + margin-bottom: @component-padding; + } } button { @@ -48,10 +52,6 @@ overflow: hidden; text-overflow: ellipsis; } - - :not(:last-child) { - margin-bottom: 10px; - } } &-LargeIcon:before { From de16f7d69707f926bfb7b319132cc9f95ca4600c Mon Sep 17 00:00:00 2001 From: Robert Rossmann Date: Fri, 1 Feb 2019 10:28:33 +0100 Subject: [PATCH 1997/4053] Add headings to all informational & dialog views --- lib/views/git-tab-view.js | 5 +++-- lib/views/github-login-view.js | 2 ++ lib/views/github-tab-view.js | 1 + lib/views/remote-selector-view.js | 1 + styles/git-tab.less | 4 ++++ styles/github-login-view.less | 4 ++++ styles/github-tab.less | 4 ++++ 7 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/views/git-tab-view.js b/lib/views/git-tab-view.js index f055ea52d3..20e54b9164 100644 --- a/lib/views/git-tab-view.js +++ b/lib/views/git-tab-view.js @@ -90,7 +90,7 @@ export default class GitTabView extends React.Component {
    -

    Too many changes

    +

    Too many changes

    The repository at {this.props.workingDirectoryPath} has too many changed files to display in Atom. Ensure that you have set up an appropriate .gitignore file. @@ -104,7 +104,7 @@ export default class GitTabView extends React.Component {
    -

    Unsupported directory

    +

    Unsupported directory

    Atom does not support managing Git repositories in your home or root directories.
    @@ -125,6 +125,7 @@ export default class GitTabView extends React.Component {
    +

    Create Repository

    {message}

    + ); @@ -539,7 +545,7 @@ export default class MultiFilePatchView extends React.Component { ); } - renderHunkHeaders(filePatch) { + renderHunkHeaders(filePatch, orderOffset) { const toggleVerb = this.props.stagingStatus === 'unstaged' ? 'Stage' : 'Unstage'; const selectedHunks = new Set( Array.from(this.props.selectedRows, row => this.props.multiFilePatch.getHunkAt(row)), @@ -573,7 +579,7 @@ export default class MultiFilePatchView extends React.Component { return ( - + Date: Thu, 21 Feb 2019 09:15:12 -0500 Subject: [PATCH 2060/4053] hasHunks is never legitimately used... ? --- lib/views/file-patch-header-view.js | 3 +-- lib/views/multi-file-patch-view.js | 1 - test/views/file-patch-header-view.test.js | 8 -------- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 500718993c..a41b0fef7b 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -18,7 +18,6 @@ export default class FilePatchHeaderView extends React.Component { newPath: PropTypes.string, stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), isPartiallyStaged: PropTypes.bool, - hasHunks: PropTypes.bool.isRequired, hasUndoHistory: PropTypes.bool, hasMultipleFileSelections: PropTypes.bool.isRequired, @@ -146,7 +145,7 @@ export default class FilePatchHeaderView extends React.Component { } renderMirrorPatchButton() { - if (!this.props.isPartiallyStaged && this.props.hasHunks) { + if (!this.props.isPartiallyStaged) { return null; } diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 88ffb3d71d..d5c4639e69 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -378,7 +378,6 @@ export default class MultiFilePatchView extends React.Component { newPath={filePatch.getStatus() === 'renamed' ? filePatch.getNewPath() : null} stagingStatus={this.props.stagingStatus} isPartiallyStaged={this.props.isPartiallyStaged} - hasHunks={filePatch.getHunks().length > 0} hasUndoHistory={this.props.hasUndoHistory} hasMultipleFileSelections={this.props.hasMultipleFileSelections} diff --git a/test/views/file-patch-header-view.test.js b/test/views/file-patch-header-view.test.js index 9cff2dc22f..4daeec1027 100644 --- a/test/views/file-patch-header-view.test.js +++ b/test/views/file-patch-header-view.test.js @@ -193,14 +193,6 @@ describe('FilePatchHeaderView', function() { it('includes a toggle to unstaged button when staged', createStagedPatchToggleTest(props)); }); - describe('when the patch contains no hunks', function() { - const props = {hasHunks: false}; - - it('includes a toggle to staged button when unstaged', createUnstagedPatchToggleTest(props)); - - it('includes a toggle to unstaged button when staged', createStagedPatchToggleTest(props)); - }); - describe('the jump-to-file button', function() { it('calls the jump to file file action prop', function() { const openFile = sinon.stub(); From 3fb4c1e68f9d4c7def5b205b726041b7a3cf5941 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 09:31:02 -0500 Subject: [PATCH 2061/4053] Rename isPatchTooLargeOrCollapsed to isPatchVisible --- lib/controllers/pr-reviews-controller.js | 2 +- lib/models/patch/multi-file-patch.js | 7 +++-- lib/views/multi-file-patch-view.js | 6 ++--- lib/views/pr-review-comments-view.js | 5 ++-- .../controllers/pr-reviews-controller.test.js | 2 +- test/models/patch/multi-file-patch.test.js | 26 +++++++++---------- test/views/pr-comments-view.test.js | 4 +-- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index f2a836fc32..fdaf09c434 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -19,7 +19,7 @@ export default class PullRequestReviewsController extends React.Component { ), }), getBufferRowForDiffPosition: PropTypes.func.isRequired, - isPatchTooLargeOrCollapsed: PropTypes.func.isRequired, + isPatchVisible: PropTypes.func.isRequired, } constructor(props) { diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index d0762a16cc..d91e029eb8 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -354,13 +354,12 @@ export default class MultiFilePatch { } } - isPatchTooLargeOrCollapsed = filePatchPath => { + isPatchVisible = filePatchPath => { const patch = this.filePatchesByPath.get(filePatchPath); if (!patch) { - return null; + return false; } - const renderStatus = patch.getRenderStatus(); - return renderStatus === TOO_LARGE || renderStatus === COLLAPSED; + return patch.getRenderStatus().isVisible(); } getBufferRowForDiffPosition = (fileName, diffRow) => { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index d5c4639e69..2de9e6d03f 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -355,7 +355,7 @@ export default class MultiFilePatchView extends React.Component { ); } else { @@ -392,8 +392,8 @@ export default class MultiFilePatchView extends React.Component { triggerCollapse={() => this.props.multiFilePatch.collapseFilePatch(filePatch)} triggerExpand={() => this.props.multiFilePatch.expandFilePatch(filePatch)} /> - {this.renderSymlinkChangeMeta(filePatch)} - {this.renderExecutableModeChangeMeta(filePatch)} + {!isCollapsed && this.renderSymlinkChangeMeta(filePatch)} + {!isCollapsed && this.renderExecutableModeChangeMeta(filePatch)} diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index f31a9fe36b..dfdefff8df 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -24,7 +24,7 @@ export default class PullRequestCommentsView extends React.Component { })), getBufferRowForDiffPosition: PropTypes.func.isRequired, switchToIssueish: PropTypes.func.isRequired, - isPatchTooLargeOrCollapsed: PropTypes.func.isRequired, + isPatchVisible: PropTypes.func.isRequired, } render() { @@ -35,9 +35,10 @@ export default class PullRequestCommentsView extends React.Component { } // if file patch is collapsed or too large, do not render the comments - if (this.props.isPatchTooLargeOrCollapsed(rootComment.path)) { + if (!this.props.isPatchVisible(rootComment.path)) { return null; } + const nativePath = toNativePathSep(rootComment.path); const row = this.props.getBufferRowForDiffPosition(nativePath, rootComment.position); const point = new Point(row, 0); diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index fcd44eacc8..60e2dbf2be 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -43,7 +43,7 @@ describe('PullRequestReviewsController', function() { switchToIssueish: () => {}, getBufferRowForDiffPosition: () => {}, - isPatchTooLargeOrCollapsed: () => {}, + isPatchVisible: () => true, pullRequest: {reviews}, ...overrideProps, }; diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index db6502f846..8532bea250 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -703,8 +703,8 @@ describe('MultiFilePatch', function() { }); }); - describe('isPatchTooLargeOrCollapsed', function() { - it('returns true if patch exceeds large diff threshold', function() { + describe('isPatchVisible', function() { + it('returns false if patch exceeds large diff threshold', function() { const multiFilePatch = multiFilePatchBuilder() .addFilePatch(fp => { fp.setOldFile(f => f.path('file-0')); @@ -712,20 +712,20 @@ describe('MultiFilePatch', function() { }) .build() .multiFilePatch; - assert.isTrue(multiFilePatch.isPatchTooLargeOrCollapsed('file-0')); + assert.isFalse(multiFilePatch.isPatchVisible('file-0')); }); - it('returns true if patch is collapsed', function() { + it('returns false if patch is collapsed', function() { const multiFilePatch = multiFilePatchBuilder() .addFilePatch(fp => { fp.setOldFile(f => f.path('file-0')); fp.renderStatus(COLLAPSED); }).build().multiFilePatch; - assert.isTrue(multiFilePatch.isPatchTooLargeOrCollapsed('file-0')); + assert.isFalse(multiFilePatch.isPatchVisible('file-0')); }); - it('returns false if patch is expanded', function() { + it('returns true if patch is expanded', function() { const multiFilePatch = multiFilePatchBuilder() .addFilePatch(fp => { fp.setOldFile(f => f.path('file-0')); @@ -734,8 +734,9 @@ describe('MultiFilePatch', function() { .build() .multiFilePatch; - assert.isFalse(multiFilePatch.isPatchTooLargeOrCollapsed('file-0')); + assert.isTrue(multiFilePatch.isPatchVisible('file-0')); }); + it('multiFilePatch with multiple hunks returns correct values', function() { const multiFilePatch = multiFilePatchBuilder() .addFilePatch(fp => { @@ -753,12 +754,12 @@ describe('MultiFilePatch', function() { .build() .multiFilePatch; - assert.isFalse(multiFilePatch.isPatchTooLargeOrCollapsed('expanded-file')); - assert.isTrue(multiFilePatch.isPatchTooLargeOrCollapsed('too-large-file')); - assert.isTrue(multiFilePatch.isPatchTooLargeOrCollapsed('collapsed-file')); + assert.isTrue(multiFilePatch.isPatchVisible('expanded-file')); + assert.isFalse(multiFilePatch.isPatchVisible('too-large-file')); + assert.isFalse(multiFilePatch.isPatchVisible('collapsed-file')); }); - it('returns null if patch does not exist', function() { + it('returns false if patch does not exist', function() { const multiFilePatch = multiFilePatchBuilder() .addFilePatch(fp => { fp.setOldFile(f => f.path('file-0')); @@ -766,11 +767,10 @@ describe('MultiFilePatch', function() { }) .build() .multiFilePatch; - assert.isNull(multiFilePatch.isPatchTooLargeOrCollapsed('invalid-file-path')); + assert.isFalse(multiFilePatch.isPatchVisible('invalid-file-path')); }); }); - describe('diff position translation', function() { it('offsets rows in the first hunk by the first hunk header', function() { const {multiFilePatch} = multiFilePatchBuilder() diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-comments-view.test.js index 53b7ef0eba..ff563441fc 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-comments-view.test.js @@ -14,7 +14,7 @@ describe('PullRequestCommentsView', function() { }; return shallow( { return true; }}); + const wrapper = buildApp(multiFilePatch, pr, {isPatchVisible: () => { return false; }}); const comments = wrapper.find('PullRequestCommentView'); assert.lengthOf(comments, 0); }); From fad1e9158dc1a1b9b4251f4c979a241f11d08676 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 10:05:50 -0500 Subject: [PATCH 2062/4053] Force PullRequestsReviewsContainer to re-render --- lib/views/multi-file-patch-view.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 2de9e6d03f..4edd6ca52f 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -351,13 +351,17 @@ export default class MultiFilePatchView extends React.Component { renderPullRequestReviews() { if (this.props.itemType === IssueishDetailItem) { + // "forceRerender" ensures that the PullRequestCommentsView re-renders each time that the MultiFilePatchView does. + // It doesn't re-query for reviews, but it does re-check patch visibility. return ( ); + forceRerender={{}} + /> + ); } else { return null; } From ab347f93f74e4a49165e799f56ad949cc070533d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 10:25:50 -0500 Subject: [PATCH 2063/4053] ChangedFileContainer tests: :white_check_mark: + :100: --- lib/containers/changed-file-container.js | 1 + .../containers/changed-file-container.test.js | 29 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/containers/changed-file-container.js b/lib/containers/changed-file-container.js index b7955a33e1..e97e8ffd2e 100644 --- a/lib/containers/changed-file-container.js +++ b/lib/containers/changed-file-container.js @@ -79,6 +79,7 @@ export default class ChangedFileContainer extends React.Component { const currentMultiFilePatch = data && data.multiFilePatch; if (currentMultiFilePatch !== this.lastMultiFilePatch) { this.sub.dispose(); + /* istanbul ignore else */ if (currentMultiFilePatch) { // Keep this component's renderStatusOverride synchronized with the FilePatch we're rendering this.sub = new CompositeDisposable( diff --git a/test/containers/changed-file-container.test.js b/test/containers/changed-file-container.test.js index e06d2fb520..a9612146a0 100644 --- a/test/containers/changed-file-container.test.js +++ b/test/containers/changed-file-container.test.js @@ -65,7 +65,7 @@ describe('ChangedFileContainer', function() { await assert.async.isTrue(wrapper.update().find('ChangedFileController').exists()); }); - it('adopts the buffer from the previous FilePatch when a new one arrives', async function() { + it('uses a consistent TextBuffer', async function() { const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); await assert.async.isTrue(wrapper.update().find('ChangedFileController').exists()); @@ -81,21 +81,6 @@ describe('ChangedFileContainer', function() { assert.strictEqual(nextBuffer, prevBuffer); }); - it('does not adopt a buffer from an unchanged patch', async function() { - const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged'})); - await assert.async.isTrue(wrapper.update().find('ChangedFileController').exists()); - - const prevPatch = wrapper.find('ChangedFileController').prop('multiFilePatch'); - sinon.spy(prevPatch, 'adoptBufferFrom'); - - wrapper.setProps({}); - - assert.isFalse(prevPatch.adoptBufferFrom.called); - - const nextPatch = wrapper.find('ChangedFileController').prop('multiFilePatch'); - assert.strictEqual(nextPatch, prevPatch); - }); - it('passes unrecognized props to the FilePatchView', async function() { const extra = Symbol('extra'); const wrapper = mount(buildApp({relPath: 'a.txt', stagingStatus: 'unstaged', extra})); @@ -119,4 +104,16 @@ describe('ChangedFileContainer', function() { assert.notStrictEqual(after, before); assert.strictEqual(after.getFilePatches()[0].getRenderStatus(), EXPANDED); }); + + it('disposes its FilePatch subscription on unmount', async function() { + const wrapper = mount(buildApp({})); + await assert.async.isTrue(wrapper.update().exists('ChangedFileController')); + + const patch = wrapper.find('ChangedFileController').prop('multiFilePatch'); + const [fp] = patch.getFilePatches(); + assert.strictEqual(fp.emitter.listenerCountForEventName('change-render-status'), 1); + + wrapper.unmount(); + assert.strictEqual(fp.emitter.listenerCountForEventName('change-render-status'), 0); + }); }); From fb07191163049b4e3bed017a88ac9126ddbbe205 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 10:33:35 -0500 Subject: [PATCH 2064/4053] FilePatch integration tests :white_check_mark: --- test/integration/file-patch.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 9b8c5fc59d..63d3746cfc 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -513,8 +513,8 @@ describe('integration: file patches', function() { await patchContent( 'unstaged', 'sample.js', - [repoPath('target.txt'), 'selected'], - [' No newline at end of file'], + [repoPath('target.txt'), 'added', 'selected'], + [' No newline at end of file', 'nonewline', 'selected'], ); assert.isTrue(getPatchItem('unstaged', 'sample.js').find('.github-FilePatchView-metaTitle').exists()); From c87a28026496362daf02c36fcee2ca924980cbdc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 10:44:01 -0500 Subject: [PATCH 2065/4053] builder tests :white_check_mark: + :100: --- test/models/patch/builder.test.js | 85 ++++++++++++++----------------- 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 0106c2d451..9ecf093c78 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -2,7 +2,6 @@ import dedent from 'dedent-js'; import {buildFilePatch, buildMultiFilePatch} from '../../../lib/models/patch'; import {TOO_LARGE, EXPANDED} from '../../../lib/models/patch/patch'; -import PatchBuffer from '../../../lib/models/patch/patch-buffer'; import {multiFilePatchBuilder} from '../../builder/patch'; import {assertInPatch, assertInFilePatch} from '../../helpers'; @@ -926,7 +925,7 @@ describe('buildFilePatch', function() { ); }); - it('re-parse a HiddenPatch as a Patch', function() { + it('re-parses a HiddenPatch as a Patch', function() { const mfp = buildMultiFilePatch([ { oldPath: 'first', oldMode: '100644', newPath: 'first', newMode: '100644', status: 'modified', @@ -964,6 +963,44 @@ describe('buildFilePatch', function() { ); }); + it('re-parses a HiddenPatch from a paired symlink diff as a Patch', function() { + const {raw} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.status('deleted'); + fp.setOldFile(f => f.path('big').symlinkTo('/somewhere')); + fp.nullNewFile(); + }) + .addFilePatch(fp => { + fp.status('added'); + fp.nullOldFile(); + fp.setNewFile(f => f.path('big')); + fp.addHunk(h => h.oldRow(1).added('0', '1', '2', '3', '4', '5')); + }) + .build(); + const mfp = buildMultiFilePatch(raw, {largeDiffThreshold: 3}); + + assert.lengthOf(mfp.getFilePatches(), 1); + const [fp] = mfp.getFilePatches(); + + assert.strictEqual(fp.getRenderStatus(), TOO_LARGE); + assert.strictEqual(fp.getOldPath(), 'big'); + assert.strictEqual(fp.getNewPath(), 'big'); + assert.deepEqual(fp.getStartRange().serialize(), [[0, 0], [0, 0]]); + assertInFilePatch(fp).hunks(); + + mfp.expandFilePatch(fp); + + assert.strictEqual(fp.getRenderStatus(), EXPANDED); + assert.deepEqual(fp.getMarker().getRange().serialize(), [[0, 0], [5, 1]]); + assertInFilePatch(fp, mfp.getBuffer()).hunks( + { + startRow: 0, endRow: 5, header: '@@ -1,0 +1,6 @@', regions: [ + {kind: 'addition', string: '+0\n+1\n+2\n+3\n+4\n+5', range: [[0, 0], [5, 1]]}, + ], + }, + ); + }); + it('does not interfere with markers from surrounding visible patches when expanded', function() { const mfp = buildMultiFilePatch([ { @@ -1141,50 +1178,6 @@ describe('buildFilePatch', function() { }); }); - it('uses an existing PatchBuffer if one is provided', function() { - const existing = new PatchBuffer(); - existing - .createInserterAtEnd() - .insert('aaa\n') - .insertMarked('bbb\n', 'patch', {}) - .insertMarked('ccc\n', 'addition', {}) - .insert('ddd\n') - .apply(); - - const {raw} = multiFilePatchBuilder() - .addFilePatch(fp => { - fp.setOldFile(f => f.path('file.txt')); - fp.addHunk(h => h.oldRow(10).unchanged('000').added('111', '222').unchanged('333')); - }) - .build(); - - buildFilePatch(raw, {patchBuffer: existing}); - - assert.strictEqual(existing.getBuffer().getText(), dedent` - 000 - 111 - 222 - 333 - `); - - assert.deepEqual( - existing.findMarkers('patch', {}).map(m => m.getRange().serialize()), - [[[0, 0], [3, 3]]], - ); - assert.deepEqual( - existing.findMarkers('hunk', {}).map(m => m.getRange().serialize()), - [[[0, 0], [3, 3]]], - ); - assert.deepEqual( - existing.findMarkers('unchanged', {}).map(m => m.getRange().serialize()), - [[[0, 0], [0, 3]], [[3, 0], [3, 3]]], - ); - assert.deepEqual( - existing.findMarkers('addition', {}).map(m => m.getRange().serialize()), - [[[1, 0], [2, 3]]], - ); - }); - it('throws an error with an unexpected number of diffs', function() { assert.throws(() => buildFilePatch([1, 2, 3]), /Unexpected number of diffs: 3/); }); From 4b0b676c0b9afb757121667792bebf7b19e766fe Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 10:48:42 -0500 Subject: [PATCH 2066/4053] reMarkOn() is no more --- lib/models/patch/hunk.js | 8 -------- lib/models/patch/patch.js | 12 ------------ lib/models/patch/region.js | 8 -------- test/models/patch/hunk.test.js | 15 --------------- test/models/patch/region.test.js | 11 ----------- 5 files changed, 54 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 8bff265dd4..65c7ebe4bc 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -137,14 +137,6 @@ export default class Hunk { .reduce((count, change) => count + change.bufferRowCount(), 0); } - reMarkOn(markable) { - this.marker = markable.markRange( - this.constructor.layerName, - this.getRange(), - {invalidate: 'never', exclusive: false}, - ); - } - updateMarkers(map) { this.marker = map.get(this.marker) || this.marker; for (const region of this.regions) { diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index 4124726bef..ae07554b32 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -69,14 +69,6 @@ export default class Patch { return this.marker.getRange().intersectsRow(row); } - reMarkOn(patchBuffer) { - this.marker = patchBuffer.markRange( - this.constructor.layerName, - this.getRange(), - {invalidate: 'never', exclusive: false}, - ); - } - destroyMarkers() { this.marker.destroy(); for (const hunk of this.hunks) { @@ -436,10 +428,6 @@ class NullPatch { return false; } - reMarkOn(markable) { - this.marker = markable.markRange(Patch.layerName, this.getRange(), {invalidate: 'never', exclusive: false}); - } - getMaxLineNumberWidth() { return 0; } diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index c4835d4e83..85e05dbeef 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -107,14 +107,6 @@ class Region { return callback(); } - reMarkOn(markable) { - this.marker = markable.markRange( - this.constructor.layerName, - this.getRange(), - {invalidate: 'never', exclusive: false}, - ); - } - updateMarkers(map) { this.marker = map.get(this.marker) || this.marker; } diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index 4080f261fc..ae6ba01fab 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -193,21 +193,6 @@ describe('Hunk', function() { assert.strictEqual(h1.getMaxLineNumberWidth(), 5); }); - it('creates a new marker on a different markable target', function() { - const h = new Hunk({ - ...attrs, - marker: buffer.markRange([[1, 0], [4, 4]]), - }); - - assert.strictEqual(h.getMarker().layer, buffer.getDefaultMarkerLayer()); - - const nextBuffer = new TextBuffer({text: buffer.getText()}); - h.reMarkOn(nextBuffer); - - assert.deepEqual(h.getRange().serialize(), [[1, 0], [4, 4]]); - assert.strictEqual(h.getMarker().layer, nextBuffer.getDefaultMarkerLayer()); - }); - describe('toStringIn()', function() { it('prints its header', function() { const h = new Hunk({ diff --git a/test/models/patch/region.test.js b/test/models/patch/region.test.js index 9a879c0776..105a8746b5 100644 --- a/test/models/patch/region.test.js +++ b/test/models/patch/region.test.js @@ -29,17 +29,6 @@ describe('Regions', function() { assert.isTrue(addition.includesBufferRow(2)); }); - it('can be re-marked on a new markable target', function() { - assert.strictEqual(addition.getMarker().layer, buffer.getDefaultMarkerLayer()); - - const nextBuffer = new TextBuffer({text: buffer.getText()}); - const nextLayer = nextBuffer.addMarkerLayer(); - addition.reMarkOn(nextLayer); - - assert.strictEqual(addition.getMarker().layer, nextLayer); - assert.deepEqual(addition.getRange().serialize(), [[1, 0], [3, 4]]); - }); - it('can be recognized by the isAddition predicate', function() { assert.isTrue(addition.isAddition()); assert.isFalse(addition.isDeletion()); From 662a1ddb259aee79cbd34d670d3fc6823a79f7d8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 11:01:05 -0500 Subject: [PATCH 2067/4053] Hunk tests :white_check_mark: + :100: --- lib/models/patch/hunk.js | 1 + test/models/patch/hunk.test.js | 57 ++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index 65c7ebe4bc..c4da9a48a8 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -158,6 +158,7 @@ export default class Hunk { /* * Construct a String containing internal diagnostic information. */ + /* istanbul ignore next */ inspect(opts = {}) { const options = { indent: 0, diff --git a/test/models/patch/hunk.test.js b/test/models/patch/hunk.test.js index ae6ba01fab..b0915e390f 100644 --- a/test/models/patch/hunk.test.js +++ b/test/models/patch/hunk.test.js @@ -193,6 +193,63 @@ describe('Hunk', function() { assert.strictEqual(h1.getMaxLineNumberWidth(), 5); }); + it('updates markers from a marker map', function() { + const oMarker = buffer.markRange([[0, 0], [10, 4]]); + + const h = new Hunk({ + oldStartRow: 0, + newStartRow: 1, + oldRowCount: 2, + newRowCount: 3, + sectionHeading: 'sectionHeading', + marker: oMarker, + regions: [ + new Addition(buffer.markRange([[1, 0], [2, 4]])), + new Deletion(buffer.markRange([[3, 0], [4, 4]])), + new Deletion(buffer.markRange([[5, 0], [6, 4]])), + new Unchanged(buffer.markRange([[7, 0], [10, 4]])), + ], + }); + + h.updateMarkers(new Map()); + assert.strictEqual(h.getMarker(), oMarker); + + const regionUpdateMaps = h.getRegions().map(r => sinon.spy(r, 'updateMarkers')); + + const layer = buffer.addMarkerLayer(); + const nMarker = layer.markRange([[0, 0], [10, 4]]); + const map = new Map([[oMarker, nMarker]]); + + h.updateMarkers(map); + + assert.strictEqual(h.getMarker(), nMarker); + assert.isTrue(regionUpdateMaps.every(spy => spy.calledWith(map))); + }); + + it('destroys all of its markers', function() { + const h = new Hunk({ + oldStartRow: 0, + newStartRow: 1, + oldRowCount: 2, + newRowCount: 3, + sectionHeading: 'sectionHeading', + marker: buffer.markRange([[0, 0], [10, 4]]), + regions: [ + new Addition(buffer.markRange([[1, 0], [2, 4]])), + new Deletion(buffer.markRange([[3, 0], [4, 4]])), + new Deletion(buffer.markRange([[5, 0], [6, 4]])), + new Unchanged(buffer.markRange([[7, 0], [10, 4]])), + ], + }); + + const allMarkers = [h.getMarker(), ...h.getRegions().map(r => r.getMarker())]; + assert.isFalse(allMarkers.some(m => m.isDestroyed())); + + h.destroyMarkers(); + + assert.isTrue(allMarkers.every(m => m.isDestroyed())); + }); + describe('toStringIn()', function() { it('prints its header', function() { const h = new Hunk({ From f85ec22011eea178fc008f2417861f3875210027 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 11:22:24 -0500 Subject: [PATCH 2068/4053] Repository tests :white_check_mark: --- lib/models/patch/multi-file-patch.js | 2 +- test/models/repository.test.js | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index d91e029eb8..bb66638fd8 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -372,7 +372,7 @@ export default class MultiFilePatch { * Construct an apply-able patch String. */ toString() { - return this.filePatches.map(fp => fp.toStringIn(this.getBuffer())).join(''); + return this.filePatches.map(fp => fp.toStringIn(this.getBuffer())).join('') + '\n'; } /* diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 9570d61065..a0e68c9eb1 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -393,8 +393,7 @@ describe('Repository', function() { @@ -0,0 +1,3 @@ +qux +foo - +bar - + +bar\n `); // Unstage symlink change, leaving deleted file staged @@ -415,8 +414,7 @@ describe('Repository', function() { -foo -bar -baz - - - + -\n `); }); From c4a7294609f46aef39d5a5a381042177fd2365d8 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 21 Feb 2019 19:32:39 +0000 Subject: [PATCH 2069/4053] fix(package): update react to version 16.8.3 Closes #1971 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac2a5101f8..50e367e98d 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "moment": "2.23.0", "node-emoji": "^1.8.1", "prop-types": "15.7.2", - "react": "16.8.2", + "react": "16.8.3", "react-dom": "16.8.2", "react-relay": "1.7.0", "react-select": "1.2.1", From fb1b1f9911bab15b7b554b4c795e0c9f6267e879 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 21 Feb 2019 19:32:43 +0000 Subject: [PATCH 2070/4053] fix(package): update react-dom to version 16.8.3 Closes #1971 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 50e367e98d..dba7fd1726 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "node-emoji": "^1.8.1", "prop-types": "15.7.2", "react": "16.8.3", - "react-dom": "16.8.2", + "react-dom": "16.8.3", "react-relay": "1.7.0", "react-select": "1.2.1", "react-tabs": "^3.0.0", From 201955a8964842d1474c9cc1ba5fdcca293fd6f5 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 21 Feb 2019 19:32:47 +0000 Subject: [PATCH 2071/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 73d92d8248..b50b213f9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7106,20 +7106,20 @@ } }, "react": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/react/-/react-16.8.2.tgz", - "integrity": "sha512-aB2ctx9uQ9vo09HVknqv3DGRpI7OIGJhCx3Bt0QqoRluEjHSaObJl+nG12GDdYH6sTgE7YiPJ6ZUyMx9kICdXw==", + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.3.tgz", + "integrity": "sha512-3UoSIsEq8yTJuSu0luO1QQWYbgGEILm+eJl2QN/VLDi7hL+EN18M3q3oVZwmVzzBJ3DkM7RMdRwBmZZ+b4IzSA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.2" + "scheduler": "^0.13.3" }, "dependencies": { "scheduler": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.2.tgz", - "integrity": "sha512-qK5P8tHS7vdEMCW5IPyt8v9MJOHqTrOUgPXib7tqm9vh834ibBX5BNhwkplX/0iOzHW5sXyluehYfS9yrkz9+w==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz", + "integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -7128,20 +7128,20 @@ } }, "react-dom": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.2.tgz", - "integrity": "sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg==", + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.3.tgz", + "integrity": "sha512-ttMem9yJL4/lpItZAQ2NTFAbV7frotHk5DZEHXUOws2rMmrsvh1Na7ThGT0dTzUIl6pqTOi5tYREfL8AEna3lA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.2" + "scheduler": "^0.13.3" }, "dependencies": { "scheduler": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.2.tgz", - "integrity": "sha512-qK5P8tHS7vdEMCW5IPyt8v9MJOHqTrOUgPXib7tqm9vh834ibBX5BNhwkplX/0iOzHW5sXyluehYfS9yrkz9+w==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz", + "integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" From 6f42f45c3c64abd74da8a2559e82a61c95605ddf Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 15:13:36 -0500 Subject: [PATCH 2072/4053] Remove unused imports --- lib/models/patch/multi-file-patch.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index bb66638fd8..5b28a5f97f 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -2,7 +2,6 @@ import {Range} from 'atom'; import {RBTree} from 'bintrees'; import PatchBuffer from './patch-buffer'; -import {TOO_LARGE, COLLAPSED} from './patch'; export default class MultiFilePatch { static createNull() { From 9e9eed80bb515ba7c149aeb12e7b9680721d18bc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 15:13:57 -0500 Subject: [PATCH 2073/4053] Consistently use MultiFilePatch.createNull() --- lib/models/repository-states/state.js | 4 ++-- test/controllers/commit-preview-controller.test.js | 2 +- test/controllers/multi-file-patch-controller.test.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 747b7b3ac5..d76548c6fb 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -281,11 +281,11 @@ export default class State { } getFilePatchForPath(filePath, options = {}) { - return Promise.resolve(new MultiFilePatch({})); + return Promise.resolve(MultiFilePatch.createNull()); } getStagedChangesPatch() { - return Promise.resolve(new MultiFilePatch({})); + return Promise.resolve(MultiFilePatch.createNull()); } readFileFromIndex(filePath) { diff --git a/test/controllers/commit-preview-controller.test.js b/test/controllers/commit-preview-controller.test.js index f4c6944bae..e5d7eac130 100644 --- a/test/controllers/commit-preview-controller.test.js +++ b/test/controllers/commit-preview-controller.test.js @@ -21,7 +21,7 @@ describe('CommitPreviewController', function() { const props = { repository, stagingStatus: 'unstaged', - multiFilePatch: new MultiFilePatch({}), + multiFilePatch: MultiFilePatch.createNull(), workspace: atomEnv.workspace, commands: atomEnv.commands, diff --git a/test/controllers/multi-file-patch-controller.test.js b/test/controllers/multi-file-patch-controller.test.js index aed313dd76..1b3a2cd74b 100644 --- a/test/controllers/multi-file-patch-controller.test.js +++ b/test/controllers/multi-file-patch-controller.test.js @@ -160,7 +160,7 @@ describe('MultiFilePatchController', function() { // Simulate updated patch arrival const promise = wrapper.instance().patchChangePromise; - wrapper.setProps({multiFilePatch: new MultiFilePatch({})}); + wrapper.setProps({multiFilePatch: MultiFilePatch.createNull()}); await promise; // Performs an operation again From 84a91eaeb8b2c2dd79802ef3c904dbe76d7bebcd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 15:15:36 -0500 Subject: [PATCH 2074/4053] Patches end with a newline --- test/models/patch/multi-file-patch.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 8532bea250..f3abeee01f 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -269,7 +269,7 @@ describe('MultiFilePatch', function() { 1;1;0 +1;1;1 -1;1;2 - 1;1;3 + 1;1;3\n `); }); From 7b0d11f578db4b3b12b00261dc58310d4f835469 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 15:16:43 -0500 Subject: [PATCH 2075/4053] Change MultiFilePatch buffer adoption tests --- test/models/patch/multi-file-patch.test.js | 101 +++++++++------------ 1 file changed, 42 insertions(+), 59 deletions(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index f3abeee01f..f2d8f34c4a 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -4,6 +4,7 @@ import {multiFilePatchBuilder, filePatchBuilder} from '../../builder/patch'; import {TOO_LARGE, COLLAPSED, EXPANDED} from '../../../lib/models/patch/patch'; import MultiFilePatch from '../../../lib/models/patch/multi-file-patch'; +import PatchBuffer from '../../../lib/models/patch/patch-buffer'; import {assertInFilePatch} from '../../helpers'; @@ -273,8 +274,8 @@ describe('MultiFilePatch', function() { `); }); - it('adopts a buffer from a previous patch', function() { - const {multiFilePatch: lastMultiPatch} = multiFilePatchBuilder() + it('adopts a new buffer', function() { + const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(fp => { fp.setOldFile(f => f.path('A0.txt')); fp.addHunk(h => h.unchanged('a0').added('a1').deleted('a2').unchanged('a3')); @@ -286,52 +287,34 @@ describe('MultiFilePatch', function() { }) .addFilePatch(fp => { fp.setOldFile(f => f.path('A2.txt')); - fp.addHunk(h => h.oldRow(99).deleted('7').noNewline()); - }) - .build(); - - const {multiFilePatch: nextMultiPatch} = multiFilePatchBuilder() - .addFilePatch(fp => { - fp.setOldFile(f => f.path('B0.txt')); - fp.addHunk(h => h.unchanged('b0', 'b1').added('b2').unchanged('b3', 'b4')); - }) - .addFilePatch(fp => { - fp.setOldFile(f => f.path('B1.txt')); - fp.addHunk(h => h.unchanged('b5', 'b6').added('b7')); - }) - .addFilePatch(fp => { - fp.setOldFile(f => f.path('B2.txt')); - fp.addHunk(h => h.unchanged('b8', 'b9').deleted('b10').unchanged('b11')); - fp.addHunk(h => h.oldRow(99).deleted('b12').noNewline()); + fp.addHunk(h => h.oldRow(99).deleted('a10').noNewline()); }) .build(); - assert.notStrictEqual(nextMultiPatch.getBuffer(), lastMultiPatch.getBuffer()); - - nextMultiPatch.adoptBufferFrom(lastMultiPatch); - - assert.strictEqual(nextMultiPatch.getBuffer(), lastMultiPatch.getBuffer()); - assert.strictEqual(nextMultiPatch.getPatchLayer(), lastMultiPatch.getPatchLayer()); - assert.strictEqual(nextMultiPatch.getHunkLayer(), lastMultiPatch.getHunkLayer()); - assert.strictEqual(nextMultiPatch.getUnchangedLayer(), lastMultiPatch.getUnchangedLayer()); - assert.strictEqual(nextMultiPatch.getAdditionLayer(), lastMultiPatch.getAdditionLayer()); - assert.strictEqual(nextMultiPatch.getDeletionLayer(), lastMultiPatch.getDeletionLayer()); - assert.strictEqual(nextMultiPatch.getNoNewlineLayer(), lastMultiPatch.getNoNewlineLayer()); - - assert.deepEqual(nextMultiPatch.getBuffer().getText(), dedent` - b0 - b1 - b2 - b3 - b4 - b5 - b6 - b7 - b8 - b9 - b10 - b11 - b12 + const nextBuffer = new PatchBuffer(); + + multiFilePatch.adoptBuffer(nextBuffer); + + assert.strictEqual(nextBuffer.getBuffer(), multiFilePatch.getBuffer()); + assert.strictEqual(nextBuffer.getLayer('patch'), multiFilePatch.getPatchLayer()); + assert.strictEqual(nextBuffer.getLayer('hunk'), multiFilePatch.getHunkLayer()); + assert.strictEqual(nextBuffer.getLayer('unchanged'), multiFilePatch.getUnchangedLayer()); + assert.strictEqual(nextBuffer.getLayer('addition'), multiFilePatch.getAdditionLayer()); + assert.strictEqual(nextBuffer.getLayer('deletion'), multiFilePatch.getDeletionLayer()); + assert.strictEqual(nextBuffer.getLayer('nonewline'), multiFilePatch.getNoNewlineLayer()); + + assert.deepEqual(nextBuffer.getBuffer().getText(), dedent` + a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 No newline at end of file `); @@ -339,27 +322,27 @@ describe('MultiFilePatch', function() { assert.deepEqual(layer.getMarkers().map(m => m.getRange().serialize()), ranges); }; - assertMarkedLayerRanges(nextMultiPatch.getPatchLayer(), [ - [[0, 0], [4, 2]], [[5, 0], [7, 2]], [[8, 0], [13, 26]], + assertMarkedLayerRanges(nextBuffer.getLayer('patch'), [ + [[0, 0], [3, 2]], [[4, 0], [9, 2]], [[10, 0], [11, 26]], ]); - assertMarkedLayerRanges(nextMultiPatch.getHunkLayer(), [ - [[0, 0], [4, 2]], [[5, 0], [7, 2]], [[8, 0], [11, 3]], [[12, 0], [13, 26]], + assertMarkedLayerRanges(nextBuffer.getLayer('hunk'), [ + [[0, 0], [3, 2]], [[4, 0], [6, 2]], [[7, 0], [9, 2]], [[10, 0], [11, 26]], ]); - assertMarkedLayerRanges(nextMultiPatch.getUnchangedLayer(), [ - [[0, 0], [1, 2]], [[3, 0], [4, 2]], [[5, 0], [6, 2]], [[8, 0], [9, 2]], [[11, 0], [11, 3]], + assertMarkedLayerRanges(nextBuffer.getLayer('unchanged'), [ + [[0, 0], [0, 2]], [[3, 0], [3, 2]], [[4, 0], [4, 2]], [[6, 0], [6, 2]], [[7, 0], [7, 2]], [[9, 0], [9, 2]], ]); - assertMarkedLayerRanges(nextMultiPatch.getAdditionLayer(), [ - [[2, 0], [2, 2]], [[7, 0], [7, 2]], + assertMarkedLayerRanges(nextBuffer.getLayer('addition'), [ + [[1, 0], [1, 2]], [[8, 0], [8, 2]], ]); - assertMarkedLayerRanges(nextMultiPatch.getDeletionLayer(), [ - [[10, 0], [10, 3]], [[12, 0], [12, 3]], + assertMarkedLayerRanges(nextBuffer.getLayer('deletion'), [ + [[2, 0], [2, 2]], [[5, 0], [5, 2]], [[10, 0], [10, 3]], ]); - assertMarkedLayerRanges(nextMultiPatch.getNoNewlineLayer(), [ - [[13, 0], [13, 26]], + assertMarkedLayerRanges(nextBuffer.getLayer('nonewline'), [ + [[11, 0], [11, 26]], ]); - assert.strictEqual(nextMultiPatch.getBufferRowForDiffPosition('B0.txt', 1), 0); - assert.strictEqual(nextMultiPatch.getBufferRowForDiffPosition('B2.txt', 5), 12); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('A0.txt', 1), 0); + assert.strictEqual(multiFilePatch.getBufferRowForDiffPosition('A1.txt', 5), 7); }); describe('derived patch generation', function() { From e043d08a31a83942c8e7bb3e6bf626844c11a51a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 15:17:55 -0500 Subject: [PATCH 2076/4053] getNextSelectionRange => getMaxSelectionIndex+getSelectionRangeForIndex --- test/models/patch/multi-file-patch.test.js | 113 +++++++-------------- 1 file changed, 34 insertions(+), 79 deletions(-) diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index f2d8f34c4a..0917a6e0e4 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -551,24 +551,14 @@ describe('MultiFilePatch', function() { }); }); - describe('next selection range derivation', function() { - it('selects the origin if the new patch is empty', function() { - const {multiFilePatch: lastMultiPatch} = multiFilePatchBuilder().addFilePatch().build(); - const {multiFilePatch: nextMultiPatch} = multiFilePatchBuilder().build(); - - const nextSelectionRange = nextMultiPatch.getNextSelectionRange(lastMultiPatch, new Set()); - assert.deepEqual(nextSelectionRange.serialize(), [[0, 0], [0, 0]]); - }); - - it('selects the first change row if there was no prior selection', function() { - const {multiFilePatch: lastMultiPatch} = multiFilePatchBuilder().build(); - const {multiFilePatch: nextMultiPatch} = multiFilePatchBuilder().addFilePatch().build(); - const nextSelectionRange = nextMultiPatch.getNextSelectionRange(lastMultiPatch, new Set()); - assert.deepEqual(nextSelectionRange.serialize(), [[1, 0], [1, Infinity]]); + describe('maximum selection index', function() { + it('returns zero if there are no selections', function() { + const {multiFilePatch} = multiFilePatchBuilder().addFilePatch().build(); + assert.strictEqual(multiFilePatch.getMaxSelectionIndex(new Set()), 0); }); - it('preserves the numeric index of the highest selected change row', function() { - const {multiFilePatch: lastMultiPatch} = multiFilePatchBuilder() + it('returns the ordinal index of the highest selected change row', function() { + const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(fp => { fp.addHunk(h => h.unchanged('.').added('0', '1', 'x *').unchanged('.')); fp.addHunk(h => h.unchanged('.').deleted('2').added('3').unchanged('.')); @@ -579,83 +569,48 @@ describe('MultiFilePatch', function() { }) .build(); - const {multiFilePatch: nextMultiPatch} = multiFilePatchBuilder() - .addFilePatch(fp => { - fp.addHunk(h => h.unchanged('.').added('0', '1').unchanged('x', '.')); - fp.addHunk(h => h.unchanged('.').deleted('2').added('3').unchanged('.')); - }) - .addFilePatch(fp => { - fp.addHunk(h => h.unchanged('.').deleted('4', '6 *').unchanged('.')); - fp.addHunk(h => h.unchanged('.').added('7').unchanged('.')); - }) - .build(); - - const nextSelectionRange = nextMultiPatch.getNextSelectionRange(lastMultiPatch, new Set([3, 11])); - assert.deepEqual(nextSelectionRange.serialize(), [[11, 0], [11, Infinity]]); - }); - - describe('when the bottom-most changed row is selected', function() { - it('selects the bottom-most changed row of the new patch', function() { - const {multiFilePatch: lastMultiPatch} = multiFilePatchBuilder() - .addFilePatch(fp => { - fp.addHunk(h => h.unchanged('.').added('0', '1', 'x').unchanged('.')); - fp.addHunk(h => h.unchanged('.').deleted('2').added('3').unchanged('.')); - }) - .addFilePatch(fp => { - fp.addHunk(h => h.unchanged('.').deleted('4', '5', '6').unchanged('.')); - fp.addHunk(h => h.unchanged('.').added('7', '8 *').unchanged('.')); - }) - .build(); - - const {multiFilePatch: nextMultiPatch} = multiFilePatchBuilder() - .addFilePatch(fp => { - fp.addHunk(h => h.unchanged('.').added('0', '1', 'x').unchanged('.')); - fp.addHunk(h => h.unchanged('.').deleted('2').added('3').unchanged('.')); - }) - .addFilePatch(fp => { - fp.addHunk(h => h.unchanged('.').deleted('4', '5', '6').unchanged('.')); - fp.addHunk(h => h.unchanged('.').added('7').unchanged('.')); - }) - .build(); - - const nextSelectionRange = nextMultiPatch.getNextSelectionRange(lastMultiPatch, new Set([16])); - assert.deepEqual(nextSelectionRange.serialize(), [[15, 0], [15, Infinity]]); - }); + assert.strictEqual(multiFilePatch.getMaxSelectionIndex(new Set([3])), 2); + assert.strictEqual(multiFilePatch.getMaxSelectionIndex(new Set([3, 11])), 5); }); + }); - it('skips hunks that were completely selected', function() { - const {multiFilePatch: lastMultiPatch} = multiFilePatchBuilder() - .addFilePatch(fp => { - fp.addHunk(h => h.unchanged('.').added('0').unchanged('.')); - fp.addHunk(h => h.unchanged('.').added('x *', 'x *').unchanged('.')); - }) + describe('selection range by change index', function() { + it('selects the last change row if no longer present', function() { + const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(fp => { - fp.addHunk(h => h.unchanged('.').deleted('x *').unchanged('.')); + fp.addHunk(h => h.unchanged('.').added('0', '1', '2').unchanged('.')); + fp.addHunk(h => h.unchanged('.').deleted('3').added('4').unchanged('.')); }) .addFilePatch(fp => { - fp.addHunk(h => h.unchanged('.').added('x *', '1').deleted('2').unchanged('.')); - fp.addHunk(h => h.unchanged('.').deleted('x *').unchanged('.')); - fp.addHunk(h => h.unchanged('.', '.').deleted('4', '5 *', '6').unchanged('.')); - fp.addHunk(h => h.unchanged('.').deleted('7', '8').unchanged('.', '.')); + fp.addHunk(h => h.unchanged('.').deleted('5', '6', '7').unchanged('.')); + fp.addHunk(h => h.unchanged('.').added('8').unchanged('.')); }) .build(); - const {multiFilePatch: nextMultiPatch} = multiFilePatchBuilder() + assert.deepEqual(multiFilePatch.getSelectionRangeForIndex(9).serialize(), [[15, 0], [15, Infinity]]); + }); + + it('returns the range of the change row by ordinal', function() { + const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(fp => { - fp.addHunk(h => h.unchanged('.').added('0').unchanged('.')); + fp.addHunk(h => h.unchanged('.').added('0', '1', '2').unchanged('.')); + fp.addHunk(h => h.unchanged('.').deleted('3').added('4').unchanged('.')); }) .addFilePatch(fp => { - fp.addHunk(h => h.unchanged('.', 'x').added('1').deleted('2').unchanged('.')); - fp.addHunk(h => h.unchanged('.', '.').deleted('4', '6 +').unchanged('.')); - fp.addHunk(h => h.unchanged('.').deleted('7', '8').unchanged('.', '.')); + fp.addHunk(h => h.unchanged('.').deleted('5', '6', '7').unchanged('.')); + fp.addHunk(h => h.unchanged('.').added('8').unchanged('.')); }) .build(); - const nextSelectionRange = nextMultiPatch.getNextSelectionRange( - lastMultiPatch, - new Set([4, 5, 8, 11, 16, 21]), - ); - assert.deepEqual(nextSelectionRange.serialize(), [[11, 0], [11, Infinity]]); + assert.deepEqual(multiFilePatch.getSelectionRangeForIndex(0).serialize(), [[1, 0], [1, Infinity]]); + assert.deepEqual(multiFilePatch.getSelectionRangeForIndex(1).serialize(), [[2, 0], [2, Infinity]]); + assert.deepEqual(multiFilePatch.getSelectionRangeForIndex(2).serialize(), [[3, 0], [3, Infinity]]); + assert.deepEqual(multiFilePatch.getSelectionRangeForIndex(3).serialize(), [[6, 0], [6, Infinity]]); + assert.deepEqual(multiFilePatch.getSelectionRangeForIndex(4).serialize(), [[7, 0], [7, Infinity]]); + assert.deepEqual(multiFilePatch.getSelectionRangeForIndex(5).serialize(), [[10, 0], [10, Infinity]]); + assert.deepEqual(multiFilePatch.getSelectionRangeForIndex(6).serialize(), [[11, 0], [11, Infinity]]); + assert.deepEqual(multiFilePatch.getSelectionRangeForIndex(7).serialize(), [[12, 0], [12, Infinity]]); + assert.deepEqual(multiFilePatch.getSelectionRangeForIndex(8).serialize(), [[15, 0], [15, Infinity]]); }); }); From 5a20cf2f6f88de29fbf26ca79c107f16bd85767e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 15:18:06 -0500 Subject: [PATCH 2077/4053] MultiFilePatch tests: :white_check_mark: + :100: --- lib/models/patch/multi-file-patch.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 5b28a5f97f..72fb5f937b 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -10,7 +10,7 @@ export default class MultiFilePatch { constructor({patchBuffer, filePatches}) { this.patchBuffer = patchBuffer; - this.filePatches = filePatches || []; + this.filePatches = filePatches; this.filePatchesByMarker = new Map(); this.filePatchesByPath = new Map(); @@ -138,12 +138,7 @@ export default class MultiFilePatch { getMaxSelectionIndex(selectedRows) { if (selectedRows.size === 0) { - const [firstPatch] = this.getFilePatches(); - if (!firstPatch) { - return Range.fromObject([[0, 0], [0, 0]]); - } - - return firstPatch.getFirstChangeRange(); + return 0; } const lastMax = Math.max(...selectedRows); @@ -188,7 +183,7 @@ export default class MultiFilePatch { let remainingChangedLines = selectionIndex; let foundRow = false; - let lastChangedRow; + let lastChangedRow = 0; patchLoop: for (const filePatch of this.getFilePatches()) { for (const hunk of filePatch.getHunks()) { @@ -307,7 +302,9 @@ export default class MultiFilePatch { filePatch.triggerCollapseIn(this.patchBuffer); this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); + // This hunk collection should be empty, but let's iterate anyway just in case filePatch was already collapsed + /* istanbul ignore next */ for (const hunk of filePatch.getHunks()) { this.hunksByMarker.set(hunk.getMarker(), hunk); } @@ -377,6 +374,7 @@ export default class MultiFilePatch { /* * Construct a string of diagnostic information useful for debugging. */ + /* istanbul ignore next */ inspect() { let inspectString = '(MultiFilePatch'; inspectString += ` filePatchesByMarker=(${Array.from(this.filePatchesByMarker.keys(), m => m.id).join(', ')})`; @@ -388,6 +386,7 @@ export default class MultiFilePatch { return inspectString; } + /* istanbul ignore next */ isEqual(other) { return this.toString() === other.toString(); } From cc5cba8d285c893a4d12aeea8aed8e30c30814a1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 21 Feb 2019 15:24:17 -0500 Subject: [PATCH 2078/4053] :shirt: --- lib/atom/atom-text-editor.js | 1 + lib/views/multi-file-patch-view.js | 2 ++ test/models/patch/builder.test.js | 2 -- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index f3333e64db..3e4425c0d3 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -128,6 +128,7 @@ export default class AtomTextEditor extends React.Component { } else { this.refElement.map(element => element.classList.remove(EMPTY_CLASS)); } + return null; }); } diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 90751c3dae..515c72a1de 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -115,6 +115,7 @@ export default class MultiFilePatchView extends React.Component { lastSelectionIndex = this.props.multiFilePatch.getMaxSelectionIndex(this.props.selectedRows); lastScrollTop = editor.getElement().getScrollTop(); lastScrollLeft = editor.getElement().getScrollLeft(); + return null; }); }), this.props.onDidUpdatePatch(nextPatch => { @@ -141,6 +142,7 @@ export default class MultiFilePatchView extends React.Component { } if (lastScrollTop !== null) { editor.getElement().setScrollTop(lastScrollTop); } if (lastScrollLeft !== null) { editor.getElement().setScrollLeft(lastScrollLeft); } + return null; }); }), ); diff --git a/test/models/patch/builder.test.js b/test/models/patch/builder.test.js index 9ecf093c78..83816454c3 100644 --- a/test/models/patch/builder.test.js +++ b/test/models/patch/builder.test.js @@ -1,5 +1,3 @@ -import dedent from 'dedent-js'; - import {buildFilePatch, buildMultiFilePatch} from '../../../lib/models/patch'; import {TOO_LARGE, EXPANDED} from '../../../lib/models/patch/patch'; import {multiFilePatchBuilder} from '../../builder/patch'; From 6634a4b7ae81ff07bd97a880aa913cec81451836 Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 21 Feb 2019 14:07:10 -0800 Subject: [PATCH 2079/4053] fix mfpView test for preserving the selection index Now we have these functions for willUpdate and didUpdate, which need to be passed in as props and then manually called in the test in order for the component to be in the state we want. Co-Authored-By: Katrina Uychaco --- test/views/multi-file-patch-view.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 9862be3d50..b2c32563d5 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -229,10 +229,23 @@ describe('MultiFilePatchView', function() { it('preserves the selection index when a new file patch arrives in line selection mode', function() { const selectedRowsChanged = sinon.spy(); + + let willUpdate, didUpdate; + const onWillUpdatePatch = cb => { + willUpdate = cb; + return {dispose: () => {}} + } + const onDidUpdatePatch = cb => { + didUpdate = cb; + return {dispose: () => {}} + } + const wrapper = mount(buildApp({ selectedRows: new Set([2]), selectionMode: 'line', selectedRowsChanged, + onWillUpdatePatch, + onDidUpdatePatch })); const {multiFilePatch} = multiFilePatchBuilder() @@ -244,7 +257,9 @@ describe('MultiFilePatchView', function() { }); }).build(); + willUpdate(); wrapper.setProps({multiFilePatch}); + didUpdate(multiFilePatch); assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [3]); assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'line'); From ae41e10f2cca6d9c85bf5e3f2490f23c48a7a2e1 Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 21 Feb 2019 14:11:59 -0800 Subject: [PATCH 2080/4053] fix mfpView test for hunk selection mode --- test/views/multi-file-patch-view.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index b2c32563d5..e995833316 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -295,12 +295,24 @@ describe('MultiFilePatchView', function() { }); }).build(); + let willUpdate, didUpdate; + const onWillUpdatePatch = cb => { + willUpdate = cb; + return {dispose: () => {}} + } + const onDidUpdatePatch = cb => { + didUpdate = cb; + return {dispose: () => {}} + } + const selectedRowsChanged = sinon.spy(); const wrapper = mount(buildApp({ multiFilePatch, selectedRows: new Set([6, 7, 8]), selectionMode: 'hunk', selectedRowsChanged, + onWillUpdatePatch, + onDidUpdatePatch })); const {multiFilePatch: nextMfp} = multiFilePatchBuilder() @@ -320,7 +332,9 @@ describe('MultiFilePatchView', function() { }); }).build(); + willUpdate(); wrapper.setProps({multiFilePatch: nextMfp}); + didUpdate(nextMfp); assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [6, 7]); assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'hunk'); From e41ca56fde1390f8acef2f0410e451d5fde28a13 Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 21 Feb 2019 14:28:23 -0800 Subject: [PATCH 2081/4053] :shirt: --- test/views/multi-file-patch-view.test.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index e995833316..c222a430c3 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -233,19 +233,19 @@ describe('MultiFilePatchView', function() { let willUpdate, didUpdate; const onWillUpdatePatch = cb => { willUpdate = cb; - return {dispose: () => {}} - } + return {dispose: () => {}}; + }; const onDidUpdatePatch = cb => { didUpdate = cb; - return {dispose: () => {}} - } + return {dispose: () => {}}; + }; const wrapper = mount(buildApp({ selectedRows: new Set([2]), selectionMode: 'line', selectedRowsChanged, onWillUpdatePatch, - onDidUpdatePatch + onDidUpdatePatch, })); const {multiFilePatch} = multiFilePatchBuilder() @@ -298,12 +298,12 @@ describe('MultiFilePatchView', function() { let willUpdate, didUpdate; const onWillUpdatePatch = cb => { willUpdate = cb; - return {dispose: () => {}} - } + return {dispose: () => {}}; + }; const onDidUpdatePatch = cb => { didUpdate = cb; - return {dispose: () => {}} - } + return {dispose: () => {}}; + }; const selectedRowsChanged = sinon.spy(); const wrapper = mount(buildApp({ @@ -312,7 +312,7 @@ describe('MultiFilePatchView', function() { selectionMode: 'hunk', selectedRowsChanged, onWillUpdatePatch, - onDidUpdatePatch + onDidUpdatePatch, })); const {multiFilePatch: nextMfp} = multiFilePatchBuilder() From fbf56e62ef98176d993346fde6dbec3a8a7adf43 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Thu, 21 Feb 2019 16:58:42 -0800 Subject: [PATCH 2082/4053] In onDidUpdatePatch callback, let parent component know that we updated the selected row Fixes https://github.com/atom/github/projects/18#card-17958157. Consider reverting https://github.com/atom/github/pull/1913/commits/2dad315efc313518df98613f1103b087330c0aaf as it may be obsoleted by this change. Check with @smashwilson first. Co-Authored-By: Tilde Ann Thurium --- lib/views/multi-file-patch-view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 515c72a1de..d992239375 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -144,6 +144,7 @@ export default class MultiFilePatchView extends React.Component { if (lastScrollLeft !== null) { editor.getElement().setScrollLeft(lastScrollLeft); } return null; }); + this.didChangeSelectedRows(); }), ); } From 93750c99afb2053e84f119edd5336785531480bf Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 21 Feb 2019 17:20:09 -0800 Subject: [PATCH 2083/4053] fiddle with large diff gate styles - make the hover color brighter because the color I originally picked doesn't look very noticeable on certain themes - add some top and bottom margin so it doesn't look so cramped if the large diff gate exists inthe middle of other patches Co-Authored-By: Katrina Uychaco --- styles/file-patch-view.less | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 678eee782c..fa2aa29b3e 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -77,7 +77,7 @@ &-message { font-family: @font-family; text-align: center; - margin-bottom: 0; + margin: 15px 0 0 0; } &-showDiffButton { @@ -85,8 +85,10 @@ border: none; color: @text-color-highlight; font-size: 18px; + margin-bottom: 15px; + &:hover { - color: @syntax-text-color + color: @gh-background-color-blue; } } From 6c83111967dbef06a7a22cc4cb542e1b0400fa6c Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 22 Feb 2019 14:05:07 +0800 Subject: [PATCH 2084/4053] threshold 800 lines! --- lib/models/patch/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index bc3ef888a7..f378c3f838 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -9,7 +9,7 @@ import MultiFilePatch from './multi-file-patch'; export const DEFAULT_OPTIONS = { // Number of lines after which we consider the diff "large" // TODO: Set this based on performance measurements - largeDiffThreshold: 100, + largeDiffThreshold: 800, // Map of file path (relative to repository root) to Patch render status (EXPANDED, COLLAPSED, TOO_LARGE) renderStatusOverrides: {}, From 0838f73681fb6123c50be49e13267e6d599c6422 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 22 Feb 2019 15:52:54 +0800 Subject: [PATCH 2085/4053] add MFP view performance metrics --- lib/views/multi-file-patch-view.js | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index d992239375..f7ff96ae27 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -87,6 +87,7 @@ export default class MultiFilePatchView extends React.Component { this.refRoot = new RefHolder(); this.refEditor = new RefHolder(); this.refEditorElement = new RefHolder(); + this.mounted = false; this.subs = new CompositeDisposable(); @@ -151,6 +152,9 @@ export default class MultiFilePatchView extends React.Component { } componentDidMount() { + this.mounted = true; + this.measurePerformance('mount'); + window.addEventListener('mouseup', this.didMouseUp); this.refEditor.map(editor => { // this.props.multiFilePatch is guaranteed to contain at least one FilePatch if is rendered. @@ -171,6 +175,8 @@ export default class MultiFilePatchView extends React.Component { } componentDidUpdate(prevProps, prevState) { + this.measurePerformance('update'); + if (prevProps.refInitialFocus !== this.props.refInitialFocus) { prevProps.refInitialFocus && prevProps.refInitialFocus.setter(null); this.props.refInitialFocus && this.refEditorElement.map(this.props.refInitialFocus.setter); @@ -194,6 +200,12 @@ export default class MultiFilePatchView extends React.Component { {'github-FilePatchView--hunkMode': this.props.selectionMode === 'hunk'}, ); + if (this.mounted) { + performance.mark('MultiFilePatchView-update-start'); + } else { + performance.mark('MultiFilePatchView-mount-start'); + } + return (
    {this.renderCommands()} @@ -1187,4 +1199,23 @@ export default class MultiFilePatchView extends React.Component { return NBSP_CHARACTER.repeat(maxDigits - num.toString().length) + num.toString(); } } + + measurePerformance(action) { + if (action === 'update' || action === 'mount') { + performance.mark(`MultiFilePatchView-${action}-end`); + performance.measure( + `MultiFilePatchView-${action}`, + `MultiFilePatchView-${action}-start`, + `MultiFilePatchView-${action}-end`); + const perf = performance.getEntriesByName(`MultiFilePatchView-${action}`)[0]; + performance.clearMarks(`MultiFilePatchView-${action}-start`); + performance.clearMarks(`MultiFilePatchView-${action}-end`); + performance.clearMeasures(`MultiFilePatchView-${action}`); + addEvent(`MultiFilePatchView-${action}`, { + package: 'github', + filePatchesLineCount: [], + duration: perf.duration, + }); + } + } } From 3f2f592ef7cf0e01bcc9c4f7afdd7f4d73cad00c Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 22 Feb 2019 16:00:29 +0800 Subject: [PATCH 2086/4053] add linecounts --- lib/views/multi-file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index f7ff96ae27..819a3ddd9a 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -1213,7 +1213,7 @@ export default class MultiFilePatchView extends React.Component { performance.clearMeasures(`MultiFilePatchView-${action}`); addEvent(`MultiFilePatchView-${action}`, { package: 'github', - filePatchesLineCount: [], + filePatchesLineCounts: this.props.multiFilePatch.getFilePatches().map(fp => fp.getPatch().getChangedLineCount()), duration: perf.duration, }); } From fef2d67b40f0adeac934ee2ca2af857218981721 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 22 Feb 2019 16:01:11 +0800 Subject: [PATCH 2087/4053] lint --- lib/views/multi-file-patch-view.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 819a3ddd9a..21c39ff4de 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -1213,7 +1213,9 @@ export default class MultiFilePatchView extends React.Component { performance.clearMeasures(`MultiFilePatchView-${action}`); addEvent(`MultiFilePatchView-${action}`, { package: 'github', - filePatchesLineCounts: this.props.multiFilePatch.getFilePatches().map(fp => fp.getPatch().getChangedLineCount()), + filePatchesLineCounts: this.props.multiFilePatch.getFilePatches().map( + fp => fp.getPatch().getChangedLineCount(), + ), duration: perf.duration, }); } From 5873d143864a9907eacda1e1be53b88fe7d94d7f Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 22 Feb 2019 16:04:59 +0800 Subject: [PATCH 2088/4053] clear all perf markers and measurements unpon unmount --- lib/views/multi-file-patch-view.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 21c39ff4de..a73dc9373b 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -190,6 +190,8 @@ export default class MultiFilePatchView extends React.Component { componentWillUnmount() { window.removeEventListener('mouseup', this.didMouseUp); this.subs.dispose(); + performance.clearMarks(); + performance.clearMeasures(); } render() { From c7158423dc0ac6c920264aef42435fdb2ad59d92 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 22 Feb 2019 17:52:56 +0800 Subject: [PATCH 2089/4053] dispose subscriptions upon commit preview unmount --- lib/containers/commit-preview-container.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/containers/commit-preview-container.js b/lib/containers/commit-preview-container.js index 9abba725dc..388c98be61 100644 --- a/lib/containers/commit-preview-container.js +++ b/lib/containers/commit-preview-container.js @@ -91,6 +91,10 @@ export default class CommitPreviewContainer extends React.Component { ); } + componentWillUnmount() { + this.sub.dispose(); + } + onWillUpdatePatch = cb => this.emitter.on('will-update-patch', cb); onDidUpdatePatch = cb => this.emitter.on('did-update-patch', cb); From b018d22744cf457c3a2fb144dbf37dea316958d7 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 22 Feb 2019 17:53:51 +0800 Subject: [PATCH 2090/4053] dispose subscriptions upon component unmount --- lib/containers/commit-detail-container.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/containers/commit-detail-container.js b/lib/containers/commit-detail-container.js index eedf3370be..c5debebe78 100644 --- a/lib/containers/commit-detail-container.js +++ b/lib/containers/commit-detail-container.js @@ -63,4 +63,8 @@ export default class CommitDetailContainer extends React.Component { /> ); } + + componentWillUnmount() { + this.sub.dispose(); + } } From 7d1acf1c01a57a5abb0a645ba19df0af9b97c0f0 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 22 Feb 2019 23:19:31 +0800 Subject: [PATCH 2091/4053] set unmount --- lib/views/multi-file-patch-view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index a73dc9373b..e25097aa82 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -190,6 +190,7 @@ export default class MultiFilePatchView extends React.Component { componentWillUnmount() { window.removeEventListener('mouseup', this.didMouseUp); this.subs.dispose(); + this.mounted = false; performance.clearMarks(); performance.clearMeasures(); } From 35286718f025c48c5673d20cf1bc4dcfef06898e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 22 Feb 2019 11:48:32 -0500 Subject: [PATCH 2092/4053] Hide .cursor-line instead of .line If we apply .line.dummy by mistake, we break the TextEditor's ability to measure line height and get overlapping text --- styles/atom-text-editor.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/atom-text-editor.less b/styles/atom-text-editor.less index 8bd338ad98..a5c61779d4 100644 --- a/styles/atom-text-editor.less +++ b/styles/atom-text-editor.less @@ -5,6 +5,6 @@ } // Conceal the automatically added blank line element within TextEditors that we want to actually be empty -.github-AtomTextEditor-empty .line { +.github-AtomTextEditor-empty .cursor-line { display: none; } From 9d6034052d281d06f5d6dd734bedd72c97c76c3c Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Sat, 23 Feb 2019 01:12:01 +0800 Subject: [PATCH 2093/4053] better conditionals --- lib/views/multi-file-patch-view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index e25097aa82..4e350ef7eb 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -1204,7 +1204,8 @@ export default class MultiFilePatchView extends React.Component { } measurePerformance(action) { - if (action === 'update' || action === 'mount') { + if ((action === 'update' || action === 'mount') + && performance.getEntriesByName(`MultiFilePatchView-${action}-start`).length > 0) { performance.mark(`MultiFilePatchView-${action}-end`); performance.measure( `MultiFilePatchView-${action}`, From 0da4f6020961d3a9ddc0f43737ce740531482a60 Mon Sep 17 00:00:00 2001 From: annthurium Date: Fri, 22 Feb 2019 11:45:09 -0800 Subject: [PATCH 2094/4053] bump version of Atom engine the newer version of atom contains an update to how markers are ordered. We need this marker ordering update in order for patches to be ordered consistently in the large diff gate pull request. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac2a5101f8..fe9d2f7588 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "relay": "relay-compiler --src ./lib --schema graphql/schema.graphql" }, "engines": { - "atom": ">=1.32.0" + "atom": ">=1.37.0" }, "atomTestRunner": "./test/runner", "atomTranspilers": [ From 14bfc276fbe673d7560ebed450ed99b41fef05fe Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 22 Feb 2019 12:51:34 -0800 Subject: [PATCH 2095/4053] Revert 2dad315efc313518df98613f1103b087330c0aaf I think it is obsoleted by fbf56e62ef98176d993346fde6dbec3a8a7adf43 @smashwilson will verify by testing Co-Authored-By: Ash Wilson --- lib/views/multi-file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 4e350ef7eb..989ccf14e7 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -121,7 +121,6 @@ export default class MultiFilePatchView extends React.Component { }), this.props.onDidUpdatePatch(nextPatch => { this.refEditor.map(editor => { - this.suppressChanges = false; if (lastSelectionIndex !== null) { const nextSelectionRange = nextPatch.getSelectionRangeForIndex(lastSelectionIndex); if (this.props.selectionMode === 'line') { @@ -145,6 +144,7 @@ export default class MultiFilePatchView extends React.Component { if (lastScrollLeft !== null) { editor.getElement().setScrollLeft(lastScrollLeft); } return null; }); + this.suppressChanges = false; this.didChangeSelectedRows(); }), ); From 7e6609305cef3efc37319658846343521f27f91c Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 22 Feb 2019 13:25:02 -0800 Subject: [PATCH 2096/4053] Add test to verify that `selectedRowsChanged` gets called with the new editor selection index This is crucially importent when staging the last line in a non-last file patch because the selected row has changed. Whereas when staging a non-last line the selection index typically stays the same. --- test/views/multi-file-patch-view.test.js | 73 ++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index c222a430c3..e96c3df4bc 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -344,6 +344,79 @@ describe('MultiFilePatchView', function() { ]); }); + describe('when the last line in a non-last file patch is staged', function() { + it('updates the selected row to be the first changed line in the next file patch', function() { + const selectedRowsChanged = sinon.spy(); + + let willUpdate, didUpdate; + const onWillUpdatePatch = cb => { + willUpdate = cb; + return {dispose: () => {}}; + }; + const onDidUpdatePatch = cb => { + didUpdate = cb; + return {dispose: () => {}}; + }; + + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('path.txt')); + fp.addHunk(h => { + h.oldRow(5); + h.unchanged('0000').added('0001').unchanged('0002').deleted('0003').unchanged('0004'); + }); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('another-path.txt')); + fp.addHunk(h => { + h.oldRow(5); + h.unchanged('0000').added('0001').unchanged('0002').deleted('0003').unchanged('0004'); + }); + }).build(); + + const wrapper = mount(buildApp({ + selectedRows: new Set([3]), + selectionMode: 'line', + selectedRowsChanged, + onWillUpdatePatch, + onDidUpdatePatch, + multiFilePatch, + })); + + assert.deepEqual([...wrapper.prop('selectedRows')], [3]); + + const {multiFilePatch: multiFilePatch2} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('path.txt')); + fp.addHunk(h => { + h.oldRow(5); + h.unchanged('0000').added('0001').unchanged('0002').unchanged('0003').unchanged('0004'); + }); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('another-path.txt')); + fp.addHunk(h => { + h.oldRow(5); + h.unchanged('0000').added('0001').unchanged('0002').deleted('0003').unchanged('0004'); + }); + }).build(); + + selectedRowsChanged.resetHistory(); + willUpdate(); + wrapper.setProps({multiFilePatch: multiFilePatch2}); + didUpdate(multiFilePatch2); + + assert.strictEqual(selectedRowsChanged.callCount, 1); + assert.sameMembers(Array.from(selectedRowsChanged.lastCall.args[0]), [6]); + assert.strictEqual(selectedRowsChanged.lastCall.args[1], 'line'); + + const editor = wrapper.find('AtomTextEditor').instance().getModel(); + assert.deepEqual(editor.getSelectedBufferRanges().map(r => r.serialize()), [ + [[6, 0], [6, 4]], + ]); + }); + }); + it('unregisters the mouseup handler on unmount', function() { sinon.spy(window, 'addEventListener'); sinon.spy(window, 'removeEventListener'); From 26e6cf3bcd2504b8d269eff5de92275568f39df9 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 25 Feb 2019 17:34:38 +0800 Subject: [PATCH 2097/4053] draft of mfp atlas --- docs/react-component-atlas.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md index 596b3acbb4..3234d2a053 100644 --- a/docs/react-component-atlas.md +++ b/docs/react-component-atlas.md @@ -178,3 +178,29 @@ This is a high-level overview of the structure of the React component tree that > > > [``](/lib/views/changed-files-count-view.js) > > > > > > Displays the GitHub logo. Clicking it opens the GitHub tab. + + +## `MultiFilePatchView` Atlas + +> [``](/lib/views/multi-file-patch-view.js) +> > [``](lib/atom/atom-text-editor.js) +> > +> > React implementation of an [Atom TextEditor](https://atom.io/docs/api/latest/TextEditor). Each `MultiFilePatchView` contains one `AtomTextEditor`, regardless of the number of file patch. +> > +> > > [``](lib/atom/gutter.js) +> > > +> > > gutter explanation +> > +> > > [``](lib/atom/marker-layer.js) +> > > > +> > > > [``](lib/atom/marker.js) +> > > > +> > > > marker explanation +> > > > +> > > > > [``](lib/atom/decoration.js) +> > > > > +> > > > > decoration explanation +> > > > > +> > > > > > [``](lib/views/file-patch-header-view.js) +> > > > > +> > > > > > [``](lib/views/hunk-header-view.js) From ea0003be56f9f783fa2867b5c833a9be8bfa955e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 25 Feb 2019 19:41:38 +0800 Subject: [PATCH 2098/4053] more descirption --- docs/react-component-atlas.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md index 3234d2a053..ae52537a23 100644 --- a/docs/react-component-atlas.md +++ b/docs/react-component-atlas.md @@ -185,21 +185,23 @@ This is a high-level overview of the structure of the React component tree that > [``](/lib/views/multi-file-patch-view.js) > > [``](lib/atom/atom-text-editor.js) > > -> > React implementation of an [Atom TextEditor](https://atom.io/docs/api/latest/TextEditor). Each `MultiFilePatchView` contains one `AtomTextEditor`, regardless of the number of file patch. +> > React wrapper of an [Atom TextEditor](https://atom.io/docs/api/latest/TextEditor). Each `MultiFilePatchView` contains one `AtomTextEditor`, regardless of the number of file patch. > > > > > [``](lib/atom/gutter.js) > > > -> > > gutter explanation +> > > React wrapper of Atom's [Gutter](https://atom.io/docs/api/latest/Gutter) class. > > > > > [``](lib/atom/marker-layer.js) > > > > +> > > > React wrapper of Atom's [MarkerLayer](https://atom.io/docs/api/latest/MarkerLayer) class. +> > > > > > > > [``](lib/atom/marker.js) > > > > -> > > > marker explanation +> > > > React wrapper of Atom's [DisplayMarker](https://atom.io/docs/api/latest/DisplayMarker) class. > > > > > > > > > [``](lib/atom/decoration.js) > > > > > -> > > > > decoration explanation +> > > > > React wrapper of Atom's [Decoration](https://atom.io/docs/api/latest/Decoration) class. > > > > > > > > > > > [``](lib/views/file-patch-header-view.js) > > > > > From b2cf51e2e59cd3830815be8334361649f4785aa6 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 25 Feb 2019 19:41:50 +0800 Subject: [PATCH 2099/4053] headers description --- docs/react-component-atlas.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md index ae52537a23..5b9fc13d01 100644 --- a/docs/react-component-atlas.md +++ b/docs/react-component-atlas.md @@ -204,5 +204,9 @@ This is a high-level overview of the structure of the React component tree that > > > > > React wrapper of Atom's [Decoration](https://atom.io/docs/api/latest/Decoration) class. > > > > > > > > > > > [``](lib/views/file-patch-header-view.js) +> > > > > > +> > > > > > Header above each file patch. Handles file patch level operations (e.g. discard change, stage/unstage, jump to file, expand/collapse file patch, etc.) > > > > > > > > > > > [``](lib/views/hunk-header-view.js) +> > > > > > +> > > > > > Header above each hunk. Handles more granular stage/unstage operation (per hunk or per line). From 0c3d099abbc29596bcdcd418c0aba82ca5dcaa15 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 25 Feb 2019 19:46:30 +0800 Subject: [PATCH 2100/4053] link to the new section --- docs/react-component-atlas.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/react-component-atlas.md b/docs/react-component-atlas.md index 5b9fc13d01..8ddd9fc97e 100644 --- a/docs/react-component-atlas.md +++ b/docs/react-component-atlas.md @@ -74,8 +74,8 @@ This is a high-level overview of the structure of the React component tree that > > > [``](/lib/controllers/multi-file-patch-controller.js) > > > [``](/lib/views/multi-file-patch-view.js) > > > -> > > Render a sequence of git-generated file patches within a TextEditor, using decorations to include contextually -> > > relevant controls. +> > > Render a sequence of git-generated file patches within a TextEditor, using decorations to include contextually relevant controls. +> > > See [`MultiFilePatchView` atlas](#multifilepatchview-atlas) below for a more detailed breakdown. > > > [``](/lig/items/commit-preview-item.js) > > [``](/lib/containers/commit-preview-container.js) @@ -180,6 +180,7 @@ This is a high-level overview of the structure of the React component tree that > > > Displays the GitHub logo. Clicking it opens the GitHub tab. + ## `MultiFilePatchView` Atlas > [``](/lib/views/multi-file-patch-view.js) From b5f3c256729f6d033f9f5633af9919047741d79d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 25 Feb 2019 15:01:22 -0500 Subject: [PATCH 2101/4053] Upgrade Babel to Babel 6 Relay 3 needs Babel 7, so it's time for us to upgrade our transpilation pipeline. --- package.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index fae6334e0d..18d3a4eee7 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "atomTranspilers": [ { "glob": "{lib,test}/**/*.js", - "transpiler": "atom-babel6-transpiler", + "transpiler": "@atom/babel7-transpiler", "options": { "cacheKeyFiles": [ "package.json", @@ -39,14 +39,13 @@ } ], "dependencies": { - "atom-babel6-transpiler": "1.2.0", - "babel-generator": "6.26.1", + "@atom/babel7-transpiler": "1.0.0-1", + "@babel/generator": "7.3.4", + "@babel/plugin-proposal-class-properties": "7.3.4", + "@babel/preset-env": "7.3.4", + "@babel/preset-react": "7.0.0", "babel-plugin-chai-assert-async": "0.1.0", "babel-plugin-relay": "1.7.0", - "babel-plugin-transform-class-properties": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", - "babel-plugin-transform-object-rest-spread": "6.26.0", - "babel-preset-react": "6.24.1", "bintrees": "1.0.2", "bytes": "^3.0.0", "classnames": "2.2.6", From e2f8e077e07e569ae466bbfa0a79a65307d02d36 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 25 Feb 2019 15:02:29 -0500 Subject: [PATCH 2102/4053] Use a .babelrc.js file instead of a .babelrc This lets us use `process.versions.electron` to magically transpile source appropriate to the actual Electron version we're running under. --- .babelrc | 20 -------------------- .babelrc.js | 20 ++++++++++++++++++++ package.json | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) delete mode 100644 .babelrc create mode 100644 .babelrc.js diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 4421da864b..0000000000 --- a/.babelrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "sourceMaps": "inline", - "babelrc": false, - "plugins": [ - "relay", - "./assert-messages-plugin.js", - "chai-assert-async", - "transform-object-rest-spread", - "transform-es2015-modules-commonjs", - "transform-class-properties", - ], - "presets": [ - "react", - ], - "env": { - "coverage": { - "plugins": ["istanbul"] - } - } -} diff --git a/.babelrc.js b/.babelrc.js new file mode 100644 index 0000000000..c3189debec --- /dev/null +++ b/.babelrc.js @@ -0,0 +1,20 @@ +module.exports = { + sourceMaps: "inline", + plugins: [ + "relay", + "./assert-messages-plugin.js", + "babel-plugin-chai-assert-async", + "@babel/plugin-proposal-class-properties", + ], + presets: [ + ["@babel/preset-env", { + "targets": {"electron": process.versions.electron} + }], + "@babel/preset-react" + ], + env: { + coverage: { + plugins: ["babel-plugin-istanbul"] + } + } +} diff --git a/package.json b/package.json index 18d3a4eee7..3a9b08d1c9 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "options": { "cacheKeyFiles": [ "package.json", - ".babelrc", + ".babelrc.js", "assert-messages-plugin.js", "graphql/schema.graphql", ".nycrc.json" From 262d33c3a30232ead302588123f4e12594889bba Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 25 Feb 2019 15:02:51 -0500 Subject: [PATCH 2103/4053] Update Babel package imports to use "@babel/..." scope --- assert-messages-plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assert-messages-plugin.js b/assert-messages-plugin.js index bed581e1e6..6387d36ca6 100644 --- a/assert-messages-plugin.js +++ b/assert-messages-plugin.js @@ -1,4 +1,4 @@ -const generate = require('babel-generator').default; +const generate = require('@babel/generator').default; module.exports = function({types: t}) { return { From ac1d1b0e409e1e27f4e4373baade9ee9a4c6e17b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 25 Feb 2019 15:03:15 -0500 Subject: [PATCH 2104/4053] Update Relay two major versions at once because fuck it, that's why --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3a9b08d1c9..1b09706fbe 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@babel/preset-env": "7.3.4", "@babel/preset-react": "7.0.0", "babel-plugin-chai-assert-async": "0.1.0", - "babel-plugin-relay": "1.7.0", + "babel-plugin-relay": "3.0.0", "bintrees": "1.0.2", "bytes": "^3.0.0", "classnames": "2.2.6", @@ -53,7 +53,7 @@ "dugite": "^1.81.0", "event-kit": "2.5.3", "fs-extra": "4.0.3", - "graphql": "0.13.2", + "graphql": "14.1.1", "keytar": "4.4.0", "lodash.memoize": "4.1.2", "moment": "2.23.0", @@ -61,10 +61,10 @@ "prop-types": "15.7.2", "react": "16.8.2", "react-dom": "16.8.2", - "react-relay": "1.7.0", + "react-relay": "3.0.0", "react-select": "1.2.1", "react-tabs": "^3.0.0", - "relay-runtime": "1.7.0", + "relay-runtime": "3.0.0", "temp": "0.9.0", "tinycolor2": "1.4.1", "tree-kill": "1.2.1", @@ -100,7 +100,7 @@ "mocha-stress": "1.0.0", "node-fetch": "2.3.0", "nyc": "13.3.0", - "relay-compiler": "1.7.0", + "relay-compiler": "3.0.0", "semver": "5.6.0", "sinon": "7.2.4", "test-until": "1.1.1" From 2fb26cafe14807b4b813dfb45279843fed32ccb4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 25 Feb 2019 15:03:29 -0500 Subject: [PATCH 2105/4053] :lock: Bring package-lock.json up to date --- package-lock.json | 3087 ++++++++++++++++++++++++--------------------- 1 file changed, 1644 insertions(+), 1443 deletions(-) diff --git a/package-lock.json b/package-lock.json index fe14a40a37..22b705408d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,47 +10,291 @@ "integrity": "sha1-nK+xca+CMpSQNTtIFvAzR6oVCjA=", "dev": true }, + "@atom/babel7-transpiler": { + "version": "1.0.0-1", + "resolved": "https://registry.npmjs.org/@atom/babel7-transpiler/-/babel7-transpiler-1.0.0-1.tgz", + "integrity": "sha512-9M11+CLgifczOlh/j7R9VyOx7YVMeAPexAnxQJAhjqeg4XYgmFoAdBGIyZNuDq5nK4XWi3E11mJgdkF+u6gy2w==", + "requires": { + "@babel/core": "7.x" + } + }, "@babel/code-frame": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, "requires": { "@babel/highlight": "^7.0.0" } }, + "@babel/core": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.3.4.tgz", + "integrity": "sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.3.4", + "@babel/helpers": "^7.2.0", + "@babel/parser": "^7.3.4", + "@babel/template": "^7.2.2", + "@babel/traverse": "^7.3.4", + "@babel/types": "^7.3.4", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/parser": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", + "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==" + }, + "@babel/traverse": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", + "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.3.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.3.4", + "@babel/types": "^7.3.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "@babel/types": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", + "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, "@babel/generator": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.2.tgz", - "integrity": "sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==", - "dev": true, + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.4.tgz", + "integrity": "sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==", "requires": { - "@babel/types": "^7.2.2", + "@babel/types": "^7.3.4", "jsesc": "^2.5.1", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "source-map": "^0.5.0", "trim-right": "^1.0.1" }, "dependencies": { - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "@babel/types": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", + "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } }, "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + } + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", + "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", + "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-builder-react-jsx": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz", + "integrity": "sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==", + "requires": { + "@babel/types": "^7.3.0", + "esutils": "^2.0.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.3.tgz", + "integrity": "sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + } + } + }, + "@babel/helper-call-delegate": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz", + "integrity": "sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==", + "requires": { + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.4.tgz", + "integrity": "sha512-uFpzw6L2omjibjxa8VGZsJUPL5wJH0zzGKpoz0ccBkzIa6C8kWNUbiBmQ0rgOKWlHJ6qzmfa6lTiGchiV8SC+g==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.3.4", + "@babel/helper-split-export-declaration": "^7.0.0" + }, + "dependencies": { + "@babel/helper-replace-supers": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.3.4.tgz", + "integrity": "sha512-pvObL9WVf2ADs+ePg0jrqlhHoxRXlOa+SHRHzAXIz2xkYuOHfGl+fKxPMaS4Fq+uje8JQPobnertBBvyrWnQ1A==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.3.4", + "@babel/types": "^7.3.4" + } + }, + "@babel/parser": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", + "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==" + }, + "@babel/traverse": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", + "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.3.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.3.4", + "@babel/types": "^7.3.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "@babel/types": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", + "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "@babel/helper-define-map": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz", + "integrity": "sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.0.0", + "lodash": "^4.17.10" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" } } }, + "@babel/helper-explode-assignable-expression": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", + "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "requires": { + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, "@babel/helper-function-name": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.0.0", "@babel/template": "^7.1.0", @@ -61,25 +305,164 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, "requires": { "@babel/types": "^7.0.0" } }, + "@babel/helper-hoist-variables": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz", + "integrity": "sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", + "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.2.2.tgz", + "integrity": "sha512-YRD7I6Wsv+IHuTPkAmAS4HhY0dkPobgLftHp0cRGZSdrRvmZY8rFvae/GVu3bD00qscuvK3WPHB3YdNpBXUqrA==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/template": "^7.2.2", + "@babel/types": "^7.2.2", + "lodash": "^4.17.10" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + } + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==" + }, + "@babel/helper-regex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0.tgz", + "integrity": "sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==", + "requires": { + "lodash": "^4.17.10" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + } + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", + "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-wrap-function": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz", + "integrity": "sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.2.3", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", + "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "requires": { + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, "@babel/helper-split-export-declaration": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, "requires": { "@babel/types": "^7.0.0" } }, + "@babel/helper-wrap-function": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", + "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/template": "^7.1.0", + "@babel/traverse": "^7.1.0", + "@babel/types": "^7.2.0" + } + }, + "@babel/helpers": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.3.1.tgz", + "integrity": "sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==", + "requires": { + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.5", + "@babel/types": "^7.3.0" + }, + "dependencies": { + "@babel/types": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", + "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + } + } + }, "@babel/highlight": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, "requires": { "chalk": "^2.0.0", "esutils": "^2.0.2", @@ -90,7 +473,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -99,7 +481,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -109,14 +490,12 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -126,158 +505,157 @@ "@babel/parser": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.3.tgz", - "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==", - "dev": true + "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==" }, - "@babel/template": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", - "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", - "dev": true, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", + "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.2.2", - "@babel/types": "^7.2.2" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0", + "@babel/plugin-syntax-async-generators": "^7.2.0" } }, - "@babel/traverse": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", - "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", + "@babel/plugin-proposal-class-properties": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.4.tgz", + "integrity": "sha512-lUf8D3HLs4yYlAo8zjuneLvfxN7qfKv1Yzbj5vjqaqMJxgJA3Ipwp4VUJ+OrOdz53Wbww6ahwB8UhB2HQyLotA==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.3.4", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", + "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-json-strings": "^7.2.0" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.2.tgz", + "integrity": "sha512-DjeMS+J2+lpANkYLLO+m6GjoTMygYglKmRe6cDTbFv3L9i6mmiE8fe6B8MtCSLZpVXscD5kn7s6SgtHrDoBWoA==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.2.2", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.2.3", - "@babel/types": "^7.2.2", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.10" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "globals": { - "version": "11.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", - "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", - "dev": true - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" } }, - "@babel/types": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.2.tgz", - "integrity": "sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==", + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz", + "integrity": "sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.2.0" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", + "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.2.0.tgz", + "integrity": "sha512-UxYaGXYQ7rrKJS/PxIKRkv3exi05oH7rokBAsmCSsCxz1sVPZ7Fu6FzKoGgUvmY+0YgSkYHgUoCh5R5bCNBQlw==", "dev": true, "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "@babel/plugin-syntax-flow": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.2.0.tgz", + "integrity": "sha512-r6YMuZDWLtLlu0kqIim5o/3TNRAlWb073HwT3e2nKf9I8IIvOggPrnILYPsrrKilmn/mYEMCf/Z07w3yQJF6dg==", "dev": true, "requires": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", - "dev": true + "@babel/plugin-syntax-json-strings": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", + "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "@shiftkey/prebuild-install": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@shiftkey/prebuild-install/-/prebuild-install-5.2.4.tgz", - "integrity": "sha512-42L/pSGD/+diCg8SwhZaXjDlkAWV10u42UozyG7rqDdyPW7HDp2/j/RYRZ3x0sXFf7hAUtLYvI9HdACWdjyfVw==", + "@babel/plugin-syntax-jsx": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz", + "integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==", "requires": { - "detect-libc": "^1.0.3", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "napi-build-utils": "^1.0.1", - "node-abi": "^2.2.0", - "noop-logger": "^0.1.1", - "npmlog": "^4.0.1", - "os-homedir": "^1.0.1", - "pump": "^2.0.1", - "rc": "^1.2.7", - "simple-get": "^2.7.0", - "tar-fs": "^1.13.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" - }, - "dependencies": { - "expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" - } + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@sinonjs/commons": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", - "integrity": "sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA==", - "dev": true, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", "requires": { - "type-detect": "4.0.8" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@sinonjs/formatio": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz", - "integrity": "sha512-ZAR2bPHOl4Xg6eklUGpsdiIJ4+J1SNag1DHHrG/73Uz/nVwXqjgUtRPLoS+aVyieN9cSbc0E4LsU984tWcDyNg==", - "dev": true, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", + "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", "requires": { - "@sinonjs/samsam": "^2 || ^3" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "@sinonjs/samsam": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.1.1.tgz", - "integrity": "sha512-ILlwvQUwAiaVBzr3qz8oT1moM7AIUHqUc2UmEjQcH9lLe+E+BZPwUMuc9FFojMswRK4r96x5zDTTrowMLw/vuA==", + "@babel/plugin-transform-arrow-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", + "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.3.4.tgz", + "integrity": "sha512-Y7nCzv2fw/jEZ9f678MuKdMo99MFDJMT/PvD9LisrR5JDFcJH6vYeH6RnjVt3p5tceyGRvTtEN0VOlU+rgHZjA==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-remap-async-to-generator": "^7.1.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", + "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz", + "integrity": "sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q==", "dev": true, "requires": { - "@sinonjs/commons": "^1.0.2", - "array-from": "^2.1.1", - "lodash": "^4.17.11" + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.10" }, "dependencies": { "lodash": { @@ -288,1306 +666,1109 @@ } } }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, - "@smashwilson/atom-mocha-test-runner": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@smashwilson/atom-mocha-test-runner/-/atom-mocha-test-runner-1.4.0.tgz", - "integrity": "sha512-Zp50XTy2QZEk53PUxXQ1kLTAkSwEuM2X7JXtMGLRWuU68piFghkXGaopTrjXK3CwgzmmFi26m65sTCrXg3zqbg==", + "@babel/plugin-transform-classes": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.3.3.tgz", + "integrity": "sha512-n0CLbsg7KOXsMF4tSTLCApNMoXk0wOPb0DYfsOO1e7SfIb9gOyfbpKI2MZ+AXfqvlfzq2qsflJ1nEns48Caf2w==", "dev": true, "requires": { - "diff": "3.5.0", - "etch": "^0.8.0", - "grim": "^2.0.1", - "less": "^3.7.1", - "mocha": "^5.2.0", - "tmp": "0.0.31" + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.1.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", + "dev": true + } } }, - "@types/node": { - "version": "10.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.12.tgz", - "integrity": "sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==", - "dev": true + "@babel/plugin-transform-computed-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", + "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "abstract-leveldown": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz", - "integrity": "sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A==", - "dev": true, + "@babel/plugin-transform-destructuring": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.3.2.tgz", + "integrity": "sha512-Lrj/u53Ufqxl/sGxyjsJ2XNtNuEjDyjpqdhMNh5aZ+XFOdThL46KBj27Uem4ggoezSYBxKWAil6Hu8HtwqesYw==", "requires": { - "xtend": "~4.0.0" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "acorn": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", - "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", - "dev": true + "@babel/plugin-transform-dotall-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz", + "integrity": "sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" + } }, - "acorn-jsx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", - "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", - "dev": true + "@babel/plugin-transform-duplicate-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz", + "integrity": "sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", - "dev": true, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", + "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", "requires": { - "es6-promisify": "^5.0.0" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "@babel/plugin-transform-flow-strip-types": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.2.3.tgz", + "integrity": "sha512-xnt7UIk9GYZRitqCnsVMjQK1O2eKZwFB3CvvHjf5SGx6K6vr/MScCKQDnf1DxRaj501e3pXjti+inbSXX2ZUoQ==", "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.2.0" } }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true + "@babel/plugin-transform-for-of": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz", + "integrity": "sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "@babel/plugin-transform-function-name": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz", + "integrity": "sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ==", + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + "@babel/plugin-transform-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", + "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" + "@babel/plugin-transform-member-expression-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", + "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "@babel/plugin-transform-modules-amd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz", + "integrity": "sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==", "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "@babel/plugin-transform-modules-commonjs": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz", + "integrity": "sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ==", "requires": { - "sprintf-js": "~1.0.2" + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-simple-access": "^7.1.0" } }, - "argv": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz", - "integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=", - "dev": true + "@babel/plugin-transform-modules-systemjs": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.3.4.tgz", + "integrity": "sha512-VZ4+jlGOF36S7TjKs8g4ojp4MEI+ebCQZdswWb/T9I4X84j8OtFAyjXjt/M16iIm5RIZn0UMQgg/VgIwo/87vw==", + "requires": { + "@babel/helper-hoist-variables": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", - "dev": true, + "@babel/plugin-transform-modules-umd": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", + "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", "requires": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" + "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.3.0.tgz", + "integrity": "sha512-NxIoNVhk9ZxS+9lSoAQ/LM0V2UEvARLttEHUrRDGKFaAxOYQcrkN/nLRE+BbbicCAvZPl7wMP0X60HsHE5DtQw==", + "requires": { + "regexp-tree": "^0.1.0" + } }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, - "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", - "dev": true, + "@babel/plugin-transform-new-target": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz", + "integrity": "sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw==", "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, + "@babel/plugin-transform-object-super": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", + "integrity": "sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==", "requires": { - "array-uniq": "^1.0.1" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.1.0" } }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "array.prototype.find": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz", - "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", - "dev": true, + "@babel/plugin-transform-parameters": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.3.3.tgz", + "integrity": "sha512-IrIP25VvXWu/VlBWTpsjGptpomtIkYrN/3aDp4UKm7xK6UxZY88kcJ1UwETbzHAlwN21MnNfwlar0u8y3KpiXw==", "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "@babel/helper-call-delegate": "^7.1.0", + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" } }, - "array.prototype.flat": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz", - "integrity": "sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==", + "@babel/plugin-transform-property-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", + "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.10.0", - "function-bind": "^1.1.1" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "ast-types": { - "version": "0.6.16", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.6.16.tgz", - "integrity": "sha1-BCBbcu3dGVqP6qCB8R0ClKJN7ZM=", - "dev": true + "@babel/plugin-transform-react-display-name": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", + "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", - "dev": true + "@babel/plugin-transform-react-jsx": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", + "integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==", + "requires": { + "@babel/helper-builder-react-jsx": "^7.3.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0" + } }, - "ast-util": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/ast-util/-/ast-util-0.6.0.tgz", - "integrity": "sha1-DZE9BPDpgx5T+ZkdyZAJ4tp3SBA=", - "dev": true, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz", + "integrity": "sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==", "requires": { - "ast-types": "~0.6.7", - "private": "~0.1.6" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0" } }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true + "@babel/plugin-transform-react-jsx-source": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.2.0.tgz", + "integrity": "sha512-A32OkKTp4i5U6aE88GwwcuV4HAprUgHcTq0sSafLxjr6AW0QahrCRCjxogkbbcdtpbXkuTOlgpjophCxb6sh5g==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0" + } }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "@babel/plugin-transform-regenerator": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.3.4.tgz", + "integrity": "sha512-hvJg8EReQvXT6G9H2MvNPXkv9zK36Vxa1+csAVTpE1J3j0zlHplw76uudEbJxgvqZzAq9Yh45FLD4pk5mKRFQA==", + "requires": { + "regenerator-transform": "^0.13.4" + } }, - "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", - "dev": true + "@babel/plugin-transform-shorthand-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", + "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "atom-babel6-transpiler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/atom-babel6-transpiler/-/atom-babel6-transpiler-1.2.0.tgz", - "integrity": "sha512-lZucrjVyRtPAPPJxvICCEBsAC1qn48wUHaIlieriWCXTXLqtLC2PvkQU7vNvU2w1eZ7tw9m0lojZ8PbpVyWTvg==", + "@babel/plugin-transform-spread": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", + "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", "requires": { - "babel-core": "6.x" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "@babel/plugin-transform-sticky-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", + "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0" + } }, - "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha1-1NDpudv8p3vwjusKikcVUP454ok=", - "dev": true + "@babel/plugin-transform-template-literals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz", + "integrity": "sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0" + } }, - "axobject-query": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", - "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", - "dev": true, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", + "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", "requires": { - "ast-types-flow": "0.0.7" + "@babel/helper-plugin-utils": "^7.0.0" } }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "@babel/plugin-transform-unicode-regex": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz", + "integrity": "sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA==", "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-regex": "^7.0.0", + "regexpu-core": "^4.1.3" } }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha1-suLwnjQtDwyI4vAuBneUEl51wgc=", + "@babel/polyfill": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.2.5.tgz", + "integrity": "sha512-8Y/t3MWThtMLYr0YNC/Q76tqN1w30+b0uQMeFUYauG2UGTR19zyUtFrAzT23zNtBxPp+LbE5E/nwV/q/r3y6ug==", + "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" + "core-js": "^2.5.7", + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", + "dev": true + } + } + }, + "@babel/preset-env": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.3.4.tgz", + "integrity": "sha512-2mwqfYMK8weA0g0uBKOt4FE3iEodiHy9/CW0b+nWXcbL+pGzLx8ESYc+j9IIxr6LTDHWKgPm71i9smo02bw+gA==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-async-generator-functions": "^7.2.0", + "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-object-rest-spread": "^7.3.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.2.0", + "@babel/plugin-syntax-async-generators": "^7.2.0", + "@babel/plugin-syntax-json-strings": "^7.2.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.2.0", + "@babel/plugin-transform-async-to-generator": "^7.3.4", + "@babel/plugin-transform-block-scoped-functions": "^7.2.0", + "@babel/plugin-transform-block-scoping": "^7.3.4", + "@babel/plugin-transform-classes": "^7.3.4", + "@babel/plugin-transform-computed-properties": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.2.0", + "@babel/plugin-transform-dotall-regex": "^7.2.0", + "@babel/plugin-transform-duplicate-keys": "^7.2.0", + "@babel/plugin-transform-exponentiation-operator": "^7.2.0", + "@babel/plugin-transform-for-of": "^7.2.0", + "@babel/plugin-transform-function-name": "^7.2.0", + "@babel/plugin-transform-literals": "^7.2.0", + "@babel/plugin-transform-modules-amd": "^7.2.0", + "@babel/plugin-transform-modules-commonjs": "^7.2.0", + "@babel/plugin-transform-modules-systemjs": "^7.3.4", + "@babel/plugin-transform-modules-umd": "^7.2.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.3.0", + "@babel/plugin-transform-new-target": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.2.0", + "@babel/plugin-transform-parameters": "^7.2.0", + "@babel/plugin-transform-regenerator": "^7.3.4", + "@babel/plugin-transform-shorthand-properties": "^7.2.0", + "@babel/plugin-transform-spread": "^7.2.0", + "@babel/plugin-transform-sticky-regex": "^7.2.0", + "@babel/plugin-transform-template-literals": "^7.2.0", + "@babel/plugin-transform-typeof-symbol": "^7.2.0", + "@babel/plugin-transform-unicode-regex": "^7.2.0", + "browserslist": "^4.3.4", + "invariant": "^2.2.2", + "js-levenshtein": "^1.1.3", + "semver": "^5.3.0" }, "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "@babel/helper-replace-supers": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.3.4.tgz", + "integrity": "sha512-pvObL9WVf2ADs+ePg0jrqlhHoxRXlOa+SHRHzAXIz2xkYuOHfGl+fKxPMaS4Fq+uje8JQPobnertBBvyrWnQ1A==", "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.3.4", + "@babel/types": "^7.3.4" } }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "@babel/parser": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", + "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==" + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.4.tgz", + "integrity": "sha512-j7VQmbbkA+qrzNqbKHrBsW3ddFnOeva6wzSe/zB7T+xaxGc+RCpwo44wCmRixAIGRoIpmVgvzFzNJqQcO3/9RA==", "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.2.0" } }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "@babel/plugin-transform-block-scoping": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.3.4.tgz", + "integrity": "sha512-blRr2O8IOZLAOJklXLV4WhcEzpYafYQKSGT3+R26lWG41u/FODJuBggehtOwilVAcFu393v3OFj+HmaE6tVjhA==", "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" + "@babel/helper-plugin-utils": "^7.0.0", + "lodash": "^4.17.11" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.3.4.tgz", + "integrity": "sha512-J9fAvCFBkXEvBimgYxCjvaVDzL6thk0j0dBvCeZmIUDBwyt+nv6HfbImsSrWsYXfDNDivyANgJlFXDUWRTZBuA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-define-map": "^7.1.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.3.4", + "@babel/helper-split-export-declaration": "^7.0.0", + "globals": "^11.1.0" + } + }, + "@babel/traverse": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", + "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.3.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.3.4", + "@babel/types": "^7.3.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" } }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "@babel/types": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", + "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", "requires": { - "babel-runtime": "^6.26.0", "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" } }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" } } }, - "babel-eslint": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", - "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", - "dev": true, + "@babel/preset-react": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", + "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==", "requires": { - "babel-code-frame": "^6.22.0", - "babel-traverse": "^6.23.1", - "babel-types": "^6.23.0", - "babylon": "^6.17.0" + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0" } }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha1-GERAjTuPDTWkBOp6wYDwh6YBvZA=", + "@babel/runtime": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz", + "integrity": "sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==", "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" + "regenerator-runtime": "^0.12.0" }, "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" } } }, - "babel-helper-builder-react-jsx": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", - "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", + "@babel/template": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "esutils": "^2.0.2" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" - } + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.2.2", + "@babel/types": "^7.2.2" } }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-define-map": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", - "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", - "dev": true, + "@babel/traverse": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", + "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.2.2", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.2.3", + "@babel/types": "^7.2.2", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.10" }, "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "ms": "^2.1.1" } }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } + "globals": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", + "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==" }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" } } }, - "babel-helper-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "requires": { - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "dev": true, + "@babel/types": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.2.tgz", + "integrity": "sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==", "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "esutils": "^2.0.2", + "lodash": "^4.17.10", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + } } }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" } }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "dev": true, - "requires": { - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "@shiftkey/prebuild-install": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@shiftkey/prebuild-install/-/prebuild-install-5.2.4.tgz", + "integrity": "sha512-42L/pSGD/+diCg8SwhZaXjDlkAWV10u42UozyG7rqDdyPW7HDp2/j/RYRZ3x0sXFf7hAUtLYvI9HdACWdjyfVw==", "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.2.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "os-homedir": "^1.0.1", + "pump": "^2.0.1", + "rc": "^1.2.7", + "simple-get": "^2.7.0", + "tar-fs": "^1.13.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + }, + "dependencies": { + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + } } }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "@sinonjs/commons": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", + "integrity": "sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA==", + "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "type-detect": "4.0.8" } }, - "babel-plugin-chai-assert-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-chai-assert-async/-/babel-plugin-chai-assert-async-0.1.0.tgz", - "integrity": "sha1-pJMXc79WPcKt3mzXm28ZRknr/SU=" - }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "@sinonjs/formatio": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz", + "integrity": "sha512-ZAR2bPHOl4Xg6eklUGpsdiIJ4+J1SNag1DHHrG/73Uz/nVwXqjgUtRPLoS+aVyieN9cSbc0E4LsU984tWcDyNg==", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "@sinonjs/samsam": "^2 || ^3" } }, - "babel-plugin-istanbul": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.1.tgz", - "integrity": "sha512-RNNVv2lsHAXJQsEJ5jonQwrJVWK8AcZpG1oxhnjCUaAjL7xahYLANhPUZbzEQHjKy1NMYUwn+0NPKQc8iSY4xQ==", + "@sinonjs/samsam": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.1.1.tgz", + "integrity": "sha512-ILlwvQUwAiaVBzr3qz8oT1moM7AIUHqUc2UmEjQcH9lLe+E+BZPwUMuc9FFojMswRK4r96x5zDTTrowMLw/vuA==", "dev": true, "requires": { - "find-up": "^3.0.0", - "istanbul-lib-instrument": "^3.0.0", - "test-exclude": "^5.0.0" + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" }, "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true } } }, - "babel-plugin-macros": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.4.5.tgz", - "integrity": "sha512-+/9yteNQw3yuZ3krQUfjAeoT/f4EAdn3ELwhFfDj0rTMIaoHfIdrcLePOfIaL0qmFLpIcgPIL2Lzm58h+CGWaw==", - "requires": { - "cosmiconfig": "^5.0.5", - "resolve": "^1.8.1" - } + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true }, - "babel-plugin-relay": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/babel-plugin-relay/-/babel-plugin-relay-1.7.0.tgz", - "integrity": "sha512-4kDgElsQ3+m1YHGinm2CWu55XzpPqEzf42JuYWUAJWvTBcHkd/VGVftO9C6BjnssUU7fDH9izn3qMtp0XFWGKw==", + "@smashwilson/atom-mocha-test-runner": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@smashwilson/atom-mocha-test-runner/-/atom-mocha-test-runner-1.4.0.tgz", + "integrity": "sha512-Zp50XTy2QZEk53PUxXQ1kLTAkSwEuM2X7JXtMGLRWuU68piFghkXGaopTrjXK3CwgzmmFi26m65sTCrXg3zqbg==", + "dev": true, "requires": { - "babel-plugin-macros": "^2.0.0", - "babel-runtime": "^6.23.0", - "babel-types": "^6.24.1" + "diff": "3.5.0", + "etch": "^0.8.0", + "grim": "^2.0.1", + "less": "^3.7.1", + "mocha": "^5.2.0", + "tmp": "0.0.31" } }, - "babel-plugin-syntax-class-properties": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", - "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=" - }, - "babel-plugin-syntax-flow": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", - "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=" - }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=" - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "@types/node": { + "version": "10.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.12.tgz", + "integrity": "sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==", "dev": true }, - "babel-plugin-transform-class-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", - "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-plugin-syntax-class-properties": "^6.8.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "abstract-leveldown": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz", + "integrity": "sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A==", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "xtend": "~4.0.0" } }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "acorn": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", + "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true + }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "es6-promisify": "^5.0.0" } }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", - "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - } + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", - "dev": true, - "requires": { - "babel-helper-define-map": "^6.24.1", - "babel-helper-function-name": "^6.24.1", - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-helper-replace-supers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "dev": true, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "requires": { - "babel-runtime": "^6.22.0" + "sprintf-js": "~1.0.2" } }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", - "integrity": "sha1-WKeThjqefKhwvcWogRF/+sJ9tvM=", - "requires": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-types": "^6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" - } - } + "argv": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz", + "integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=", + "dev": true }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", "dev": true, "requires": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" } }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", - "dev": true, - "requires": { - "babel-helper-call-delegate": "^6.24.1", - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "array-uniq": "^1.0.1" } }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "array.prototype.find": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz", + "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, - "babel-plugin-transform-es3-member-expression-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es3-member-expression-literals/-/babel-plugin-transform-es3-member-expression-literals-6.22.0.tgz", - "integrity": "sha1-cz00RPPsxBvvjtGmpOCWV7iWnrs=", + "array.prototype.flat": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz", + "integrity": "sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.10.0", + "function-bind": "^1.1.1" } }, - "babel-plugin-transform-es3-property-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es3-property-literals/-/babel-plugin-transform-es3-property-literals-6.22.0.tgz", - "integrity": "sha1-sgeNWELiKr9A9z6M3pzTcRq9V1g=", + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "ast-types": { + "version": "0.6.16", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.6.16.tgz", + "integrity": "sha1-BCBbcu3dGVqP6qCB8R0ClKJN7ZM=", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "ast-util": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/ast-util/-/ast-util-0.6.0.tgz", + "integrity": "sha1-DZE9BPDpgx5T+ZkdyZAJ4tp3SBA=", "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "ast-types": "~0.6.7", + "private": "~0.1.6" } }, - "babel-plugin-transform-flow-strip-types": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", - "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", - "requires": { - "babel-plugin-syntax-flow": "^6.18.0", - "babel-runtime": "^6.22.0" - } + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true }, - "babel-plugin-transform-object-rest-spread": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", - "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.8.0", - "babel-runtime": "^6.26.0" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - } - } + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "babel-plugin-transform-react-display-name": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", - "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", + "atob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha1-1NDpudv8p3vwjusKikcVUP454ok=", + "dev": true + }, + "axobject-query": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", + "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", + "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "ast-types-flow": "0.0.7" } }, - "babel-plugin-transform-react-jsx": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", - "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, "requires": { - "babel-helper-builder-react-jsx": "^6.24.1", - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" } }, - "babel-plugin-transform-react-jsx-self": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", - "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", + "babel-eslint": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", + "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", + "dev": true, "requires": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" + "babel-code-frame": "^6.22.0", + "babel-traverse": "^6.23.1", + "babel-types": "^6.23.0", + "babylon": "^6.17.0" } }, - "babel-plugin-transform-react-jsx-source": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", - "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, "requires": { - "babel-plugin-syntax-jsx": "^6.8.0", "babel-runtime": "^6.22.0" } }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } + "babel-plugin-chai-assert-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-chai-assert-async/-/babel-plugin-chai-assert-async-0.1.0.tgz", + "integrity": "sha1-pJMXc79WPcKt3mzXm28ZRknr/SU=" }, - "babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "babel-plugin-istanbul": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.1.tgz", + "integrity": "sha512-RNNVv2lsHAXJQsEJ5jonQwrJVWK8AcZpG1oxhnjCUaAjL7xahYLANhPUZbzEQHjKy1NMYUwn+0NPKQc8iSY4xQ==", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.0.0", + "test-exclude": "^5.0.0" }, "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true } } }, - "babel-preset-fbjs": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.2.0.tgz", - "integrity": "sha512-jj0KFJDioYZMtPtZf77dQuU+Ad/1BtN0UnAYlHDa8J8f4tGXr3YrPoJImD5MdueaOPeN/jUdrCgu330EfXr0XQ==", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "^6.8.0", - "babel-plugin-syntax-class-properties": "^6.8.0", - "babel-plugin-syntax-flow": "^6.8.0", - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-plugin-syntax-object-rest-spread": "^6.8.0", - "babel-plugin-syntax-trailing-function-commas": "^6.8.0", - "babel-plugin-transform-class-properties": "^6.8.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.8.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.8.0", - "babel-plugin-transform-es2015-block-scoping": "^6.8.0", - "babel-plugin-transform-es2015-classes": "^6.8.0", - "babel-plugin-transform-es2015-computed-properties": "^6.8.0", - "babel-plugin-transform-es2015-destructuring": "^6.8.0", - "babel-plugin-transform-es2015-for-of": "^6.8.0", - "babel-plugin-transform-es2015-function-name": "^6.8.0", - "babel-plugin-transform-es2015-literals": "^6.8.0", - "babel-plugin-transform-es2015-modules-commonjs": "^6.8.0", - "babel-plugin-transform-es2015-object-super": "^6.8.0", - "babel-plugin-transform-es2015-parameters": "^6.8.0", - "babel-plugin-transform-es2015-shorthand-properties": "^6.8.0", - "babel-plugin-transform-es2015-spread": "^6.8.0", - "babel-plugin-transform-es2015-template-literals": "^6.8.0", - "babel-plugin-transform-es3-member-expression-literals": "^6.8.0", - "babel-plugin-transform-es3-property-literals": "^6.8.0", - "babel-plugin-transform-flow-strip-types": "^6.8.0", - "babel-plugin-transform-object-rest-spread": "^6.8.0", - "babel-plugin-transform-react-display-name": "^6.8.0", - "babel-plugin-transform-react-jsx": "^6.8.0" - } - }, - "babel-preset-flow": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", - "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", + "babel-plugin-macros": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.5.0.tgz", + "integrity": "sha512-BWw0lD0kVZAXRD3Od1kMrdmfudqzDzYv2qrN3l2ISR1HVp1EgLKfbOrYV9xmY5k3qx3RIu5uPAUZZZHpo0o5Iw==", "requires": { - "babel-plugin-transform-flow-strip-types": "^6.22.0" + "cosmiconfig": "^5.0.5", + "resolve": "^1.8.1" } }, - "babel-preset-react": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", - "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", + "babel-plugin-relay": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-relay/-/babel-plugin-relay-3.0.0.tgz", + "integrity": "sha512-lI+PR4/H8XytNVUtz8cYYM7sR2unFMeTsBbnKFvuU/mnxxmDO3JA7OHxtTdctLjlRSYrcCVmvIEL2JDehMxlTg==", "requires": { - "babel-plugin-syntax-jsx": "^6.3.13", - "babel-plugin-transform-react-display-name": "^6.23.0", - "babel-plugin-transform-react-jsx": "^6.24.1", - "babel-plugin-transform-react-jsx-self": "^6.22.0", - "babel-plugin-transform-react-jsx-source": "^6.22.0", - "babel-preset-flow": "^6.23.0" + "babel-plugin-macros": "^2.0.0" } }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - } + "babel-plugin-syntax-trailing-function-commas": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", + "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==", + "dev": true + }, + "babel-preset-fbjs": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.2.0.tgz", + "integrity": "sha512-5Jo+JeWiVz2wHUUyAlvb/sSYnXNig9r+HqGAOSfh5Fzxp7SnAaR/tEGRJ1ZX7C77kfk82658w6R5Z+uPATTD9g==", + "dev": true, + "requires": { + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-syntax-class-properties": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-block-scoped-functions": "^7.0.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-member-expression-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-property-literals": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-template-literals": "^7.0.0", + "babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0" } }, "babel-runtime": { "version": "6.25.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=", + "dev": true, "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.10.0" } }, - "babel-template": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", - "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=", - "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.25.0", - "babel-types": "^6.25.0", - "babylon": "^6.17.2", - "lodash": "^4.2.0" - } - }, "babel-traverse": { "version": "6.25.0", "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=", + "dev": true, "requires": { "babel-code-frame": "^6.22.0", "babel-messages": "^6.23.0", @@ -1604,6 +1785,7 @@ "version": "6.25.0", "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=", + "dev": true, "requires": { "babel-runtime": "^6.22.0", "esutils": "^2.0.2", @@ -1614,14 +1796,16 @@ "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true } } }, "babylon": { "version": "6.17.4", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz", - "integrity": "sha512-kChlV+0SXkjE0vUn9OZ7pBMWRFd8uq3mZe8x1K6jhuNcAFAtEnjchFAqB+dYEXKyd+JpT6eppRR78QAr5gTsUw==" + "integrity": "sha512-kChlV+0SXkjE0vUn9OZ7pBMWRFd8uq3mZe8x1K6jhuNcAFAtEnjchFAqB+dYEXKyd+JpT6eppRR78QAr5gTsUw==", + "dev": true }, "balanced-match": { "version": "1.0.0", @@ -1797,6 +1981,16 @@ "integrity": "sha1-uqVZ7hTO1zRSIputcyZGfGH6vWA=", "dev": true }, + "browserslist": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.2.tgz", + "integrity": "sha512-ISS/AIAiHERJ3d45Fz0AVYKkgcy+F/eJHzKEvv1j0wwKGKD9T3BrwKr/5g45L+Y4XIK5PlTqefHciRFcfE1Jxg==", + "requires": { + "caniuse-lite": "^1.0.30000939", + "electron-to-chromium": "^1.3.113", + "node-releases": "^1.1.8" + } + }, "bser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", @@ -1924,6 +2118,11 @@ "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=", "dev": true }, + "caniuse-lite": { + "version": "1.0.30000939", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000939.tgz", + "integrity": "sha512-oXB23ImDJOgQpGjRv1tCtzAvJr4/OvrHi5SO2vUgB0g0xpdZZoA/BxfImiWfdwoYdUTtQrPsXsvYU/dmCSM8gg==" + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -1956,6 +2155,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -2120,7 +2320,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -2128,8 +2327,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "0.5.1", @@ -2185,9 +2383,12 @@ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "requires": { + "safe-buffer": "~5.1.1" + } }, "copy-descriptor": { "version": "0.1.1", @@ -2206,13 +2407,14 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz", - "integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.1.0.tgz", + "integrity": "sha512-kCNPvthka8gvLtzAxQXvWo4FxqRB+ftRZyPZNuab5ngvM9Y7yw7hbEysglptLgpkGX9nAOKTBVkHUAe8xtYR6Q==", "requires": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", "js-yaml": "^3.9.0", + "lodash.get": "^4.4.2", "parse-json": "^4.0.0" }, "dependencies": { @@ -2275,12 +2477,6 @@ "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", "which": "^1.2.9" - }, - "dependencies": { - "yallist": { - "version": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - } } }, "cross-unzip": { @@ -2340,6 +2536,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, "requires": { "ms": "2.0.0" } @@ -2467,14 +2664,6 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "requires": { - "repeating": "^2.0.0" - } - }, "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -2812,6 +3001,11 @@ "extract-zip": "^1.6.5" } }, + "electron-to-chromium": { + "version": "1.3.113", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz", + "integrity": "sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==" + }, "emoji-regex": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", @@ -3589,26 +3783,25 @@ } }, "fbjs": { - "version": "0.8.17", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", - "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-1.0.0.tgz", + "integrity": "sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA==", "requires": { - "core-js": "^1.0.0", + "core-js": "^2.4.1", + "fbjs-css-vars": "^1.0.0", "isomorphic-fetch": "^2.1.1", "loose-envify": "^1.0.0", "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", "ua-parser-js": "^0.7.18" - }, - "dependencies": { - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - } } }, + "fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" + }, "fbjs-eslint-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fbjs-eslint-utils/-/fbjs-eslint-utils-1.0.0.tgz", @@ -3831,9 +4024,9 @@ } }, "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, "get-func-name": { @@ -3926,7 +4119,8 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=" + "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", + "dev": true }, "globby": { "version": "8.0.2", @@ -3963,22 +4157,11 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "graphql": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.13.2.tgz", - "integrity": "sha1-THQK48Iigj5wBAlvgy57k7IQgnA=", - "requires": { - "iterall": "^1.2.1" - } - }, - "graphql-compiler": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/graphql-compiler/-/graphql-compiler-1.7.0.tgz", - "integrity": "sha512-jC4PBo1ikFRiA2CLclNxMMFJTwhxPam1HxzIxJgWWHm26AXw0ZsN41a2t6vGfw7NxXCfnef4JCIHWFMgIm8iOQ==", - "dev": true, + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.1.1.tgz", + "integrity": "sha512-C5zDzLqvfPAgTtP8AUPIt9keDabrdRAqSWjj2OPRKrKxI9Fb65I36s1uCs1UUBFnSWTdO7hyHi7z1ZbwKMKF6Q==", "requires": { - "chalk": "^1.1.1", - "fb-watchman": "^2.0.0", - "immutable": "~3.7.6" + "iterall": "^1.2.2" } }, "grim": { @@ -4024,6 +4207,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4031,8 +4215,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.0", @@ -4093,15 +4276,6 @@ "url-equal": "0.1.2-1" } }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, "hosted-git-info": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", @@ -4182,9 +4356,12 @@ } }, "iconv-lite": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "ignore": { "version": "4.0.6", @@ -4469,6 +4646,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4668,7 +4846,12 @@ "iterall": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", - "integrity": "sha1-ktcN64Ao4MOf8xZP2/TYsIgTDNc=" + "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==" + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" }, "js-tokens": { "version": "3.0.2", @@ -4691,9 +4874,9 @@ "optional": true }, "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "json-parse-better-errors": { "version": "1.0.2", @@ -4723,9 +4906,12 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "requires": { + "minimist": "^1.2.0" + } }, "jsonfile": { "version": "4.0.0", @@ -4925,7 +5111,8 @@ "lodash": { "version": "4.17.5", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha1-maktZcAnLevoyWtgV7yPv6O+1RE=" + "integrity": "sha1-maktZcAnLevoyWtgV7yPv6O+1RE=", + "dev": true }, "lodash.escape": { "version": "4.0.1", @@ -4939,6 +5126,11 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -4986,9 +5178,9 @@ } }, "lru-cache": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", - "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { "pseudomap": "^1.0.2", @@ -5387,7 +5579,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "mute-stream": { "version": "0.0.7", @@ -5515,6 +5708,14 @@ "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", "dev": true }, + "node-releases": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.8.tgz", + "integrity": "sha512-gQm+K9mGCiT/NXHy+V/ZZS1N/LOaGGqRAAJJs3X9Ah1g+CIbRcBgNyoNYQ+SEtcyAtB9KqDruu+fF7nWjsqRaA==", + "requires": { + "semver": "^5.3.0" + } + }, "nomnom": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz", @@ -5586,6 +5787,11 @@ "throttleit": "0.0.2" } }, + "nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -6809,7 +7015,7 @@ "os-locale": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha1-QrwpAKa1uL0XN2yOiCtlr8zyS/I=", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", "dev": true, "requires": { "execa": "^0.7.0", @@ -6820,7 +7026,8 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true }, "p-finally": { "version": "1.0.0", @@ -6829,9 +7036,9 @@ "dev": true }, "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha1-DpK2vty1nwIsE9DxlJ3ILRWQnxw=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { "p-try": "^1.0.0" @@ -7210,14 +7417,15 @@ "dev": true }, "react-relay": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/react-relay/-/react-relay-1.7.0.tgz", - "integrity": "sha512-vZOs1iK6LxqeaAelwSuD5eVXnQux5eVIrik/kxKt6Y3j6ylrjrdTadmgO6sapGc0TG61VtFK5CKPOtW+XSNotg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-relay/-/react-relay-3.0.0.tgz", + "integrity": "sha512-HZQ1CXhF2EBt6f5XG2nWoT5FZIn9zdoHeo8+u5rqQ3fCRBr4qmvLihfehWlCcYCcx57pnAiqsjUdJmwxGUmbsg==", "requires": { - "babel-runtime": "^6.23.0", - "fbjs": "0.8.17", + "@babel/runtime": "^7.0.0", + "fbjs": "^1.0.0", + "nullthrows": "^1.1.0", "prop-types": "^15.5.8", - "relay-runtime": "1.7.0" + "relay-runtime": "3.0.0" } }, "react-select": { @@ -7351,10 +7559,32 @@ "strip-indent": "^1.0.1" } }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" + }, + "regenerate-unicode-properties": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz", + "integrity": "sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw==", + "requires": { + "regenerate": "^1.4.0" + } + }, "regenerator-runtime": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==" + "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==", + "dev": true + }, + "regenerator-transform": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.4.tgz", + "integrity": "sha512-T0QMBjK3J0MtxjPmdIMXm72Wvj2Abb0Bd4HADdfijwMdoIsyQZ6fWC7kDFhk2YinBBEMZDL7Y7wh0J1sGx3S4A==", + "requires": { + "private": "^0.1.6" + } }, "regex-not": { "version": "1.0.2", @@ -7376,158 +7606,112 @@ } } }, + "regexp-tree": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.5.tgz", + "integrity": "sha512-nUmxvfJyAODw+0B13hj8CFVAxhe7fDEAgJgaotBu3nnR+IgGgZq59YedJP5VYTlkEfqjuK6TuRpnymKdatLZfQ==" + }, "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, + "regexpu-core": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.4.0.tgz", + "integrity": "sha512-eDDWElbwwI3K0Lo6CqbQbA6FwgtCz4kYTarrri1okfkRLZAqstU+B3voZBCjg8Fl6iq0gXrJG6MvRgLthfvgOA==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^7.0.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.0.2" + } + }, + "regjsgen": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", + "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==" + }, + "regjsparser": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", + "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } + } + }, "relay-compiler": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/relay-compiler/-/relay-compiler-1.7.0.tgz", - "integrity": "sha512-vCsgj52Up8BmYKcsjxhnXFb8sQL2iE7rZouImPk05ZpRk0uhjkizEncWHiIHSAwq8BehEK1p0G9Hfv2IhLDgMQ==", - "dev": true, - "requires": { - "@babel/generator": "7.0.0-beta.56", - "@babel/parser": "7.0.0-beta.56", - "@babel/types": "7.0.0-beta.56", - "babel-polyfill": "^6.20.0", - "babel-preset-fbjs": "2.2.0", - "babel-runtime": "^6.23.0", - "babel-traverse": "^6.26.0", - "chalk": "^1.1.1", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/relay-compiler/-/relay-compiler-3.0.0.tgz", + "integrity": "sha512-wCD1FV4IKCfeNZdNrdeRjAWl23C3hcInmBbR+rOE/w345+IoXwh1W4o152opRfwHMqPIC5YjyrWh2O73vJYGLw==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/polyfill": "^7.0.0", + "@babel/runtime": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "babel-preset-fbjs": "^3.1.2", + "chalk": "^2.4.1", "fast-glob": "^2.2.2", "fb-watchman": "^2.0.0", - "fbjs": "0.8.17", - "graphql-compiler": "1.7.0", + "fbjs": "^1.0.0", "immutable": "~3.7.6", - "relay-runtime": "1.7.0", + "nullthrows": "^1.1.0", + "relay-runtime": "3.0.0", "signedsource": "^1.0.0", "yargs": "^9.0.0" }, "dependencies": { - "@babel/generator": { - "version": "7.0.0-beta.56", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.56.tgz", - "integrity": "sha512-d+Ls/Vr5OU5FBDYQToXSqAluI3r2UaSoNZ41zD3sxdoVoaT8K5Bdh4So4eG4o//INGM7actValXGfb+5J1+r8w==", - "dev": true, - "requires": { - "@babel/types": "7.0.0-beta.56", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/parser": { - "version": "7.0.0-beta.56", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.56.tgz", - "integrity": "sha512-JM0ughhbo+sPXw2Z+SUyowfYrAOhjanzjMshcLswBdXVelJCOeEKe/FqMqPWGVPQr7wByongXIn+MKdCpY7DBw==", - "dev": true - }, - "@babel/types": { - "version": "7.0.0-beta.56", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.56.tgz", - "integrity": "sha512-fRIBeHtKxAD3D1E7hYSpG4MnLt0AfzHHs5gfVclOB0NlfLu3qiWU/IqdbK2ixTK61424iEkV1P/VAzndx6ungA==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" + "color-convert": "^1.9.0" } }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - } + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - } + "has-flag": "^3.0.0" } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true } } }, "relay-runtime": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-1.7.0.tgz", - "integrity": "sha512-gvx01aRoLHdIMQoIjMQ79js4BR9JZVfF/SoSiLXvWOgDWEnD7RKb80zmCZTByCpka0GwFzkVwBWUy1gW6g0zlQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/relay-runtime/-/relay-runtime-3.0.0.tgz", + "integrity": "sha512-P9pDoAaqku9m5MTMjampwo+0vsNd2Nv8x78GpWuxPxvqJusqz8MBpu0RVBViIpLzCn77Pegw2ihtXgQSBdvs0w==", "requires": { - "babel-runtime": "^6.23.0", - "fbjs": "0.8.17" + "@babel/runtime": "^7.0.0", + "fbjs": "^1.0.0" } }, "repeat-element": { @@ -7546,6 +7730,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, "requires": { "is-finite": "^1.0.0" } @@ -7696,8 +7881,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "scheduler": { "version": "0.11.3", @@ -7839,7 +8023,8 @@ "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true }, "slice-ansi": { "version": "2.1.0", @@ -7994,14 +8179,6 @@ "urix": "^0.1.0" } }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha1-Aoam3ovkJkEzhZTpfM6nXwosWF8=", - "requires": { - "source-map": "^0.5.6" - } - }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", @@ -8214,7 +8391,8 @@ "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true }, "table": { "version": "5.2.3", @@ -8592,8 +8770,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-object-path": { "version": "0.3.0", @@ -8739,6 +8916,30 @@ } } }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz", + "integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz", + "integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==" + }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -8939,9 +9140,9 @@ } }, "whatwg-fetch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", - "integrity": "sha1-3eal3zFfnTmZGqF2IYU9cguFVm8=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" }, "which": { "version": "1.3.0", From 316a02c0b3b22b4d165fd5c09e3d055d1fa13d12 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 25 Feb 2019 15:06:11 -0500 Subject: [PATCH 2106/4053] Recompile generated Relay files --- ...urrentPullRequestContainerQuery.graphql.js | 52 +-- .../issueishDetailContainerQuery.graphql.js | 344 +++++++++--------- .../issueishSearchContainerQuery.graphql.js | 44 +-- ...sueishTooltipContainer_resource.graphql.js | 12 +- .../prReviewCommentsContainerQuery.graphql.js | 40 +- ...rReviewCommentsContainer_review.graphql.js | 10 +- .../prReviewsContainerQuery.graphql.js | 78 ++-- .../prReviewsContainer_pullRequest.graphql.js | 4 +- .../remoteContainerQuery.graphql.js | 36 +- ...ooltipContainer_repositoryOwner.graphql.js | 8 +- .../issueTimelineControllerQuery.graphql.js | 96 ++--- .../issueTimelineController_issue.graphql.js | 4 +- ...eishDetailController_repository.graphql.js | 36 +- .../issueishListController_results.graphql.js | 4 +- .../prTimelineControllerQuery.graphql.js | 146 ++++---- ...rTimelineController_pullRequest.graphql.js | 4 +- .../issueishTooltipItemQuery.graphql.js | 44 +-- .../userMentionTooltipItemQuery.graphql.js | 26 +- .../issueDetailViewRefetchQuery.graphql.js | 138 +++---- .../issueDetailView_issue.graphql.js | 12 +- .../issueDetailView_repository.graphql.js | 4 +- .../prCommitView_item.graphql.js | 4 +- .../prCommitsViewQuery.graphql.js | 40 +- .../prCommitsView_pullRequest.graphql.js | 4 +- .../prDetailViewRefetchQuery.graphql.js | 302 +++++++-------- .../prDetailView_pullRequest.graphql.js | 16 +- .../prDetailView_repository.graphql.js | 4 +- .../prStatusContextView_context.graphql.js | 4 +- .../prStatusesViewRefetchQuery.graphql.js | 36 +- .../prStatusesView_pullRequest.graphql.js | 12 +- .../commitCommentThreadView_item.graphql.js | 4 +- .../commitCommentView_item.graphql.js | 4 +- .../commitView_commit.graphql.js | 8 +- .../commitsView_nodes.graphql.js | 4 +- .../crossReferencedEventView_item.graphql.js | 16 +- ...crossReferencedEventsView_nodes.graphql.js | 8 +- ...efForcePushedEventView_issueish.graphql.js | 8 +- ...eadRefForcePushedEventView_item.graphql.js | 8 +- .../issueCommentView_item.graphql.js | 4 +- .../mergedEventView_item.graphql.js | 4 +- 40 files changed, 830 insertions(+), 802 deletions(-) diff --git a/lib/containers/__generated__/currentPullRequestContainerQuery.graphql.js b/lib/containers/__generated__/currentPullRequestContainerQuery.graphql.js index a284aa3c5f..1d731a4e22 100644 --- a/lib/containers/__generated__/currentPullRequestContainerQuery.graphql.js +++ b/lib/containers/__generated__/currentPullRequestContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 5821e0667d3f593dc75d2c4ac34985ca + * @relayHash 613bbee977dd8377c2b61e1755aca140 */ /* eslint-disable */ @@ -173,24 +173,19 @@ v5 = { }; return { "kind": "Request", - "operationKind": "query", - "name": "currentPullRequestContainerQuery", - "id": null, - "text": "query currentPullRequestContainerQuery(\n $headOwner: String!\n $headName: String!\n $headRef: String!\n $first: Int!\n) {\n repository(owner: $headOwner, name: $headName) {\n ref(qualifiedName: $headRef) {\n associatedPullRequests(first: $first, states: [OPEN]) {\n totalCount\n nodes {\n ...issueishListController_results\n id\n }\n }\n id\n }\n id\n }\n}\n\nfragment issueishListController_results on PullRequest {\n number\n title\n url\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n createdAt\n headRefName\n repository {\n id\n }\n commits(last: 1) {\n nodes {\n commit {\n status {\n contexts {\n state\n id\n }\n id\n }\n id\n }\n id\n }\n }\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "currentPullRequestContainerQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "repository", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": "Repository", "plural": false, "selections": [ @@ -199,7 +194,7 @@ return { "alias": null, "name": "ref", "storageKey": null, - "args": v2, + "args": (v2/*: any*/), "concreteType": "Ref", "plural": false, "selections": [ @@ -208,11 +203,11 @@ return { "alias": null, "name": "associatedPullRequests", "storageKey": null, - "args": v3, + "args": (v3/*: any*/), "concreteType": "PullRequestConnection", "plural": false, "selections": [ - v4, + (v4/*: any*/), { "kind": "LinkedField", "alias": null, @@ -240,14 +235,14 @@ return { "operation": { "kind": "Operation", "name": "currentPullRequestContainerQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "repository", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": "Repository", "plural": false, "selections": [ @@ -256,7 +251,7 @@ return { "alias": null, "name": "ref", "storageKey": null, - "args": v2, + "args": (v2/*: any*/), "concreteType": "Ref", "plural": false, "selections": [ @@ -265,11 +260,11 @@ return { "alias": null, "name": "associatedPullRequests", "storageKey": null, - "args": v3, + "args": (v3/*: any*/), "concreteType": "PullRequestConnection", "plural": false, "selections": [ - v4, + (v4/*: any*/), { "kind": "LinkedField", "alias": null, @@ -330,7 +325,7 @@ return { "args": null, "storageKey": null }, - v5 + (v5/*: any*/) ] }, { @@ -356,7 +351,7 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v5 + (v5/*: any*/) ] }, { @@ -418,32 +413,39 @@ return { "args": null, "storageKey": null }, - v5 + (v5/*: any*/) ] }, - v5 + (v5/*: any*/) ] }, - v5 + (v5/*: any*/) ] }, - v5 + (v5/*: any*/) ] } ] }, - v5 + (v5/*: any*/) ] } ] }, - v5 + (v5/*: any*/) ] }, - v5 + (v5/*: any*/) ] } ] + }, + "params": { + "operationKind": "query", + "name": "currentPullRequestContainerQuery", + "id": null, + "text": "query currentPullRequestContainerQuery(\n $headOwner: String!\n $headName: String!\n $headRef: String!\n $first: Int!\n) {\n repository(owner: $headOwner, name: $headName) {\n ref(qualifiedName: $headRef) {\n associatedPullRequests(first: $first, states: [OPEN]) {\n totalCount\n nodes {\n ...issueishListController_results\n id\n }\n }\n id\n }\n id\n }\n}\n\nfragment issueishListController_results on PullRequest {\n number\n title\n url\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n createdAt\n headRefName\n repository {\n id\n }\n commits(last: 1) {\n nodes {\n commit {\n status {\n contexts {\n state\n id\n }\n id\n }\n id\n }\n id\n }\n }\n}\n", + "metadata": {} } }; })(); diff --git a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js index 2ec6a9595d..d374175432 100644 --- a/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishDetailContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash aa9982b03c50cc4fd52b07a4206b1eb0 + * @relayHash 0d4fc026130019bf15eb5f964be2efcf */ /* eslint-disable */ @@ -708,9 +708,9 @@ v5 = { "storageKey": null }, v6 = [ - v4, - v5, - v2 + (v4/*: any*/), + (v5/*: any*/), + (v2/*: any*/) ], v7 = { "kind": "LinkedField", @@ -720,7 +720,7 @@ v7 = { "args": null, "concreteType": null, "plural": false, - "selections": v6 + "selections": (v6/*: any*/) }, v8 = [ { @@ -773,7 +773,7 @@ v14 = { "storageKey": null }, v15 = [ - v14 + (v14/*: any*/) ], v16 = { "kind": "LinkedField", @@ -784,19 +784,19 @@ v16 = { "concreteType": null, "plural": false, "selections": [ - v4, - v5, - v13, - v2, + (v4/*: any*/), + (v5/*: any*/), + (v13/*: any*/), + (v2/*: any*/), { "kind": "InlineFragment", "type": "Bot", - "selections": v15 + "selections": (v15/*: any*/) }, { "kind": "InlineFragment", "type": "User", - "selections": v15 + "selections": (v15/*: any*/) } ] }, @@ -837,8 +837,8 @@ v20 = { "concreteType": "PageInfo", "plural": false, "selections": [ - v18, - v19 + (v18/*: any*/), + (v19/*: any*/) ] }, v21 = { @@ -856,10 +856,10 @@ v22 = { "storageKey": null }, v23 = [ - v4, - v5, - v13, - v2 + (v4/*: any*/), + (v5/*: any*/), + (v13/*: any*/), + (v2/*: any*/) ], v24 = { "kind": "InlineFragment", @@ -872,7 +872,7 @@ v24 = { "args": null, "storageKey": null }, - v22, + (v22/*: any*/), { "kind": "LinkedField", "alias": null, @@ -881,7 +881,7 @@ v24 = { "args": null, "concreteType": null, "plural": false, - "selections": v23 + "selections": (v23/*: any*/) }, { "kind": "LinkedField", @@ -892,7 +892,7 @@ v24 = { "concreteType": null, "plural": false, "selections": [ - v4, + (v4/*: any*/), { "kind": "LinkedField", "alias": null, @@ -902,9 +902,9 @@ v24 = { "concreteType": "Repository", "plural": false, "selections": [ - v3, - v7, - v2, + (v3/*: any*/), + (v7/*: any*/), + (v2/*: any*/), { "kind": "ScalarField", "alias": null, @@ -914,14 +914,14 @@ v24 = { } ] }, - v2, + (v2/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v10, - v9, - v14, + (v10/*: any*/), + (v9/*: any*/), + (v14/*: any*/), { "kind": "ScalarField", "alias": "prState", @@ -935,9 +935,9 @@ v24 = { "kind": "InlineFragment", "type": "Issue", "selections": [ - v10, - v9, - v14, + (v10/*: any*/), + (v9/*: any*/), + (v14/*: any*/), { "kind": "ScalarField", "alias": "issueState", @@ -952,10 +952,10 @@ v24 = { ] }, v25 = [ - v4, - v13, - v5, - v2 + (v4/*: any*/), + (v13/*: any*/), + (v5/*: any*/), + (v2/*: any*/) ], v26 = { "kind": "LinkedField", @@ -965,7 +965,7 @@ v26 = { "args": null, "concreteType": null, "plural": false, - "selections": v25 + "selections": (v25/*: any*/) }, v27 = { "kind": "ScalarField", @@ -978,10 +978,10 @@ v28 = { "kind": "InlineFragment", "type": "IssueComment", "selections": [ - v26, - v12, - v27, - v14 + (v26/*: any*/), + (v12/*: any*/), + (v27/*: any*/), + (v14/*: any*/) ] }, v29 = { @@ -993,8 +993,8 @@ v29 = { "concreteType": "User", "plural": false, "selections": [ - v5, - v2 + (v5/*: any*/), + (v2/*: any*/) ] }, v30 = { @@ -1017,9 +1017,9 @@ v31 = { "concreteType": "GitActor", "plural": false, "selections": [ - v3, - v29, - v13 + (v3/*: any*/), + (v29/*: any*/), + (v13/*: any*/) ] }, { @@ -1031,9 +1031,9 @@ v31 = { "concreteType": "GitActor", "plural": false, "selections": [ - v3, - v13, - v29 + (v3/*: any*/), + (v13/*: any*/), + (v29/*: any*/) ] }, { @@ -1043,7 +1043,7 @@ v31 = { "args": null, "storageKey": null }, - v30, + (v30/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1100,7 +1100,7 @@ v33 = { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v32 + "selections": (v32/*: any*/) } ] }, @@ -1141,8 +1141,8 @@ v36 = { "concreteType": "PageInfo", "plural": false, "selections": [ - v19, - v18 + (v19/*: any*/), + (v18/*: any*/) ] }, v37 = [ @@ -1153,7 +1153,7 @@ v37 = [ "args": null, "storageKey": null }, - v2 + (v2/*: any*/) ], v38 = [ { @@ -1191,7 +1191,7 @@ v41 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": (v37/*: any*/) }, v42 = { "kind": "LinkedField", @@ -1201,28 +1201,23 @@ v42 = { "args": null, "concreteType": null, "plural": false, - "selections": v25 + "selections": (v25/*: any*/) }; return { "kind": "Request", - "operationKind": "query", - "name": "issueishDetailContainerQuery", - "id": null, - "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_3D8CP9\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_3D8CP9 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "issueishDetailContainerQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "repository", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": "Repository", "plural": false, "selections": [ @@ -1293,51 +1288,51 @@ return { "operation": { "kind": "Operation", "name": "issueishDetailContainerQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "repository", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": "Repository", "plural": false, "selections": [ - v2, - v3, - v7, + (v2/*: any*/), + (v3/*: any*/), + (v7/*: any*/), { "kind": "LinkedField", "alias": "issue", "name": "issueOrPullRequest", "storageKey": null, - "args": v8, + "args": (v8/*: any*/), "concreteType": null, "plural": false, "selections": [ - v4, - v2, + (v4/*: any*/), + (v2/*: any*/), { "kind": "InlineFragment", "type": "Issue", "selections": [ - v9, - v10, - v11, - v12, - v16, - v14, + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/), + (v12/*: any*/), + (v16/*: any*/), + (v14/*: any*/), { "kind": "LinkedField", "alias": null, "name": "timeline", "storageKey": null, - "args": v17, + "args": (v17/*: any*/), "concreteType": "IssueTimelineConnection", "plural": false, "selections": [ - v20, + (v20/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1347,7 +1342,7 @@ return { "concreteType": "IssueTimelineItemEdge", "plural": true, "selections": [ - v21, + (v21/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1357,11 +1352,11 @@ return { "concreteType": null, "plural": false, "selections": [ - v4, - v2, - v24, - v28, - v31 + (v4/*: any*/), + (v2/*: any*/), + (v24/*: any*/), + (v28/*: any*/), + (v31/*: any*/) ] } ] @@ -1372,12 +1367,12 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v17, + "args": (v17/*: any*/), "handle": "connection", "key": "IssueTimelineController_timeline", "filters": null }, - v33 + (v33/*: any*/) ] } ] @@ -1387,12 +1382,12 @@ return { "alias": "pullRequest", "name": "issueOrPullRequest", "storageKey": null, - "args": v8, + "args": (v8/*: any*/), "concreteType": null, "plural": false, "selections": [ - v4, - v2, + (v4/*: any*/), + (v2/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", @@ -1402,11 +1397,11 @@ return { "alias": null, "name": "commits", "storageKey": null, - "args": v34, + "args": (v34/*: any*/), "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v20, + (v20/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1416,7 +1411,7 @@ return { "concreteType": "PullRequestCommitEdge", "plural": true, "selections": [ - v21, + (v21/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1435,7 +1430,7 @@ return { "concreteType": "Commit", "plural": false, "selections": [ - v2, + (v2/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1445,8 +1440,8 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v13, - v3, + (v13/*: any*/), + (v3/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1477,12 +1472,12 @@ return { "args": null, "storageKey": null }, - v30, - v14 + (v30/*: any*/), + (v14/*: any*/) ] }, - v2, - v4 + (v2/*: any*/), + (v4/*: any*/) ] } ] @@ -1493,12 +1488,12 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v34, + "args": (v34/*: any*/), "handle": "connection", "key": "prCommitsView_commits", "filters": null }, - v9, + (v9/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1515,9 +1510,9 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v3, - v7, - v14, + (v3/*: any*/), + (v7/*: any*/), + (v14/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1525,10 +1520,10 @@ return { "args": null, "storageKey": null }, - v2 + (v2/*: any*/) ] }, - v22, + (v22/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1536,17 +1531,17 @@ return { "args": null, "storageKey": null }, - v14, + (v14/*: any*/), { "kind": "LinkedField", "alias": null, "name": "reviews", "storageKey": null, - "args": v35, + "args": (v35/*: any*/), "concreteType": "PullRequestReviewConnection", "plural": false, "selections": [ - v36, + (v36/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1556,7 +1551,7 @@ return { "concreteType": "PullRequestReviewEdge", "plural": true, "selections": [ - v21, + (v21/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1566,7 +1561,7 @@ return { "concreteType": "PullRequestReview", "plural": false, "selections": [ - v2, + (v2/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1582,9 +1577,9 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": (v37/*: any*/) }, - v11, + (v11/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1600,7 +1595,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v6 + "selections": (v6/*: any*/) }, { "kind": "LinkedField", @@ -1611,9 +1606,9 @@ return { "concreteType": null, "plural": false, "selections": [ - v4, - v13, - v2 + (v4/*: any*/), + (v13/*: any*/), + (v2/*: any*/) ] }, { @@ -1621,11 +1616,11 @@ return { "alias": null, "name": "comments", "storageKey": null, - "args": v38, + "args": (v38/*: any*/), "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ - v36, + (v36/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1635,7 +1630,7 @@ return { "concreteType": "PullRequestReviewCommentEdge", "plural": true, "selections": [ - v21, + (v21/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1645,9 +1640,9 @@ return { "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v2, - v26, - v12, + (v2/*: any*/), + (v26/*: any*/), + (v12/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1655,8 +1650,8 @@ return { "args": null, "storageKey": null }, - v39, - v40, + (v39/*: any*/), + (v40/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1666,12 +1661,12 @@ return { "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v2 + (v2/*: any*/) ] }, - v27, - v14, - v4 + (v27/*: any*/), + (v14/*: any*/), + (v4/*: any*/) ] } ] @@ -1682,12 +1677,12 @@ return { "kind": "LinkedHandle", "alias": null, "name": "comments", - "args": v38, + "args": (v38/*: any*/), "handle": "connection", "key": "PrReviewCommentsContainer_comments", "filters": null }, - v4 + (v4/*: any*/) ] } ] @@ -1698,12 +1693,12 @@ return { "kind": "LinkedHandle", "alias": null, "name": "reviews", - "args": v35, + "args": (v35/*: any*/), "handle": "connection", "key": "PrReviewsContainer_reviews", "filters": null }, - v10, + (v10/*: any*/), { "kind": "LinkedField", "alias": "countedCommits", @@ -1712,7 +1707,7 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v32 + "selections": (v32/*: any*/) }, { "kind": "LinkedField", @@ -1766,7 +1761,7 @@ return { "concreteType": "Status", "plural": false, "selections": [ - v11, + (v11/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1776,8 +1771,8 @@ return { "concreteType": "StatusContext", "plural": true, "selections": [ - v2, - v11, + (v2/*: any*/), + (v11/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1801,21 +1796,21 @@ return { } ] }, - v2 + (v2/*: any*/) ] }, - v2 + (v2/*: any*/) ] }, - v2 + (v2/*: any*/) ] } ] } ] }, - v11, - v12, + (v11/*: any*/), + (v12/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1823,7 +1818,7 @@ return { "args": null, "storageKey": null }, - v16, + (v16/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1832,7 +1827,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v6 + "selections": (v6/*: any*/) }, { "kind": "LinkedField", @@ -1843,8 +1838,8 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v7, - v2 + (v7/*: any*/), + (v2/*: any*/) ] }, { @@ -1852,11 +1847,11 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v17, + "args": (v17/*: any*/), "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v20, + (v20/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1866,7 +1861,7 @@ return { "concreteType": "PullRequestTimelineItemEdge", "plural": true, "selections": [ - v21, + (v21/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1876,14 +1871,14 @@ return { "concreteType": null, "plural": false, "selections": [ - v4, - v2, - v24, + (v4/*: any*/), + (v2/*: any*/), + (v24/*: any*/), { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v41, + (v41/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1918,7 +1913,7 @@ return { "concreteType": "CommitComment", "plural": false, "selections": [ - v2, + (v2/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1927,13 +1922,13 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v23 + "selections": (v23/*: any*/) }, - v41, - v12, - v27, - v39, - v40 + (v41/*: any*/), + (v12/*: any*/), + (v27/*: any*/), + (v39/*: any*/), + (v40/*: any*/) ] } ] @@ -1946,7 +1941,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v42, + (v42/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1955,7 +1950,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": (v37/*: any*/) }, { "kind": "LinkedField", @@ -1965,17 +1960,17 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v37 + "selections": (v37/*: any*/) }, - v27 + (v27/*: any*/) ] }, { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v42, - v41, + (v42/*: any*/), + (v41/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1983,11 +1978,11 @@ return { "args": null, "storageKey": null }, - v27 + (v27/*: any*/) ] }, - v28, - v31 + (v28/*: any*/), + (v31/*: any*/) ] } ] @@ -1998,12 +1993,12 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v17, + "args": (v17/*: any*/), "handle": "connection", "key": "prTimelineContainer_timeline", "filters": null }, - v33 + (v33/*: any*/) ] } ] @@ -2011,6 +2006,13 @@ return { ] } ] + }, + "params": { + "operationKind": "query", + "name": "issueishDetailContainerQuery", + "id": null, + "text": "query issueishDetailContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $issueishNumber: Int!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n ...issueishDetailController_repository_y3nHF\n id\n }\n}\n\nfragment issueishDetailController_repository_y3nHF on Repository {\n ...issueDetailView_repository\n ...prDetailView_repository\n name\n owner {\n __typename\n login\n id\n }\n issue: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on Issue {\n title\n number\n ...issueDetailView_issue_3D8CP9\n }\n ... on Node {\n id\n }\n }\n pullRequest: issueOrPullRequest(number: $issueishNumber) {\n __typename\n ... on PullRequest {\n title\n number\n headRefName\n headRepository {\n name\n owner {\n __typename\n login\n id\n }\n url\n sshUrl\n id\n }\n ...prDetailView_pullRequest_2qM2KL\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_repository on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_3D8CP9 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n", + "metadata": {} } }; })(); diff --git a/lib/containers/__generated__/issueishSearchContainerQuery.graphql.js b/lib/containers/__generated__/issueishSearchContainerQuery.graphql.js index a23ceb7401..5da39bd72d 100644 --- a/lib/containers/__generated__/issueishSearchContainerQuery.graphql.js +++ b/lib/containers/__generated__/issueishSearchContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 4f7294887e96b0a41dd57f0f3164f765 + * @relayHash 2976f0b1cbe75dfca74d37fc0f14877b */ /* eslint-disable */ @@ -139,28 +139,23 @@ v4 = { }; return { "kind": "Request", - "operationKind": "query", - "name": "issueishSearchContainerQuery", - "id": null, - "text": "query issueishSearchContainerQuery(\n $query: String!\n $first: Int!\n) {\n search(first: $first, query: $query, type: ISSUE) {\n issueCount\n nodes {\n __typename\n ...issueishListController_results\n ... on Node {\n id\n }\n }\n }\n}\n\nfragment issueishListController_results on PullRequest {\n number\n title\n url\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n createdAt\n headRefName\n repository {\n id\n }\n commits(last: 1) {\n nodes {\n commit {\n status {\n contexts {\n state\n id\n }\n id\n }\n id\n }\n id\n }\n }\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "issueishSearchContainerQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "search", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": "SearchResultItemConnection", "plural": false, "selections": [ - v2, + (v2/*: any*/), { "kind": "LinkedField", "alias": null, @@ -184,18 +179,18 @@ return { "operation": { "kind": "Operation", "name": "issueishSearchContainerQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "search", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": "SearchResultItemConnection", "plural": false, "selections": [ - v2, + (v2/*: any*/), { "kind": "LinkedField", "alias": null, @@ -205,8 +200,8 @@ return { "concreteType": null, "plural": true, "selections": [ - v3, - v4, + (v3/*: any*/), + (v4/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", @@ -241,7 +236,7 @@ return { "concreteType": null, "plural": false, "selections": [ - v3, + (v3/*: any*/), { "kind": "ScalarField", "alias": null, @@ -256,7 +251,7 @@ return { "args": null, "storageKey": null }, - v4 + (v4/*: any*/) ] }, { @@ -282,7 +277,7 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v4 + (v4/*: any*/) ] }, { @@ -344,16 +339,16 @@ return { "args": null, "storageKey": null }, - v4 + (v4/*: any*/) ] }, - v4 + (v4/*: any*/) ] }, - v4 + (v4/*: any*/) ] }, - v4 + (v4/*: any*/) ] } ] @@ -365,6 +360,13 @@ return { ] } ] + }, + "params": { + "operationKind": "query", + "name": "issueishSearchContainerQuery", + "id": null, + "text": "query issueishSearchContainerQuery(\n $query: String!\n $first: Int!\n) {\n search(first: $first, query: $query, type: ISSUE) {\n issueCount\n nodes {\n __typename\n ...issueishListController_results\n ... on Node {\n id\n }\n }\n }\n}\n\nfragment issueishListController_results on PullRequest {\n number\n title\n url\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n createdAt\n headRefName\n repository {\n id\n }\n commits(last: 1) {\n nodes {\n commit {\n status {\n contexts {\n state\n id\n }\n id\n }\n id\n }\n id\n }\n }\n}\n", + "metadata": {} } }; })(); diff --git a/lib/containers/__generated__/issueishTooltipContainer_resource.graphql.js b/lib/containers/__generated__/issueishTooltipContainer_resource.graphql.js index c07f7ed456..78554a9501 100644 --- a/lib/containers/__generated__/issueishTooltipContainer_resource.graphql.js +++ b/lib/containers/__generated__/issueishTooltipContainer_resource.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; export type IssueState = "CLOSED" | "OPEN" | "%future added value"; export type PullRequestState = "CLOSED" | "MERGED" | "OPEN" | "%future added value"; import type { FragmentReference } from "relay-runtime"; @@ -53,7 +53,7 @@ export type issueishTooltipContainer_resource = {| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = { "kind": "ScalarField", "alias": null, @@ -108,7 +108,7 @@ v1 = [ "concreteType": null, "plural": false, "selections": [ - v0 + (v0/*: any*/) ] } ] @@ -122,7 +122,7 @@ v1 = [ "concreteType": null, "plural": false, "selections": [ - v0, + (v0/*: any*/), { "kind": "ScalarField", "alias": null, @@ -150,12 +150,12 @@ return { { "kind": "InlineFragment", "type": "PullRequest", - "selections": v1 + "selections": (v1/*: any*/) }, { "kind": "InlineFragment", "type": "Issue", - "selections": v1 + "selections": (v1/*: any*/) } ] }; diff --git a/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js index 6fa92aa46b..b5fec8befa 100644 --- a/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewCommentsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash d372fbac67c0b387985686b68db27130 + * @relayHash 64864427d1bd48cdc524cbec06af166c */ /* eslint-disable */ @@ -137,24 +137,19 @@ v4 = [ ]; return { "kind": "Request", - "operationKind": "query", - "name": "prReviewCommentsContainerQuery", - "id": null, - "text": "query prReviewCommentsContainerQuery(\n $commentCount: Int!\n $commentCursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on PullRequestReview {\n ...prReviewCommentsContainer_review_1VbUmL\n }\n id\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "prReviewCommentsContainerQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "node", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -189,19 +184,19 @@ return { "operation": { "kind": "Operation", "name": "prReviewCommentsContainerQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "node", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ - v2, - v3, + (v2/*: any*/), + (v3/*: any*/), { "kind": "InlineFragment", "type": "PullRequestReview", @@ -218,7 +213,7 @@ return { "alias": null, "name": "comments", "storageKey": null, - "args": v4, + "args": (v4/*: any*/), "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ @@ -272,7 +267,7 @@ return { "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v3, + (v3/*: any*/), { "kind": "LinkedField", "alias": null, @@ -282,7 +277,7 @@ return { "concreteType": null, "plural": false, "selections": [ - v2, + (v2/*: any*/), { "kind": "ScalarField", "alias": null, @@ -297,7 +292,7 @@ return { "args": null, "storageKey": null }, - v3 + (v3/*: any*/) ] }, { @@ -337,7 +332,7 @@ return { "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v3 + (v3/*: any*/) ] }, { @@ -354,7 +349,7 @@ return { "args": null, "storageKey": null }, - v2 + (v2/*: any*/) ] } ] @@ -365,7 +360,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "comments", - "args": v4, + "args": (v4/*: any*/), "handle": "connection", "key": "PrReviewCommentsContainer_comments", "filters": null @@ -375,6 +370,13 @@ return { ] } ] + }, + "params": { + "operationKind": "query", + "name": "prReviewCommentsContainerQuery", + "id": null, + "text": "query prReviewCommentsContainerQuery(\n $commentCount: Int!\n $commentCursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on PullRequestReview {\n ...prReviewCommentsContainer_review_1VbUmL\n }\n id\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "metadata": {} } }; })(); diff --git a/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js b/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js index ebe2215fea..078224f564 100644 --- a/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js +++ b/lib/containers/__generated__/prReviewCommentsContainer_review.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; import type { FragmentReference } from "relay-runtime"; declare export opaque type prReviewCommentsContainer_review$ref: FragmentReference; export type prReviewCommentsContainer_review = {| @@ -43,7 +43,7 @@ export type prReviewCommentsContainer_review = {| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = { "kind": "ScalarField", "alias": null, @@ -82,7 +82,7 @@ return { } ], "selections": [ - v0, + (v0/*: any*/), { "kind": "ScalarField", "alias": null, @@ -149,7 +149,7 @@ return { "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v0, + (v0/*: any*/), { "kind": "LinkedField", "alias": null, @@ -212,7 +212,7 @@ return { "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v0 + (v0/*: any*/) ] }, { diff --git a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js index 19537cca71..539d652da7 100644 --- a/lib/containers/__generated__/prReviewsContainerQuery.graphql.js +++ b/lib/containers/__generated__/prReviewsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash a3845eb13d831f5f140cc6adc98de1df + * @relayHash ccf4c910993e0eac7e4d84076a406ae7 */ /* eslint-disable */ @@ -261,24 +261,19 @@ v10 = [ ]; return { "kind": "Request", - "operationKind": "query", - "name": "prReviewsContainerQuery", - "id": null, - "text": "query prReviewsContainerQuery(\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_y4qc0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "prReviewsContainerQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "resource", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -325,34 +320,34 @@ return { "operation": { "kind": "Operation", "name": "prReviewsContainerQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "resource", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ - v2, - v3, + (v2/*: any*/), + (v3/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v4, + (v4/*: any*/), { "kind": "LinkedField", "alias": null, "name": "reviews", "storageKey": null, - "args": v5, + "args": (v5/*: any*/), "concreteType": "PullRequestReviewConnection", "plural": false, "selections": [ - v6, + (v6/*: any*/), { "kind": "LinkedField", "alias": null, @@ -362,7 +357,7 @@ return { "concreteType": "PullRequestReviewEdge", "plural": true, "selections": [ - v7, + (v7/*: any*/), { "kind": "LinkedField", "alias": null, @@ -372,7 +367,7 @@ return { "concreteType": "PullRequestReview", "plural": false, "selections": [ - v3, + (v3/*: any*/), { "kind": "ScalarField", "alias": null, @@ -396,7 +391,7 @@ return { "args": null, "storageKey": null }, - v3 + (v3/*: any*/) ] }, { @@ -422,9 +417,9 @@ return { "concreteType": null, "plural": false, "selections": [ - v2, - v8, - v3 + (v2/*: any*/), + (v8/*: any*/), + (v3/*: any*/) ] }, { @@ -436,9 +431,9 @@ return { "concreteType": null, "plural": false, "selections": [ - v2, - v9, - v3 + (v2/*: any*/), + (v9/*: any*/), + (v3/*: any*/) ] }, { @@ -446,11 +441,11 @@ return { "alias": null, "name": "comments", "storageKey": null, - "args": v10, + "args": (v10/*: any*/), "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ - v6, + (v6/*: any*/), { "kind": "LinkedField", "alias": null, @@ -460,7 +455,7 @@ return { "concreteType": "PullRequestReviewCommentEdge", "plural": true, "selections": [ - v7, + (v7/*: any*/), { "kind": "LinkedField", "alias": null, @@ -470,7 +465,7 @@ return { "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v3, + (v3/*: any*/), { "kind": "LinkedField", "alias": null, @@ -480,10 +475,10 @@ return { "concreteType": null, "plural": false, "selections": [ - v2, - v9, - v8, - v3 + (v2/*: any*/), + (v9/*: any*/), + (v8/*: any*/), + (v3/*: any*/) ] }, { @@ -523,7 +518,7 @@ return { "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v3 + (v3/*: any*/) ] }, { @@ -533,8 +528,8 @@ return { "args": null, "storageKey": null }, - v4, - v2 + (v4/*: any*/), + (v2/*: any*/) ] } ] @@ -545,12 +540,12 @@ return { "kind": "LinkedHandle", "alias": null, "name": "comments", - "args": v10, + "args": (v10/*: any*/), "handle": "connection", "key": "PrReviewCommentsContainer_comments", "filters": null }, - v2 + (v2/*: any*/) ] } ] @@ -561,7 +556,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "reviews", - "args": v5, + "args": (v5/*: any*/), "handle": "connection", "key": "PrReviewsContainer_reviews", "filters": null @@ -571,6 +566,13 @@ return { ] } ] + }, + "params": { + "operationKind": "query", + "name": "prReviewsContainerQuery", + "id": null, + "text": "query prReviewsContainerQuery(\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prReviewsContainer_pullRequest_y4qc0\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "metadata": {} } }; })(); diff --git a/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js index 04d2100cf7..9d8b491a56 100644 --- a/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js +++ b/lib/containers/__generated__/prReviewsContainer_pullRequest.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; type prReviewCommentsContainer_review$ref = any; export type PullRequestReviewState = "APPROVED" | "CHANGES_REQUESTED" | "COMMENTED" | "DISMISSED" | "PENDING" | "%future added value"; import type { FragmentReference } from "relay-runtime"; @@ -44,7 +44,7 @@ export type prReviewsContainer_pullRequest = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "prReviewsContainer_pullRequest", "type": "PullRequest", diff --git a/lib/containers/__generated__/remoteContainerQuery.graphql.js b/lib/containers/__generated__/remoteContainerQuery.graphql.js index a5ac465623..0e82900d15 100644 --- a/lib/containers/__generated__/remoteContainerQuery.graphql.js +++ b/lib/containers/__generated__/remoteContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 982797d241da7600c6e95299dff83585 + * @relayHash 727f857cb06ae341da8460148250d694 */ /* eslint-disable */ @@ -97,28 +97,23 @@ v4 = { }; return { "kind": "Request", - "operationKind": "query", - "name": "remoteContainerQuery", - "id": null, - "text": "query remoteContainerQuery(\n $owner: String!\n $name: String!\n) {\n repository(owner: $owner, name: $name) {\n id\n defaultBranchRef {\n prefix\n name\n id\n }\n }\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "remoteContainerQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "repository", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": "Repository", "plural": false, "selections": [ - v2, + (v2/*: any*/), { "kind": "LinkedField", "alias": null, @@ -128,8 +123,8 @@ return { "concreteType": "Ref", "plural": false, "selections": [ - v3, - v4 + (v3/*: any*/), + (v4/*: any*/) ] } ] @@ -139,18 +134,18 @@ return { "operation": { "kind": "Operation", "name": "remoteContainerQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "repository", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": "Repository", "plural": false, "selections": [ - v2, + (v2/*: any*/), { "kind": "LinkedField", "alias": null, @@ -160,14 +155,21 @@ return { "concreteType": "Ref", "plural": false, "selections": [ - v3, - v4, - v2 + (v3/*: any*/), + (v4/*: any*/), + (v2/*: any*/) ] } ] } ] + }, + "params": { + "operationKind": "query", + "name": "remoteContainerQuery", + "id": null, + "text": "query remoteContainerQuery(\n $owner: String!\n $name: String!\n) {\n repository(owner: $owner, name: $name) {\n id\n defaultBranchRef {\n prefix\n name\n id\n }\n }\n}\n", + "metadata": {} } }; })(); diff --git a/lib/containers/__generated__/userMentionTooltipContainer_repositoryOwner.graphql.js b/lib/containers/__generated__/userMentionTooltipContainer_repositoryOwner.graphql.js index 1f4b1e0cfe..3a0c3b2550 100644 --- a/lib/containers/__generated__/userMentionTooltipContainer_repositoryOwner.graphql.js +++ b/lib/containers/__generated__/userMentionTooltipContainer_repositoryOwner.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; import type { FragmentReference } from "relay-runtime"; declare export opaque type userMentionTooltipContainer_repositoryOwner$ref: FragmentReference; export type userMentionTooltipContainer_repositoryOwner = {| @@ -25,7 +25,7 @@ export type userMentionTooltipContainer_repositoryOwner = {| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = [ { "kind": "ScalarField", @@ -64,7 +64,7 @@ return { "args": null, "concreteType": "RepositoryConnection", "plural": false, - "selections": v0 + "selections": (v0/*: any*/) }, { "kind": "InlineFragment", @@ -78,7 +78,7 @@ return { "args": null, "concreteType": "UserConnection", "plural": false, - "selections": v0 + "selections": (v0/*: any*/) } ] }, diff --git a/lib/controllers/__generated__/issueTimelineControllerQuery.graphql.js b/lib/controllers/__generated__/issueTimelineControllerQuery.graphql.js index 61cd337896..582ea5717e 100644 --- a/lib/controllers/__generated__/issueTimelineControllerQuery.graphql.js +++ b/lib/controllers/__generated__/issueTimelineControllerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 70fb97e27904226bef17642a8a7b34e2 + * @relayHash a967f9435e3ba31acfede3717315be7e */ /* eslint-disable */ @@ -292,30 +292,25 @@ v11 = { "concreteType": "User", "plural": false, "selections": [ - v6, - v3 + (v6/*: any*/), + (v3/*: any*/) ] }; return { "kind": "Request", - "operationKind": "query", - "name": "issueTimelineControllerQuery", - "id": null, - "text": "query issueTimelineControllerQuery(\n $timelineCount: Int!\n $timelineCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on Issue {\n ...issueTimelineController_issue_3D8CP9\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "issueTimelineControllerQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "resource", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -350,30 +345,30 @@ return { "operation": { "kind": "Operation", "name": "issueTimelineControllerQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "resource", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ - v2, - v3, + (v2/*: any*/), + (v3/*: any*/), { "kind": "InlineFragment", "type": "Issue", "selections": [ - v4, + (v4/*: any*/), { "kind": "LinkedField", "alias": null, "name": "timeline", "storageKey": null, - "args": v5, + "args": (v5/*: any*/), "concreteType": "IssueTimelineConnection", "plural": false, "selections": [ @@ -427,8 +422,8 @@ return { "concreteType": null, "plural": false, "selections": [ - v2, - v3, + (v2/*: any*/), + (v3/*: any*/), { "kind": "InlineFragment", "type": "CrossReferencedEvent", @@ -456,10 +451,10 @@ return { "concreteType": null, "plural": false, "selections": [ - v2, - v6, - v7, - v3 + (v2/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + (v3/*: any*/) ] }, { @@ -471,7 +466,7 @@ return { "concreteType": null, "plural": false, "selections": [ - v2, + (v2/*: any*/), { "kind": "LinkedField", "alias": null, @@ -481,7 +476,7 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v8, + (v8/*: any*/), { "kind": "LinkedField", "alias": null, @@ -491,12 +486,12 @@ return { "concreteType": null, "plural": false, "selections": [ - v2, - v6, - v3 + (v2/*: any*/), + (v6/*: any*/), + (v3/*: any*/) ] }, - v3, + (v3/*: any*/), { "kind": "ScalarField", "alias": null, @@ -506,14 +501,14 @@ return { } ] }, - v3, + (v3/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v9, - v10, - v4, + (v9/*: any*/), + (v10/*: any*/), + (v4/*: any*/), { "kind": "ScalarField", "alias": "prState", @@ -527,9 +522,9 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v9, - v10, - v4, + (v9/*: any*/), + (v10/*: any*/), + (v4/*: any*/), { "kind": "ScalarField", "alias": "issueState", @@ -556,10 +551,10 @@ return { "concreteType": null, "plural": false, "selections": [ - v2, - v7, - v6, - v3 + (v2/*: any*/), + (v7/*: any*/), + (v6/*: any*/), + (v3/*: any*/) ] }, { @@ -576,7 +571,7 @@ return { "args": null, "storageKey": null }, - v4 + (v4/*: any*/) ] }, { @@ -592,9 +587,9 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v8, - v11, - v7 + (v8/*: any*/), + (v11/*: any*/), + (v7/*: any*/) ] }, { @@ -606,9 +601,9 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v8, - v7, - v11 + (v8/*: any*/), + (v7/*: any*/), + (v11/*: any*/) ] }, { @@ -658,7 +653,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v5, + "args": (v5/*: any*/), "handle": "connection", "key": "IssueTimelineController_timeline", "filters": null @@ -668,6 +663,13 @@ return { ] } ] + }, + "params": { + "operationKind": "query", + "name": "issueTimelineControllerQuery", + "id": null, + "text": "query issueTimelineControllerQuery(\n $timelineCount: Int!\n $timelineCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on Issue {\n ...issueTimelineController_issue_3D8CP9\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", + "metadata": {} } }; })(); diff --git a/lib/controllers/__generated__/issueTimelineController_issue.graphql.js b/lib/controllers/__generated__/issueTimelineController_issue.graphql.js index d5f348a00f..225b374ff6 100644 --- a/lib/controllers/__generated__/issueTimelineController_issue.graphql.js +++ b/lib/controllers/__generated__/issueTimelineController_issue.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; type commitsView_nodes$ref = any; type crossReferencedEventsView_nodes$ref = any; type issueCommentView_item$ref = any; @@ -33,7 +33,7 @@ export type issueTimelineController_issue = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "issueTimelineController_issue", "type": "Issue", diff --git a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js index fa323bdbdd..d2d232f883 100644 --- a/lib/controllers/__generated__/issueishDetailController_repository.graphql.js +++ b/lib/controllers/__generated__/issueishDetailController_repository.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; type issueDetailView_issue$ref = any; type issueDetailView_repository$ref = any; type prDetailView_pullRequest$ref = any; @@ -54,7 +54,7 @@ export type issueishDetailController_repository = {| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = { "kind": "ScalarField", "alias": null, @@ -193,30 +193,30 @@ return { "name": "prDetailView_repository", "args": null }, - v0, - v1, + (v0/*: any*/), + (v1/*: any*/), { "kind": "LinkedField", "alias": "issue", "name": "issueOrPullRequest", "storageKey": null, - "args": v2, + "args": (v2/*: any*/), "concreteType": null, "plural": false, "selections": [ - v3, + (v3/*: any*/), { "kind": "InlineFragment", "type": "Issue", "selections": [ - v4, - v5, + (v4/*: any*/), + (v5/*: any*/), { "kind": "FragmentSpread", "name": "issueDetailView_issue", "args": [ - v6, - v7 + (v6/*: any*/), + (v7/*: any*/) ] } ] @@ -228,17 +228,17 @@ return { "alias": "pullRequest", "name": "issueOrPullRequest", "storageKey": null, - "args": v2, + "args": (v2/*: any*/), "concreteType": null, "plural": false, "selections": [ - v3, + (v3/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v4, - v5, + (v4/*: any*/), + (v5/*: any*/), { "kind": "ScalarField", "alias": null, @@ -255,8 +255,8 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v0, - v1, + (v0/*: any*/), + (v1/*: any*/), { "kind": "ScalarField", "alias": null, @@ -313,8 +313,8 @@ return { "variableName": "reviewCursor", "type": null }, - v6, - v7 + (v6/*: any*/), + (v7/*: any*/) ] } ] diff --git a/lib/controllers/__generated__/issueishListController_results.graphql.js b/lib/controllers/__generated__/issueishListController_results.graphql.js index 9349b1b393..79ba552a04 100644 --- a/lib/controllers/__generated__/issueishListController_results.graphql.js +++ b/lib/controllers/__generated__/issueishListController_results.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; export type StatusState = "ERROR" | "EXPECTED" | "FAILURE" | "PENDING" | "SUCCESS" | "%future added value"; import type { FragmentReference } from "relay-runtime"; declare export opaque type issueishListController_results$ref: FragmentReference; @@ -40,7 +40,7 @@ export type issueishListController_results = $ReadOnlyArray<{| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "issueishListController_results", "type": "PullRequest", diff --git a/lib/controllers/__generated__/prTimelineControllerQuery.graphql.js b/lib/controllers/__generated__/prTimelineControllerQuery.graphql.js index 33b5c8ca6a..0a33d7c6ce 100644 --- a/lib/controllers/__generated__/prTimelineControllerQuery.graphql.js +++ b/lib/controllers/__generated__/prTimelineControllerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash bef5de96f41034a1a27b9ace9f641f7b + * @relayHash b92dc04d77687fc820129507a7ab69a9 */ /* eslint-disable */ @@ -334,9 +334,9 @@ v5 = { "storageKey": null }, v6 = [ - v2, - v5, - v3 + (v2/*: any*/), + (v5/*: any*/), + (v3/*: any*/) ], v7 = { "kind": "LinkedField", @@ -346,7 +346,7 @@ v7 = { "args": null, "concreteType": null, "plural": false, - "selections": v6 + "selections": (v6/*: any*/) }, v8 = [ { @@ -370,10 +370,10 @@ v9 = { "storageKey": null }, v10 = [ - v2, - v5, - v9, - v3 + (v2/*: any*/), + (v5/*: any*/), + (v9/*: any*/), + (v3/*: any*/) ], v11 = { "kind": "ScalarField", @@ -404,7 +404,7 @@ v14 = [ "args": null, "storageKey": null }, - v3 + (v3/*: any*/) ], v15 = { "kind": "LinkedField", @@ -414,7 +414,7 @@ v15 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v14 + "selections": (v14/*: any*/) }, v16 = { "kind": "ScalarField", @@ -431,10 +431,10 @@ v17 = { "storageKey": null }, v18 = [ - v2, - v9, - v5, - v3 + (v2/*: any*/), + (v9/*: any*/), + (v5/*: any*/), + (v3/*: any*/) ], v19 = { "kind": "LinkedField", @@ -444,7 +444,7 @@ v19 = { "args": null, "concreteType": null, "plural": false, - "selections": v18 + "selections": (v18/*: any*/) }, v20 = { "kind": "LinkedField", @@ -455,30 +455,25 @@ v20 = { "concreteType": "User", "plural": false, "selections": [ - v5, - v3 + (v5/*: any*/), + (v3/*: any*/) ] }; return { "kind": "Request", - "operationKind": "query", - "name": "prTimelineControllerQuery", - "id": null, - "text": "query prTimelineControllerQuery(\n $timelineCount: Int!\n $timelineCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "prTimelineControllerQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "resource", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -513,24 +508,24 @@ return { "operation": { "kind": "Operation", "name": "prTimelineControllerQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "resource", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ - v2, - v3, + (v2/*: any*/), + (v3/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v4, + (v4/*: any*/), { "kind": "ScalarField", "alias": null, @@ -546,7 +541,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v6 + "selections": (v6/*: any*/) }, { "kind": "LinkedField", @@ -557,8 +552,8 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v7, - v3 + (v7/*: any*/), + (v3/*: any*/) ] }, { @@ -566,7 +561,7 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v8, + "args": (v8/*: any*/), "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ @@ -620,8 +615,8 @@ return { "concreteType": null, "plural": false, "selections": [ - v2, - v3, + (v2/*: any*/), + (v3/*: any*/), { "kind": "InlineFragment", "type": "CrossReferencedEvent", @@ -648,7 +643,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v10 + "selections": (v10/*: any*/) }, { "kind": "LinkedField", @@ -659,7 +654,7 @@ return { "concreteType": null, "plural": false, "selections": [ - v2, + (v2/*: any*/), { "kind": "LinkedField", "alias": null, @@ -669,9 +664,9 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v11, - v7, - v3, + (v11/*: any*/), + (v7/*: any*/), + (v3/*: any*/), { "kind": "ScalarField", "alias": null, @@ -681,14 +676,14 @@ return { } ] }, - v3, + (v3/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v12, - v13, - v4, + (v12/*: any*/), + (v13/*: any*/), + (v4/*: any*/), { "kind": "ScalarField", "alias": "prState", @@ -702,9 +697,9 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v12, - v13, - v4, + (v12/*: any*/), + (v13/*: any*/), + (v4/*: any*/), { "kind": "ScalarField", "alias": "issueState", @@ -722,7 +717,7 @@ return { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v15, + (v15/*: any*/), { "kind": "LinkedField", "alias": null, @@ -757,7 +752,7 @@ return { "concreteType": "CommitComment", "plural": false, "selections": [ - v3, + (v3/*: any*/), { "kind": "LinkedField", "alias": null, @@ -766,11 +761,11 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v10 + "selections": (v10/*: any*/) }, - v15, - v16, - v17, + (v15/*: any*/), + (v16/*: any*/), + (v17/*: any*/), { "kind": "ScalarField", "alias": null, @@ -797,7 +792,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v19, + (v19/*: any*/), { "kind": "LinkedField", "alias": null, @@ -806,7 +801,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v14 + "selections": (v14/*: any*/) }, { "kind": "LinkedField", @@ -816,17 +811,17 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v14 + "selections": (v14/*: any*/) }, - v17 + (v17/*: any*/) ] }, { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v19, - v15, + (v19/*: any*/), + (v15/*: any*/), { "kind": "ScalarField", "alias": null, @@ -834,7 +829,7 @@ return { "args": null, "storageKey": null }, - v17 + (v17/*: any*/) ] }, { @@ -849,11 +844,11 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v18 + "selections": (v18/*: any*/) }, - v16, - v17, - v4 + (v16/*: any*/), + (v17/*: any*/), + (v4/*: any*/) ] }, { @@ -869,9 +864,9 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v11, - v20, - v9 + (v11/*: any*/), + (v20/*: any*/), + (v9/*: any*/) ] }, { @@ -883,9 +878,9 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v11, - v9, - v20 + (v11/*: any*/), + (v9/*: any*/), + (v20/*: any*/) ] }, { @@ -935,7 +930,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v8, + "args": (v8/*: any*/), "handle": "connection", "key": "prTimelineContainer_timeline", "filters": null @@ -945,6 +940,13 @@ return { ] } ] + }, + "params": { + "operationKind": "query", + "name": "prTimelineControllerQuery", + "id": null, + "text": "query prTimelineControllerQuery(\n $timelineCount: Int!\n $timelineCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prTimelineController_pullRequest_3D8CP9\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", + "metadata": {} } }; })(); diff --git a/lib/controllers/__generated__/prTimelineController_pullRequest.graphql.js b/lib/controllers/__generated__/prTimelineController_pullRequest.graphql.js index b30a37083b..2a2488194d 100644 --- a/lib/controllers/__generated__/prTimelineController_pullRequest.graphql.js +++ b/lib/controllers/__generated__/prTimelineController_pullRequest.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; type commitCommentThreadView_item$ref = any; type commitsView_nodes$ref = any; type crossReferencedEventsView_nodes$ref = any; @@ -38,7 +38,7 @@ export type prTimelineController_pullRequest = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "prTimelineController_pullRequest", "type": "PullRequest", diff --git a/lib/items/__generated__/issueishTooltipItemQuery.graphql.js b/lib/items/__generated__/issueishTooltipItemQuery.graphql.js index e72d52912c..7471d4cb07 100644 --- a/lib/items/__generated__/issueishTooltipItemQuery.graphql.js +++ b/lib/items/__generated__/issueishTooltipItemQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash ec7add21f4125e294e4679a0fed3dfc9 + * @relayHash 2af853c01640ff29fc45ef2a08b60e21 */ /* eslint-disable */ @@ -172,12 +172,12 @@ v5 = [ "concreteType": null, "plural": false, "selections": [ - v2, - v4, - v3 + (v2/*: any*/), + (v4/*: any*/), + (v3/*: any*/) ] }, - v3 + (v3/*: any*/) ] }, { @@ -189,8 +189,8 @@ v5 = [ "concreteType": null, "plural": false, "selections": [ - v2, - v4, + (v2/*: any*/), + (v4/*: any*/), { "kind": "ScalarField", "alias": null, @@ -198,30 +198,25 @@ v5 = [ "args": null, "storageKey": null }, - v3 + (v3/*: any*/) ] } ]; return { "kind": "Request", - "operationKind": "query", - "name": "issueishTooltipItemQuery", - "id": null, - "text": "query issueishTooltipItemQuery(\n $issueishUrl: URI!\n) {\n resource(url: $issueishUrl) {\n __typename\n ...issueishTooltipContainer_resource\n ... on Node {\n id\n }\n }\n}\n\nfragment issueishTooltipContainer_resource on UniformResourceLocatable {\n __typename\n ... on Issue {\n state\n number\n title\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n }\n ... on PullRequest {\n state\n number\n title\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n }\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "issueishTooltipItemQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "resource", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -237,32 +232,39 @@ return { "operation": { "kind": "Operation", "name": "issueishTooltipItemQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "resource", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ - v2, - v3, + (v2/*: any*/), + (v3/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", - "selections": v5 + "selections": (v5/*: any*/) }, { "kind": "InlineFragment", "type": "Issue", - "selections": v5 + "selections": (v5/*: any*/) } ] } ] + }, + "params": { + "operationKind": "query", + "name": "issueishTooltipItemQuery", + "id": null, + "text": "query issueishTooltipItemQuery(\n $issueishUrl: URI!\n) {\n resource(url: $issueishUrl) {\n __typename\n ...issueishTooltipContainer_resource\n ... on Node {\n id\n }\n }\n}\n\nfragment issueishTooltipContainer_resource on UniformResourceLocatable {\n __typename\n ... on Issue {\n state\n number\n title\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n }\n ... on PullRequest {\n state\n number\n title\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n }\n}\n", + "metadata": {} } }; })(); diff --git a/lib/items/__generated__/userMentionTooltipItemQuery.graphql.js b/lib/items/__generated__/userMentionTooltipItemQuery.graphql.js index 85f27f9e43..3bd8491f4c 100644 --- a/lib/items/__generated__/userMentionTooltipItemQuery.graphql.js +++ b/lib/items/__generated__/userMentionTooltipItemQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 618ef28f16a644f2aa8240535a6b5ea4 + * @relayHash cbae41158b2bedaa2322e49d0f1da45d */ /* eslint-disable */ @@ -81,24 +81,19 @@ v2 = [ ]; return { "kind": "Request", - "operationKind": "query", - "name": "userMentionTooltipItemQuery", - "id": null, - "text": "query userMentionTooltipItemQuery(\n $username: String!\n) {\n repositoryOwner(login: $username) {\n __typename\n ...userMentionTooltipContainer_repositoryOwner\n id\n }\n}\n\nfragment userMentionTooltipContainer_repositoryOwner on RepositoryOwner {\n login\n avatarUrl\n repositories {\n totalCount\n }\n ... on User {\n company\n }\n ... on Organization {\n members {\n totalCount\n }\n }\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "userMentionTooltipItemQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "repositoryOwner", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -114,14 +109,14 @@ return { "operation": { "kind": "Operation", "name": "userMentionTooltipItemQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "repositoryOwner", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -154,7 +149,7 @@ return { "args": null, "concreteType": "RepositoryConnection", "plural": false, - "selections": v2 + "selections": (v2/*: any*/) }, { "kind": "ScalarField", @@ -175,7 +170,7 @@ return { "args": null, "concreteType": "UserConnection", "plural": false, - "selections": v2 + "selections": (v2/*: any*/) } ] }, @@ -195,6 +190,13 @@ return { ] } ] + }, + "params": { + "operationKind": "query", + "name": "userMentionTooltipItemQuery", + "id": null, + "text": "query userMentionTooltipItemQuery(\n $username: String!\n) {\n repositoryOwner(login: $username) {\n __typename\n ...userMentionTooltipContainer_repositoryOwner\n id\n }\n}\n\nfragment userMentionTooltipContainer_repositoryOwner on RepositoryOwner {\n login\n avatarUrl\n repositories {\n totalCount\n }\n ... on User {\n company\n }\n ... on Organization {\n members {\n totalCount\n }\n }\n}\n", + "metadata": {} } }; })(); diff --git a/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js index dfb9d262df..861efb7789 100644 --- a/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/issueDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 0de8f3b80a7115e2fb90301e39c4591d + * @relayHash 52b4d4eae123e9624bea3c36531049e8 */ /* eslint-disable */ @@ -332,9 +332,9 @@ v8 = { "concreteType": null, "plural": false, "selections": [ - v4, - v7, - v5 + (v4/*: any*/), + (v7/*: any*/), + (v5/*: any*/) ] }, v9 = { @@ -373,7 +373,7 @@ v13 = { "storageKey": null }, v14 = [ - v13 + (v13/*: any*/) ], v15 = [ { @@ -398,37 +398,32 @@ v16 = { "concreteType": "User", "plural": false, "selections": [ - v7, - v5 + (v7/*: any*/), + (v5/*: any*/) ] }; return { "kind": "Request", - "operationKind": "query", - "name": "issueDetailViewRefetchQuery", - "id": null, - "text": "query issueDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueDetailView_repository_3D8CP9\n id\n }\n issue: node(id: $issueishId) {\n __typename\n ...issueDetailView_issue_3D8CP9\n id\n }\n}\n\nfragment issueDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_3D8CP9 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "issueDetailViewRefetchQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": "repository", "name": "node", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ { "kind": "FragmentSpread", "name": "issueDetailView_repository", - "args": v2 + "args": (v2/*: any*/) } ] }, @@ -437,14 +432,14 @@ return { "alias": "issue", "name": "node", "storageKey": null, - "args": v3, + "args": (v3/*: any*/), "concreteType": null, "plural": false, "selections": [ { "kind": "FragmentSpread", "name": "issueDetailView_issue", - "args": v2 + "args": (v2/*: any*/) } ] } @@ -453,25 +448,25 @@ return { "operation": { "kind": "Operation", "name": "issueDetailViewRefetchQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": "repository", "name": "node", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ - v4, - v5, + (v4/*: any*/), + (v5/*: any*/), { "kind": "InlineFragment", "type": "Repository", "selections": [ - v6, - v8 + (v6/*: any*/), + (v8/*: any*/) ] } ] @@ -481,17 +476,17 @@ return { "alias": "issue", "name": "node", "storageKey": null, - "args": v3, + "args": (v3/*: any*/), "concreteType": null, "plural": false, "selections": [ - v4, - v5, + (v4/*: any*/), + (v5/*: any*/), { "kind": "InlineFragment", "type": "Issue", "selections": [ - v4, + (v4/*: any*/), { "kind": "ScalarField", "alias": null, @@ -499,9 +494,9 @@ return { "args": null, "storageKey": null }, - v9, - v10, - v11, + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/), { "kind": "LinkedField", "alias": null, @@ -511,29 +506,29 @@ return { "concreteType": null, "plural": false, "selections": [ - v4, - v7, - v12, - v5, + (v4/*: any*/), + (v7/*: any*/), + (v12/*: any*/), + (v5/*: any*/), { "kind": "InlineFragment", "type": "Bot", - "selections": v14 + "selections": (v14/*: any*/) }, { "kind": "InlineFragment", "type": "User", - "selections": v14 + "selections": (v14/*: any*/) } ] }, - v13, + (v13/*: any*/), { "kind": "LinkedField", "alias": null, "name": "timeline", "storageKey": null, - "args": v15, + "args": (v15/*: any*/), "concreteType": "IssueTimelineConnection", "plural": false, "selections": [ @@ -587,8 +582,8 @@ return { "concreteType": null, "plural": false, "selections": [ - v4, - v5, + (v4/*: any*/), + (v5/*: any*/), { "kind": "InlineFragment", "type": "CrossReferencedEvent", @@ -616,10 +611,10 @@ return { "concreteType": null, "plural": false, "selections": [ - v4, - v7, - v12, - v5 + (v4/*: any*/), + (v7/*: any*/), + (v12/*: any*/), + (v5/*: any*/) ] }, { @@ -631,7 +626,7 @@ return { "concreteType": null, "plural": false, "selections": [ - v4, + (v4/*: any*/), { "kind": "LinkedField", "alias": null, @@ -641,9 +636,9 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v6, - v8, - v5, + (v6/*: any*/), + (v8/*: any*/), + (v5/*: any*/), { "kind": "ScalarField", "alias": null, @@ -653,14 +648,14 @@ return { } ] }, - v5, + (v5/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v9, - v10, - v13, + (v9/*: any*/), + (v10/*: any*/), + (v13/*: any*/), { "kind": "ScalarField", "alias": "prState", @@ -674,9 +669,9 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v9, - v10, - v13, + (v9/*: any*/), + (v10/*: any*/), + (v13/*: any*/), { "kind": "ScalarField", "alias": "issueState", @@ -703,13 +698,13 @@ return { "concreteType": null, "plural": false, "selections": [ - v4, - v12, - v7, - v5 + (v4/*: any*/), + (v12/*: any*/), + (v7/*: any*/), + (v5/*: any*/) ] }, - v11, + (v11/*: any*/), { "kind": "ScalarField", "alias": null, @@ -717,7 +712,7 @@ return { "args": null, "storageKey": null }, - v13 + (v13/*: any*/) ] }, { @@ -733,9 +728,9 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v6, - v16, - v12 + (v6/*: any*/), + (v16/*: any*/), + (v12/*: any*/) ] }, { @@ -747,9 +742,9 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v6, - v12, - v16 + (v6/*: any*/), + (v12/*: any*/), + (v16/*: any*/) ] }, { @@ -799,7 +794,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v15, + "args": (v15/*: any*/), "handle": "connection", "key": "IssueTimelineController_timeline", "filters": null @@ -845,6 +840,13 @@ return { ] } ] + }, + "params": { + "operationKind": "query", + "name": "issueDetailViewRefetchQuery", + "id": null, + "text": "query issueDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...issueDetailView_repository_3D8CP9\n id\n }\n issue: node(id: $issueishId) {\n __typename\n ...issueDetailView_issue_3D8CP9\n id\n }\n}\n\nfragment issueDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment issueDetailView_issue_3D8CP9 on Issue {\n __typename\n ... on Node {\n id\n }\n state\n number\n title\n bodyHTML\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...issueTimelineController_issue_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment issueTimelineController_issue_3D8CP9 on Issue {\n url\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n", + "metadata": {} } }; })(); diff --git a/lib/views/__generated__/issueDetailView_issue.graphql.js b/lib/views/__generated__/issueDetailView_issue.graphql.js index 3ccf457baf..8eabadcabd 100644 --- a/lib/views/__generated__/issueDetailView_issue.graphql.js +++ b/lib/views/__generated__/issueDetailView_issue.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; type issueTimelineController_issue$ref = any; export type IssueState = "CLOSED" | "OPEN" | "%future added value"; export type ReactionContent = "CONFUSED" | "HEART" | "HOORAY" | "LAUGH" | "THUMBS_DOWN" | "THUMBS_UP" | "%future added value"; @@ -38,7 +38,7 @@ export type issueDetailView_issue = {| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = { "kind": "ScalarField", "alias": null, @@ -47,7 +47,7 @@ var v0 = { "storageKey": null }, v1 = [ - v0 + (v0/*: any*/) ]; return { "kind": "Fragment", @@ -137,12 +137,12 @@ return { { "kind": "InlineFragment", "type": "Bot", - "selections": v1 + "selections": (v1/*: any*/) }, { "kind": "InlineFragment", "type": "User", - "selections": v1 + "selections": (v1/*: any*/) } ] }, @@ -164,7 +164,7 @@ return { } ] }, - v0, + (v0/*: any*/), { "kind": "LinkedField", "alias": null, diff --git a/lib/views/__generated__/issueDetailView_repository.graphql.js b/lib/views/__generated__/issueDetailView_repository.graphql.js index efd212d10c..0c843ff1cc 100644 --- a/lib/views/__generated__/issueDetailView_repository.graphql.js +++ b/lib/views/__generated__/issueDetailView_repository.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; import type { FragmentReference } from "relay-runtime"; declare export opaque type issueDetailView_repository$ref: FragmentReference; export type issueDetailView_repository = {| @@ -21,7 +21,7 @@ export type issueDetailView_repository = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "issueDetailView_repository", "type": "Repository", diff --git a/lib/views/__generated__/prCommitView_item.graphql.js b/lib/views/__generated__/prCommitView_item.graphql.js index 2d1203207c..230bdcd7da 100644 --- a/lib/views/__generated__/prCommitView_item.graphql.js +++ b/lib/views/__generated__/prCommitView_item.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; import type { FragmentReference } from "relay-runtime"; declare export opaque type prCommitView_item$ref: FragmentReference; export type prCommitView_item = {| @@ -26,7 +26,7 @@ export type prCommitView_item = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "prCommitView_item", "type": "Commit", diff --git a/lib/views/__generated__/prCommitsViewQuery.graphql.js b/lib/views/__generated__/prCommitsViewQuery.graphql.js index 7d20b34ac5..826920ceb9 100644 --- a/lib/views/__generated__/prCommitsViewQuery.graphql.js +++ b/lib/views/__generated__/prCommitsViewQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 86632ba0fe5f43bd343d798b397ad81b + * @relayHash f553e531257d2b674f00b3d93a7740b0 */ /* eslint-disable */ @@ -145,24 +145,19 @@ v5 = [ ]; return { "kind": "Request", - "operationKind": "query", - "name": "prCommitsViewQuery", - "id": null, - "text": "query prCommitsViewQuery(\n $commitCount: Int!\n $commitCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prCommitsView_pullRequest_38TpXw\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "prCommitsViewQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "resource", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -197,30 +192,30 @@ return { "operation": { "kind": "Operation", "name": "prCommitsViewQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "resource", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ - v2, - v3, + (v2/*: any*/), + (v3/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v4, + (v4/*: any*/), { "kind": "LinkedField", "alias": null, "name": "commits", "storageKey": null, - "args": v5, + "args": (v5/*: any*/), "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ @@ -283,7 +278,7 @@ return { "concreteType": "Commit", "plural": false, "selections": [ - v3, + (v3/*: any*/), { "kind": "LinkedField", "alias": null, @@ -344,11 +339,11 @@ return { "args": null, "storageKey": null }, - v4 + (v4/*: any*/) ] }, - v3, - v2 + (v3/*: any*/), + (v2/*: any*/) ] } ] @@ -359,7 +354,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v5, + "args": (v5/*: any*/), "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -369,6 +364,13 @@ return { ] } ] + }, + "params": { + "operationKind": "query", + "name": "prCommitsViewQuery", + "id": null, + "text": "query prCommitsViewQuery(\n $commitCount: Int!\n $commitCursor: String\n $url: URI!\n) {\n resource(url: $url) {\n __typename\n ... on PullRequest {\n ...prCommitsView_pullRequest_38TpXw\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n", + "metadata": {} } }; })(); diff --git a/lib/views/__generated__/prCommitsView_pullRequest.graphql.js b/lib/views/__generated__/prCommitsView_pullRequest.graphql.js index 8fb99fb788..de0b9fb9cb 100644 --- a/lib/views/__generated__/prCommitsView_pullRequest.graphql.js +++ b/lib/views/__generated__/prCommitsView_pullRequest.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; type prCommitView_item$ref = any; import type { FragmentReference } from "relay-runtime"; declare export opaque type prCommitsView_pullRequest$ref: FragmentReference; @@ -33,7 +33,7 @@ export type prCommitsView_pullRequest = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "prCommitsView_pullRequest", "type": "PullRequest", diff --git a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js index 42e336a55a..4d45a80559 100644 --- a/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prDetailViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash c9c759b66c17ed333ce2946f8675ee19 + * @relayHash 7b0866b16d5c49faece538eda674deed */ /* eslint-disable */ @@ -610,9 +610,9 @@ v8 = { "storageKey": null }, v9 = [ - v5, - v8, - v6 + (v5/*: any*/), + (v8/*: any*/), + (v6/*: any*/) ], v10 = { "kind": "LinkedField", @@ -622,7 +622,7 @@ v10 = { "args": null, "concreteType": null, "plural": false, - "selections": v9 + "selections": (v9/*: any*/) }, v11 = { "kind": "ScalarField", @@ -682,8 +682,8 @@ v17 = { "concreteType": "PageInfo", "plural": false, "selections": [ - v15, - v16 + (v15/*: any*/), + (v16/*: any*/) ] }, v18 = { @@ -701,7 +701,7 @@ v19 = [ "args": null, "storageKey": null }, - v6 + (v6/*: any*/) ], v20 = { "kind": "ScalarField", @@ -732,10 +732,10 @@ v22 = [ } ], v23 = [ - v5, - v21, - v8, - v6 + (v5/*: any*/), + (v21/*: any*/), + (v8/*: any*/), + (v6/*: any*/) ], v24 = { "kind": "LinkedField", @@ -745,7 +745,7 @@ v24 = { "args": null, "concreteType": null, "plural": false, - "selections": v23 + "selections": (v23/*: any*/) }, v25 = { "kind": "ScalarField", @@ -798,8 +798,8 @@ v30 = { "concreteType": "PageInfo", "plural": false, "selections": [ - v16, - v15 + (v16/*: any*/), + (v15/*: any*/) ] }, v31 = { @@ -826,7 +826,7 @@ v33 = { "storageKey": null }, v34 = [ - v13 + (v13/*: any*/) ], v35 = [ { @@ -843,10 +843,10 @@ v35 = [ } ], v36 = [ - v5, - v8, - v21, - v6 + (v5/*: any*/), + (v8/*: any*/), + (v21/*: any*/), + (v6/*: any*/) ], v37 = { "kind": "LinkedField", @@ -856,7 +856,7 @@ v37 = { "args": null, "concreteType": "Commit", "plural": false, - "selections": v19 + "selections": (v19/*: any*/) }, v38 = { "kind": "LinkedField", @@ -866,7 +866,7 @@ v38 = { "args": null, "concreteType": null, "plural": false, - "selections": v23 + "selections": (v23/*: any*/) }, v39 = { "kind": "LinkedField", @@ -877,30 +877,25 @@ v39 = { "concreteType": "User", "plural": false, "selections": [ - v8, - v6 + (v8/*: any*/), + (v6/*: any*/) ] }; return { "kind": "Request", - "operationKind": "query", - "name": "prDetailViewRefetchQuery", - "id": null, - "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "prDetailViewRefetchQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": "repository", "name": "node", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -908,8 +903,8 @@ return { "kind": "FragmentSpread", "name": "prDetailView_repository", "args": [ - v2, - v3 + (v2/*: any*/), + (v3/*: any*/) ] } ] @@ -919,7 +914,7 @@ return { "alias": "pullRequest", "name": "node", "storageKey": null, - "args": v4, + "args": (v4/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -963,8 +958,8 @@ return { "variableName": "reviewCursor", "type": null }, - v2, - v3 + (v2/*: any*/), + (v3/*: any*/) ] } ] @@ -974,25 +969,25 @@ return { "operation": { "kind": "Operation", "name": "prDetailViewRefetchQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": "repository", "name": "node", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ - v5, - v6, + (v5/*: any*/), + (v6/*: any*/), { "kind": "InlineFragment", "type": "Repository", "selections": [ - v7, - v10 + (v7/*: any*/), + (v10/*: any*/) ] } ] @@ -1002,19 +997,19 @@ return { "alias": "pullRequest", "name": "node", "storageKey": null, - "args": v4, + "args": (v4/*: any*/), "concreteType": null, "plural": false, "selections": [ - v5, - v6, + (v5/*: any*/), + (v6/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v11, - v5, - v12, + (v11/*: any*/), + (v5/*: any*/), + (v12/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1022,17 +1017,17 @@ return { "args": null, "storageKey": null }, - v13, + (v13/*: any*/), { "kind": "LinkedField", "alias": null, "name": "reviews", "storageKey": null, - "args": v14, + "args": (v14/*: any*/), "concreteType": "PullRequestReviewConnection", "plural": false, "selections": [ - v17, + (v17/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1042,7 +1037,7 @@ return { "concreteType": "PullRequestReviewEdge", "plural": true, "selections": [ - v18, + (v18/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1052,7 +1047,7 @@ return { "concreteType": "PullRequestReview", "plural": false, "selections": [ - v6, + (v6/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1068,9 +1063,9 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v19 + "selections": (v19/*: any*/) }, - v20, + (v20/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1086,7 +1081,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v9 + "selections": (v9/*: any*/) }, { "kind": "LinkedField", @@ -1097,9 +1092,9 @@ return { "concreteType": null, "plural": false, "selections": [ - v5, - v21, - v6 + (v5/*: any*/), + (v21/*: any*/), + (v6/*: any*/) ] }, { @@ -1107,11 +1102,11 @@ return { "alias": null, "name": "comments", "storageKey": null, - "args": v22, + "args": (v22/*: any*/), "concreteType": "PullRequestReviewCommentConnection", "plural": false, "selections": [ - v17, + (v17/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1121,7 +1116,7 @@ return { "concreteType": "PullRequestReviewCommentEdge", "plural": true, "selections": [ - v18, + (v18/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1131,9 +1126,9 @@ return { "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v6, - v24, - v25, + (v6/*: any*/), + (v24/*: any*/), + (v25/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1141,8 +1136,8 @@ return { "args": null, "storageKey": null }, - v26, - v27, + (v26/*: any*/), + (v27/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1152,12 +1147,12 @@ return { "concreteType": "PullRequestReviewComment", "plural": false, "selections": [ - v6 + (v6/*: any*/) ] }, - v28, - v13, - v5 + (v28/*: any*/), + (v13/*: any*/), + (v5/*: any*/) ] } ] @@ -1168,12 +1163,12 @@ return { "kind": "LinkedHandle", "alias": null, "name": "comments", - "args": v22, + "args": (v22/*: any*/), "handle": "connection", "key": "PrReviewCommentsContainer_comments", "filters": null }, - v5 + (v5/*: any*/) ] } ] @@ -1184,7 +1179,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "reviews", - "args": v14, + "args": (v14/*: any*/), "handle": "connection", "key": "PrReviewsContainer_reviews", "filters": null @@ -1194,11 +1189,11 @@ return { "alias": null, "name": "commits", "storageKey": null, - "args": v29, + "args": (v29/*: any*/), "concreteType": "PullRequestCommitConnection", "plural": false, "selections": [ - v30, + (v30/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1208,7 +1203,7 @@ return { "concreteType": "PullRequestCommitEdge", "plural": true, "selections": [ - v18, + (v18/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1227,7 +1222,7 @@ return { "concreteType": "Commit", "plural": false, "selections": [ - v6, + (v6/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1237,8 +1232,8 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v21, - v7, + (v21/*: any*/), + (v7/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1269,12 +1264,12 @@ return { "args": null, "storageKey": null }, - v31, - v13 + (v31/*: any*/), + (v13/*: any*/) ] }, - v6, - v5 + (v6/*: any*/), + (v5/*: any*/) ] } ] @@ -1285,7 +1280,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "commits", - "args": v29, + "args": (v29/*: any*/), "handle": "connection", "key": "prCommitsView_commits", "filters": null @@ -1298,7 +1293,7 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v32 + "selections": (v32/*: any*/) }, { "kind": "LinkedField", @@ -1352,7 +1347,7 @@ return { "concreteType": "Status", "plural": false, "selections": [ - v20, + (v20/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1362,8 +1357,8 @@ return { "concreteType": "StatusContext", "plural": true, "selections": [ - v6, - v20, + (v6/*: any*/), + (v20/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1387,22 +1382,22 @@ return { } ] }, - v6 + (v6/*: any*/) ] }, - v6 + (v6/*: any*/) ] }, - v6 + (v6/*: any*/) ] } ] } ] }, - v20, - v33, - v25, + (v20/*: any*/), + (v33/*: any*/), + (v25/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1426,19 +1421,19 @@ return { "concreteType": null, "plural": false, "selections": [ - v5, - v8, - v21, - v6, + (v5/*: any*/), + (v8/*: any*/), + (v21/*: any*/), + (v6/*: any*/), { "kind": "InlineFragment", "type": "Bot", - "selections": v34 + "selections": (v34/*: any*/) }, { "kind": "InlineFragment", "type": "User", - "selections": v34 + "selections": (v34/*: any*/) } ] }, @@ -1450,7 +1445,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v9 + "selections": (v9/*: any*/) }, { "kind": "LinkedField", @@ -1461,8 +1456,8 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v10, - v6 + (v10/*: any*/), + (v6/*: any*/) ] }, { @@ -1470,11 +1465,11 @@ return { "alias": null, "name": "timeline", "storageKey": null, - "args": v35, + "args": (v35/*: any*/), "concreteType": "PullRequestTimelineConnection", "plural": false, "selections": [ - v30, + (v30/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1484,7 +1479,7 @@ return { "concreteType": "PullRequestTimelineItemEdge", "plural": true, "selections": [ - v18, + (v18/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1494,8 +1489,8 @@ return { "concreteType": null, "plural": false, "selections": [ - v5, - v6, + (v5/*: any*/), + (v6/*: any*/), { "kind": "InlineFragment", "type": "CrossReferencedEvent", @@ -1507,7 +1502,7 @@ return { "args": null, "storageKey": null }, - v12, + (v12/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1516,7 +1511,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v36 + "selections": (v36/*: any*/) }, { "kind": "LinkedField", @@ -1527,7 +1522,7 @@ return { "concreteType": null, "plural": false, "selections": [ - v5, + (v5/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1537,9 +1532,9 @@ return { "concreteType": "Repository", "plural": false, "selections": [ - v7, - v10, - v6, + (v7/*: any*/), + (v10/*: any*/), + (v6/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1549,14 +1544,14 @@ return { } ] }, - v6, + (v6/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v11, - v33, - v13, + (v11/*: any*/), + (v33/*: any*/), + (v13/*: any*/), { "kind": "ScalarField", "alias": "prState", @@ -1570,9 +1565,9 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v11, - v33, - v13, + (v11/*: any*/), + (v33/*: any*/), + (v13/*: any*/), { "kind": "ScalarField", "alias": "issueState", @@ -1590,7 +1585,7 @@ return { "kind": "InlineFragment", "type": "CommitCommentThread", "selections": [ - v37, + (v37/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1625,7 +1620,7 @@ return { "concreteType": "CommitComment", "plural": false, "selections": [ - v6, + (v6/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1634,13 +1629,13 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v36 + "selections": (v36/*: any*/) }, - v37, - v25, - v28, - v26, - v27 + (v37/*: any*/), + (v25/*: any*/), + (v28/*: any*/), + (v26/*: any*/), + (v27/*: any*/) ] } ] @@ -1653,7 +1648,7 @@ return { "kind": "InlineFragment", "type": "HeadRefForcePushedEvent", "selections": [ - v38, + (v38/*: any*/), { "kind": "LinkedField", "alias": null, @@ -1662,7 +1657,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v19 + "selections": (v19/*: any*/) }, { "kind": "LinkedField", @@ -1672,17 +1667,17 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v19 + "selections": (v19/*: any*/) }, - v28 + (v28/*: any*/) ] }, { "kind": "InlineFragment", "type": "MergedEvent", "selections": [ - v38, - v37, + (v38/*: any*/), + (v37/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1690,17 +1685,17 @@ return { "args": null, "storageKey": null }, - v28 + (v28/*: any*/) ] }, { "kind": "InlineFragment", "type": "IssueComment", "selections": [ - v24, - v25, - v28, - v13 + (v24/*: any*/), + (v25/*: any*/), + (v28/*: any*/), + (v13/*: any*/) ] }, { @@ -1716,9 +1711,9 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v7, - v39, - v21 + (v7/*: any*/), + (v39/*: any*/), + (v21/*: any*/) ] }, { @@ -1730,9 +1725,9 @@ return { "concreteType": "GitActor", "plural": false, "selections": [ - v7, - v21, - v39 + (v7/*: any*/), + (v21/*: any*/), + (v39/*: any*/) ] }, { @@ -1742,7 +1737,7 @@ return { "args": null, "storageKey": null }, - v31, + (v31/*: any*/), { "kind": "ScalarField", "alias": null, @@ -1776,7 +1771,7 @@ return { "kind": "LinkedHandle", "alias": null, "name": "timeline", - "args": v35, + "args": (v35/*: any*/), "handle": "connection", "key": "prTimelineContainer_timeline", "filters": null @@ -1805,7 +1800,7 @@ return { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v32 + "selections": (v32/*: any*/) } ] } @@ -1814,6 +1809,13 @@ return { ] } ] + }, + "params": { + "operationKind": "query", + "name": "prDetailViewRefetchQuery", + "id": null, + "text": "query prDetailViewRefetchQuery(\n $repoId: ID!\n $issueishId: ID!\n $timelineCount: Int!\n $timelineCursor: String\n $commitCount: Int!\n $commitCursor: String\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository: node(id: $repoId) {\n __typename\n ...prDetailView_repository_3D8CP9\n id\n }\n pullRequest: node(id: $issueishId) {\n __typename\n ...prDetailView_pullRequest_2qM2KL\n id\n }\n}\n\nfragment prDetailView_repository_3D8CP9 on Repository {\n id\n name\n owner {\n __typename\n login\n id\n }\n}\n\nfragment prDetailView_pullRequest_2qM2KL on PullRequest {\n __typename\n ... on Node {\n id\n }\n isCrossRepository\n changedFiles\n ...prReviewsContainer_pullRequest_y4qc0\n ...prCommitsView_pullRequest_38TpXw\n countedCommits: commits {\n totalCount\n }\n ...prStatusesView_pullRequest\n state\n number\n title\n bodyHTML\n baseRefName\n headRefName\n author {\n __typename\n login\n avatarUrl\n ... on User {\n url\n }\n ... on Bot {\n url\n }\n ... on Node {\n id\n }\n }\n ...prTimelineController_pullRequest_3D8CP9\n ... on UniformResourceLocatable {\n url\n }\n ... on Reactable {\n reactionGroups {\n content\n users {\n totalCount\n }\n }\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prCommitsView_pullRequest_38TpXw on PullRequest {\n url\n commits(first: $commitCount, after: $commitCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n commit {\n id\n ...prCommitView_item\n }\n id\n __typename\n }\n }\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prTimelineController_pullRequest_3D8CP9 on PullRequest {\n url\n ...headRefForcePushedEventView_issueish\n timeline(first: $timelineCount, after: $timelineCursor) {\n pageInfo {\n endCursor\n hasNextPage\n }\n edges {\n cursor\n node {\n __typename\n ...commitsView_nodes\n ...issueCommentView_item\n ...mergedEventView_item\n ...headRefForcePushedEventView_item\n ...commitCommentThreadView_item\n ...crossReferencedEventsView_nodes\n ... on Node {\n id\n }\n }\n }\n }\n}\n\nfragment headRefForcePushedEventView_issueish on PullRequest {\n headRefName\n headRepositoryOwner {\n __typename\n login\n id\n }\n repository {\n owner {\n __typename\n login\n id\n }\n id\n }\n}\n\nfragment commitsView_nodes on Commit {\n id\n author {\n name\n user {\n login\n id\n }\n }\n ...commitView_commit\n}\n\nfragment issueCommentView_item on IssueComment {\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n createdAt\n url\n}\n\nfragment mergedEventView_item on MergedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n mergeRefName\n createdAt\n}\n\nfragment headRefForcePushedEventView_item on HeadRefForcePushedEvent {\n actor {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n beforeCommit {\n oid\n id\n }\n afterCommit {\n oid\n id\n }\n createdAt\n}\n\nfragment commitCommentThreadView_item on CommitCommentThread {\n commit {\n oid\n id\n }\n comments(first: 100) {\n edges {\n node {\n id\n ...commitCommentView_item\n }\n }\n }\n}\n\nfragment crossReferencedEventsView_nodes on CrossReferencedEvent {\n id\n referencedAt\n isCrossRepository\n actor {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n source {\n __typename\n ... on RepositoryNode {\n repository {\n name\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n ...crossReferencedEventView_item\n}\n\nfragment crossReferencedEventView_item on CrossReferencedEvent {\n id\n isCrossRepository\n source {\n __typename\n ... on Issue {\n number\n title\n url\n issueState: state\n }\n ... on PullRequest {\n number\n title\n url\n prState: state\n }\n ... on RepositoryNode {\n repository {\n name\n isPrivate\n owner {\n __typename\n login\n id\n }\n id\n }\n }\n ... on Node {\n id\n }\n }\n}\n\nfragment commitCommentView_item on CommitComment {\n author {\n __typename\n login\n avatarUrl\n ... on Node {\n id\n }\n }\n commit {\n oid\n id\n }\n bodyHTML\n createdAt\n path\n position\n}\n\nfragment commitView_commit on Commit {\n author {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n committer {\n name\n avatarUrl\n user {\n login\n id\n }\n }\n authoredByCommitter\n sha: oid\n message\n messageHeadlineHTML\n commitUrl\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n\nfragment prCommitView_item on Commit {\n committer {\n avatarUrl\n name\n date\n }\n messageHeadline\n messageBody\n shortSha: abbreviatedOid\n sha: oid\n url\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", + "metadata": {} } }; })(); diff --git a/lib/views/__generated__/prDetailView_pullRequest.graphql.js b/lib/views/__generated__/prDetailView_pullRequest.graphql.js index 399ae2333d..ff7516fd2a 100644 --- a/lib/views/__generated__/prDetailView_pullRequest.graphql.js +++ b/lib/views/__generated__/prDetailView_pullRequest.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; type prCommitsView_pullRequest$ref = any; type prReviewsContainer_pullRequest$ref = any; type prStatusesView_pullRequest$ref = any; @@ -48,7 +48,7 @@ export type prDetailView_pullRequest = {| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = [ { "kind": "ScalarField", @@ -66,7 +66,7 @@ v1 = { "storageKey": null }, v2 = [ - v1 + (v1/*: any*/) ]; return { "kind": "Fragment", @@ -208,7 +208,7 @@ return { "args": null, "concreteType": "PullRequestCommitConnection", "plural": false, - "selections": v0 + "selections": (v0/*: any*/) }, { "kind": "FragmentSpread", @@ -283,12 +283,12 @@ return { { "kind": "InlineFragment", "type": "Bot", - "selections": v2 + "selections": (v2/*: any*/) }, { "kind": "InlineFragment", "type": "User", - "selections": v2 + "selections": (v2/*: any*/) } ] }, @@ -310,7 +310,7 @@ return { } ] }, - v1, + (v1/*: any*/), { "kind": "LinkedField", "alias": null, @@ -335,7 +335,7 @@ return { "args": null, "concreteType": "ReactingUserConnection", "plural": false, - "selections": v0 + "selections": (v0/*: any*/) } ] } diff --git a/lib/views/__generated__/prDetailView_repository.graphql.js b/lib/views/__generated__/prDetailView_repository.graphql.js index 22a7c77d8b..69e6a95e8f 100644 --- a/lib/views/__generated__/prDetailView_repository.graphql.js +++ b/lib/views/__generated__/prDetailView_repository.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; import type { FragmentReference } from "relay-runtime"; declare export opaque type prDetailView_repository$ref: FragmentReference; export type prDetailView_repository = {| @@ -21,7 +21,7 @@ export type prDetailView_repository = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "prDetailView_repository", "type": "Repository", diff --git a/lib/views/__generated__/prStatusContextView_context.graphql.js b/lib/views/__generated__/prStatusContextView_context.graphql.js index c85134d3fa..0172100a92 100644 --- a/lib/views/__generated__/prStatusContextView_context.graphql.js +++ b/lib/views/__generated__/prStatusContextView_context.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; export type StatusState = "ERROR" | "EXPECTED" | "FAILURE" | "PENDING" | "SUCCESS" | "%future added value"; import type { FragmentReference } from "relay-runtime"; declare export opaque type prStatusContextView_context$ref: FragmentReference; @@ -21,7 +21,7 @@ export type prStatusContextView_context = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "prStatusContextView_context", "type": "StatusContext", diff --git a/lib/views/__generated__/prStatusesViewRefetchQuery.graphql.js b/lib/views/__generated__/prStatusesViewRefetchQuery.graphql.js index ae88ff6cba..0e759b9585 100644 --- a/lib/views/__generated__/prStatusesViewRefetchQuery.graphql.js +++ b/lib/views/__generated__/prStatusesViewRefetchQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash b2e4fb850c19404c7973310ca0813874 + * @relayHash 88ba0c5c281db6e27a193a18c597e5a3 */ /* eslint-disable */ @@ -102,24 +102,19 @@ v3 = { }; return { "kind": "Request", - "operationKind": "query", - "name": "prStatusesViewRefetchQuery", - "id": null, - "text": "query prStatusesViewRefetchQuery(\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on PullRequest {\n ...prStatusesView_pullRequest\n }\n id\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n", - "metadata": {}, "fragment": { "kind": "Fragment", "name": "prStatusesViewRefetchQuery", "type": "Query", "metadata": null, - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "node", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -141,14 +136,14 @@ return { "operation": { "kind": "Operation", "name": "prStatusesViewRefetchQuery", - "argumentDefinitions": v0, + "argumentDefinitions": (v0/*: any*/), "selections": [ { "kind": "LinkedField", "alias": null, "name": "node", "storageKey": null, - "args": v1, + "args": (v1/*: any*/), "concreteType": null, "plural": false, "selections": [ @@ -159,7 +154,7 @@ return { "args": null, "storageKey": null }, - v2, + (v2/*: any*/), { "kind": "InlineFragment", "type": "PullRequest", @@ -216,7 +211,7 @@ return { "concreteType": "Status", "plural": false, "selections": [ - v3, + (v3/*: any*/), { "kind": "LinkedField", "alias": null, @@ -226,8 +221,8 @@ return { "concreteType": "StatusContext", "plural": true, "selections": [ - v2, - v3, + (v2/*: any*/), + (v3/*: any*/), { "kind": "ScalarField", "alias": null, @@ -251,13 +246,13 @@ return { } ] }, - v2 + (v2/*: any*/) ] }, - v2 + (v2/*: any*/) ] }, - v2 + (v2/*: any*/) ] } ] @@ -269,6 +264,13 @@ return { ] } ] + }, + "params": { + "operationKind": "query", + "name": "prStatusesViewRefetchQuery", + "id": null, + "text": "query prStatusesViewRefetchQuery(\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ... on PullRequest {\n ...prStatusesView_pullRequest\n }\n id\n }\n}\n\nfragment prStatusesView_pullRequest on PullRequest {\n id\n recentCommits: commits(last: 1) {\n edges {\n node {\n commit {\n status {\n state\n contexts {\n id\n state\n ...prStatusContextView_context\n }\n id\n }\n id\n }\n id\n }\n }\n }\n}\n\nfragment prStatusContextView_context on StatusContext {\n context\n description\n state\n targetUrl\n}\n", + "metadata": {} } }; })(); diff --git a/lib/views/__generated__/prStatusesView_pullRequest.graphql.js b/lib/views/__generated__/prStatusesView_pullRequest.graphql.js index 558e786d20..3e87d14c42 100644 --- a/lib/views/__generated__/prStatusesView_pullRequest.graphql.js +++ b/lib/views/__generated__/prStatusesView_pullRequest.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; type prStatusContextView_context$ref = any; export type StatusState = "ERROR" | "EXPECTED" | "FAILURE" | "PENDING" | "SUCCESS" | "%future added value"; import type { FragmentReference } from "relay-runtime"; @@ -35,7 +35,7 @@ export type prStatusesView_pullRequest = {| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = { "kind": "ScalarField", "alias": null, @@ -57,7 +57,7 @@ return { "metadata": null, "argumentDefinitions": [], "selections": [ - v0, + (v0/*: any*/), { "kind": "LinkedField", "alias": "recentCommits", @@ -110,7 +110,7 @@ return { "concreteType": "Status", "plural": false, "selections": [ - v1, + (v1/*: any*/), { "kind": "LinkedField", "alias": null, @@ -120,8 +120,8 @@ return { "concreteType": "StatusContext", "plural": true, "selections": [ - v0, - v1, + (v0/*: any*/), + (v1/*: any*/), { "kind": "FragmentSpread", "name": "prStatusContextView_context", diff --git a/lib/views/timeline-items/__generated__/commitCommentThreadView_item.graphql.js b/lib/views/timeline-items/__generated__/commitCommentThreadView_item.graphql.js index 608c31f35c..212eede57d 100644 --- a/lib/views/timeline-items/__generated__/commitCommentThreadView_item.graphql.js +++ b/lib/views/timeline-items/__generated__/commitCommentThreadView_item.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; type commitCommentView_item$ref = any; import type { FragmentReference } from "relay-runtime"; declare export opaque type commitCommentThreadView_item$ref: FragmentReference; @@ -28,7 +28,7 @@ export type commitCommentThreadView_item = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "commitCommentThreadView_item", "type": "CommitCommentThread", diff --git a/lib/views/timeline-items/__generated__/commitCommentView_item.graphql.js b/lib/views/timeline-items/__generated__/commitCommentView_item.graphql.js index 9d47e87acf..85b1fc53d2 100644 --- a/lib/views/timeline-items/__generated__/commitCommentView_item.graphql.js +++ b/lib/views/timeline-items/__generated__/commitCommentView_item.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; import type { FragmentReference } from "relay-runtime"; declare export opaque type commitCommentView_item$ref: FragmentReference; export type commitCommentView_item = {| @@ -27,7 +27,7 @@ export type commitCommentView_item = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "commitCommentView_item", "type": "CommitComment", diff --git a/lib/views/timeline-items/__generated__/commitView_commit.graphql.js b/lib/views/timeline-items/__generated__/commitView_commit.graphql.js index a011cb9432..236c16deec 100644 --- a/lib/views/timeline-items/__generated__/commitView_commit.graphql.js +++ b/lib/views/timeline-items/__generated__/commitView_commit.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; import type { FragmentReference } from "relay-runtime"; declare export opaque type commitView_commit$ref: FragmentReference; export type commitView_commit = {| @@ -35,7 +35,7 @@ export type commitView_commit = {| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = [ { "kind": "ScalarField", @@ -85,7 +85,7 @@ return { "args": null, "concreteType": "GitActor", "plural": false, - "selections": v0 + "selections": (v0/*: any*/) }, { "kind": "LinkedField", @@ -95,7 +95,7 @@ return { "args": null, "concreteType": "GitActor", "plural": false, - "selections": v0 + "selections": (v0/*: any*/) }, { "kind": "ScalarField", diff --git a/lib/views/timeline-items/__generated__/commitsView_nodes.graphql.js b/lib/views/timeline-items/__generated__/commitsView_nodes.graphql.js index 8b4c04b559..abc10f01d0 100644 --- a/lib/views/timeline-items/__generated__/commitsView_nodes.graphql.js +++ b/lib/views/timeline-items/__generated__/commitsView_nodes.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; type commitView_commit$ref = any; import type { FragmentReference } from "relay-runtime"; declare export opaque type commitsView_nodes$ref: FragmentReference; @@ -25,7 +25,7 @@ export type commitsView_nodes = $ReadOnlyArray<{| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "commitsView_nodes", "type": "Commit", diff --git a/lib/views/timeline-items/__generated__/crossReferencedEventView_item.graphql.js b/lib/views/timeline-items/__generated__/crossReferencedEventView_item.graphql.js index aa95dffc61..a3a3294803 100644 --- a/lib/views/timeline-items/__generated__/crossReferencedEventView_item.graphql.js +++ b/lib/views/timeline-items/__generated__/crossReferencedEventView_item.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; export type IssueState = "CLOSED" | "OPEN" | "%future added value"; export type PullRequestState = "CLOSED" | "MERGED" | "OPEN" | "%future added value"; import type { FragmentReference } from "relay-runtime"; @@ -35,7 +35,7 @@ export type crossReferencedEventView_item = {| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = { "kind": "ScalarField", "alias": null, @@ -141,9 +141,9 @@ return { "kind": "InlineFragment", "type": "PullRequest", "selections": [ - v0, - v1, - v2, + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/), { "kind": "ScalarField", "alias": "prState", @@ -157,9 +157,9 @@ return { "kind": "InlineFragment", "type": "Issue", "selections": [ - v0, - v1, - v2, + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/), { "kind": "ScalarField", "alias": "issueState", diff --git a/lib/views/timeline-items/__generated__/crossReferencedEventsView_nodes.graphql.js b/lib/views/timeline-items/__generated__/crossReferencedEventsView_nodes.graphql.js index de1bcffdd1..0934fe001f 100644 --- a/lib/views/timeline-items/__generated__/crossReferencedEventsView_nodes.graphql.js +++ b/lib/views/timeline-items/__generated__/crossReferencedEventsView_nodes.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; type crossReferencedEventView_item$ref = any; import type { FragmentReference } from "relay-runtime"; declare export opaque type crossReferencedEventsView_nodes$ref: FragmentReference; @@ -34,7 +34,7 @@ export type crossReferencedEventsView_nodes = $ReadOnlyArray<{| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = { "kind": "ScalarField", "alias": null, @@ -81,7 +81,7 @@ return { "concreteType": null, "plural": false, "selections": [ - v0, + (v0/*: any*/), { "kind": "ScalarField", "alias": null, @@ -132,7 +132,7 @@ return { "concreteType": null, "plural": false, "selections": [ - v0 + (v0/*: any*/) ] } ] diff --git a/lib/views/timeline-items/__generated__/headRefForcePushedEventView_issueish.graphql.js b/lib/views/timeline-items/__generated__/headRefForcePushedEventView_issueish.graphql.js index 096a04ceac..e5b5daf4ea 100644 --- a/lib/views/timeline-items/__generated__/headRefForcePushedEventView_issueish.graphql.js +++ b/lib/views/timeline-items/__generated__/headRefForcePushedEventView_issueish.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; import type { FragmentReference } from "relay-runtime"; declare export opaque type headRefForcePushedEventView_issueish$ref: FragmentReference; export type headRefForcePushedEventView_issueish = {| @@ -25,7 +25,7 @@ export type headRefForcePushedEventView_issueish = {| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = [ { "kind": "ScalarField", @@ -57,7 +57,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v0 + "selections": (v0/*: any*/) }, { "kind": "LinkedField", @@ -76,7 +76,7 @@ return { "args": null, "concreteType": null, "plural": false, - "selections": v0 + "selections": (v0/*: any*/) } ] } diff --git a/lib/views/timeline-items/__generated__/headRefForcePushedEventView_item.graphql.js b/lib/views/timeline-items/__generated__/headRefForcePushedEventView_item.graphql.js index ee92dc785c..7d6cfdfca4 100644 --- a/lib/views/timeline-items/__generated__/headRefForcePushedEventView_item.graphql.js +++ b/lib/views/timeline-items/__generated__/headRefForcePushedEventView_item.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; import type { FragmentReference } from "relay-runtime"; declare export opaque type headRefForcePushedEventView_item$ref: FragmentReference; export type headRefForcePushedEventView_item = {| @@ -27,7 +27,7 @@ export type headRefForcePushedEventView_item = {| */ -const node/*: ConcreteFragment*/ = (function(){ +const node/*: ReaderFragment*/ = (function(){ var v0 = [ { "kind": "ScalarField", @@ -77,7 +77,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v0 + "selections": (v0/*: any*/) }, { "kind": "LinkedField", @@ -87,7 +87,7 @@ return { "args": null, "concreteType": "Commit", "plural": false, - "selections": v0 + "selections": (v0/*: any*/) }, { "kind": "ScalarField", diff --git a/lib/views/timeline-items/__generated__/issueCommentView_item.graphql.js b/lib/views/timeline-items/__generated__/issueCommentView_item.graphql.js index 03c065e29b..515ea0c0be 100644 --- a/lib/views/timeline-items/__generated__/issueCommentView_item.graphql.js +++ b/lib/views/timeline-items/__generated__/issueCommentView_item.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; import type { FragmentReference } from "relay-runtime"; declare export opaque type issueCommentView_item$ref: FragmentReference; export type issueCommentView_item = {| @@ -23,7 +23,7 @@ export type issueCommentView_item = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "issueCommentView_item", "type": "IssueComment", diff --git a/lib/views/timeline-items/__generated__/mergedEventView_item.graphql.js b/lib/views/timeline-items/__generated__/mergedEventView_item.graphql.js index 1d1fa6d560..9532181e66 100644 --- a/lib/views/timeline-items/__generated__/mergedEventView_item.graphql.js +++ b/lib/views/timeline-items/__generated__/mergedEventView_item.graphql.js @@ -7,7 +7,7 @@ 'use strict'; /*:: -import type { ConcreteFragment } from 'relay-runtime'; +import type { ReaderFragment } from 'relay-runtime'; import type { FragmentReference } from "relay-runtime"; declare export opaque type mergedEventView_item$ref: FragmentReference; export type mergedEventView_item = {| @@ -25,7 +25,7 @@ export type mergedEventView_item = {| */ -const node/*: ConcreteFragment*/ = { +const node/*: ReaderFragment*/ = { "kind": "Fragment", "name": "mergedEventView_item", "type": "MergedEvent", From 5edc2397a05b8cf1a47013c0c29fe35d115beccd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 25 Feb 2019 15:18:47 -0500 Subject: [PATCH 2107/4053] Update transpiler link in CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 389fefee47..54ab8637bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -162,7 +162,7 @@ We use the following technologies: * We interact with GitHub via its [GraphQL](https://graphql.org/) API. * [Relay](https://github.com/facebook/relay) is a layer of glue between React and GraphQL queries that handles responsibilities like query composition and caching. * Our tests are written with [Mocha](https://mochajs.org/) and [Chai](https://www.chaijs.com/) [_(with the "assert" style)_](https://www.chaijs.com/api/assert/). We also use [Enzyme](https://airbnb.io/enzyme/) to assert against React behavior. -* We use a [custom Babel 6 transpiler pipeline](https://github.com/atom/atom-babel6-transpiler) to write modern source with JSX, `import` statements, and other constructs unavailable natively within Atom's Node.js version. +* We use a [custom Babel 7 transpiler pipeline](https://github.com/atom/atom-babel7-transpiler) to write modern source with JSX, `import` statements, and other constructs unavailable natively within Atom's Node.js version. ### Updating the GraphQL Schema From 53dfb0ecc6db7281aa3bbfe498a26d6c48f12b83 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 25 Feb 2019 15:19:01 -0500 Subject: [PATCH 2108/4053] Update manual transpiler import --- test/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helpers.js b/test/helpers.js index ba2a7c9897..aeb6e88a62 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -2,7 +2,7 @@ import fs from 'fs-extra'; import path from 'path'; import temp from 'temp'; import until from 'test-until'; -import transpiler from 'atom-babel6-transpiler'; +import transpiler from '@atom/babel7-transpiler'; import React from 'react'; import ReactDom from 'react-dom'; From 5279efbc5517877e2e39bb92cf571365d80cb1e0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 25 Feb 2019 16:26:20 -0500 Subject: [PATCH 2109/4053] Move to the @atom fork of babel-plugin-chai-assert-async --- .babelrc.js | 4 ++-- package-lock.json | 13 ++++++++----- package.json | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.babelrc.js b/.babelrc.js index c3189debec..890087a771 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -1,9 +1,9 @@ module.exports = { sourceMaps: "inline", plugins: [ - "relay", + "babel-plugin-relay", "./assert-messages-plugin.js", - "babel-plugin-chai-assert-async", + "@atom/babel-plugin-chai-assert-async", "@babel/plugin-proposal-class-properties", ], presets: [ diff --git a/package-lock.json b/package-lock.json index 22b705408d..f7dc540a00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,14 @@ "integrity": "sha1-nK+xca+CMpSQNTtIFvAzR6oVCjA=", "dev": true }, + "@atom/babel-plugin-chai-assert-async": { + "version": "1.0.0-0", + "resolved": "https://registry.npmjs.org/@atom/babel-plugin-chai-assert-async/-/babel-plugin-chai-assert-async-1.0.0-0.tgz", + "integrity": "sha512-Hh0JrFu7THfCVJEudceu92KR0vaTXHx6QW2kWdZiUnfOXFskc55TUCXJZ39tdEIX14HQboVBYr+EM5LzPV714w==", + "requires": { + "@babel/helper-module-imports": "7.0.0" + } + }, "@atom/babel7-transpiler": { "version": "1.0.0-1", "resolved": "https://registry.npmjs.org/@atom/babel7-transpiler/-/babel7-transpiler-1.0.0-1.tgz", @@ -1635,11 +1643,6 @@ "babel-runtime": "^6.22.0" } }, - "babel-plugin-chai-assert-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-chai-assert-async/-/babel-plugin-chai-assert-async-0.1.0.tgz", - "integrity": "sha1-pJMXc79WPcKt3mzXm28ZRknr/SU=" - }, "babel-plugin-istanbul": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.1.tgz", diff --git a/package.json b/package.json index 1b09706fbe..46664d0eb0 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,12 @@ } ], "dependencies": { + "@atom/babel-plugin-chai-assert-async": "1.0.0-0", "@atom/babel7-transpiler": "1.0.0-1", "@babel/generator": "7.3.4", "@babel/plugin-proposal-class-properties": "7.3.4", "@babel/preset-env": "7.3.4", "@babel/preset-react": "7.0.0", - "babel-plugin-chai-assert-async": "0.1.0", "babel-plugin-relay": "3.0.0", "bintrees": "1.0.2", "bytes": "^3.0.0", From 89f3f2f3aa004b44d67e2aa613585e5470e88af8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 25 Feb 2019 16:32:01 -0500 Subject: [PATCH 2110/4053] Depend on stable release of @atom/babel-plugin-chai-assert-async --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index f7dc540a00..cff59613eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,9 @@ "dev": true }, "@atom/babel-plugin-chai-assert-async": { - "version": "1.0.0-0", - "resolved": "https://registry.npmjs.org/@atom/babel-plugin-chai-assert-async/-/babel-plugin-chai-assert-async-1.0.0-0.tgz", - "integrity": "sha512-Hh0JrFu7THfCVJEudceu92KR0vaTXHx6QW2kWdZiUnfOXFskc55TUCXJZ39tdEIX14HQboVBYr+EM5LzPV714w==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@atom/babel-plugin-chai-assert-async/-/babel-plugin-chai-assert-async-1.0.0.tgz", + "integrity": "sha512-YGYfZkFzMfw/fa/vVivqSMJQPN/wbReg6ikTq53/CDsN3aZgtdWKwYOQThExN0GvrgXsTGqmZl5uWs1hccKE5w==", "requires": { "@babel/helper-module-imports": "7.0.0" } diff --git a/package.json b/package.json index 46664d0eb0..0494eeb9e6 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ } ], "dependencies": { - "@atom/babel-plugin-chai-assert-async": "1.0.0-0", + "@atom/babel-plugin-chai-assert-async": "1.0.0", "@atom/babel7-transpiler": "1.0.0-1", "@babel/generator": "7.3.4", "@babel/plugin-proposal-class-properties": "7.3.4", From 95900e48767ce4ebd4d01e31ca5996516193afb5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 25 Feb 2019 16:51:26 -0500 Subject: [PATCH 2111/4053] "errors" is a top-level key within the payload, not a child of "data" --- lib/relay-network-layer-manager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/relay-network-layer-manager.js b/lib/relay-network-layer-manager.js index fd6abf2861..a79d8190f2 100644 --- a/lib/relay-network-layer-manager.js +++ b/lib/relay-network-layer-manager.js @@ -118,10 +118,10 @@ function createFetchQuery(url) { const payload = await response.json(); - if (payload.data && payload.data.errors && payload.data.errors.length > 0) { + if (payload && payload.errors && payload.errors.length > 0) { const e = new Error(`GraphQL API endpoint at ${url} returned an error for query ${operation.name}.`); e.response = response; - e.errors = payload.data.errors; + e.errors = payload.errors; e.rawStack = e.stack; throw e; } From c86cbf0ba7bee55973c8fe74840dba5a0fb2941d Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Mon, 25 Feb 2019 19:39:48 -0800 Subject: [PATCH 2112/4053] Fix broken file patch symlink test @smashwilson curious why you added the 'selected' class in fb07191163049b4e3bed017a88ac9126ddbbe205 --- test/integration/file-patch.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/file-patch.test.js b/test/integration/file-patch.test.js index 63d3746cfc..b000e59790 100644 --- a/test/integration/file-patch.test.js +++ b/test/integration/file-patch.test.js @@ -514,7 +514,7 @@ describe('integration: file patches', function() { await patchContent( 'unstaged', 'sample.js', [repoPath('target.txt'), 'added', 'selected'], - [' No newline at end of file', 'nonewline', 'selected'], + [' No newline at end of file', 'nonewline'], ); assert.isTrue(getPatchItem('unstaged', 'sample.js').find('.github-FilePatchView-metaTitle').exists()); From e5fd7e891e1d84e7baae04bdcb8ced307a3be9a0 Mon Sep 17 00:00:00 2001 From: simurai Date: Tue, 26 Feb 2019 14:02:45 +0900 Subject: [PATCH 2113/4053] Add right padding to comments --- styles/pr-comment.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/pr-comment.less b/styles/pr-comment.less index f71f9f097f..cec3c1c8b0 100644 --- a/styles/pr-comment.less +++ b/styles/pr-comment.less @@ -5,7 +5,7 @@ .github-PrCommentThread { - padding: @component-padding 0; + padding: @component-padding @component-padding @component-padding 0; } From 68f8513854b20c70693348292969551d3913784a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 08:34:07 -0500 Subject: [PATCH 2114/4053] Explicitly enable object-rest-spread plugin We need this because esprima, the JavaScript parser used by electron-link, only supports ES2017, even though our current Electron version supports some ES2018 features. Without this plugin enabled, `npm run test:snapshot` fails with an esprima error. --- .babelrc.js | 5 ++++- package-lock.json | 7 +++---- package.json | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.babelrc.js b/.babelrc.js index 890087a771..b4b117e5d1 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -5,10 +5,13 @@ module.exports = { "./assert-messages-plugin.js", "@atom/babel-plugin-chai-assert-async", "@babel/plugin-proposal-class-properties", + + // Needed for esprima + "@babel/plugin-proposal-object-rest-spread", ], presets: [ ["@babel/preset-env", { - "targets": {"electron": process.versions.electron} + targets: {electron: process.versions.electron} }], "@babel/preset-react" ], diff --git a/package-lock.json b/package-lock.json index 2cf6f9c2a9..8b3ce3a84d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -544,10 +544,9 @@ } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.2.tgz", - "integrity": "sha512-DjeMS+J2+lpANkYLLO+m6GjoTMygYglKmRe6cDTbFv3L9i6mmiE8fe6B8MtCSLZpVXscD5kn7s6SgtHrDoBWoA==", - "dev": true, + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.4.tgz", + "integrity": "sha512-j7VQmbbkA+qrzNqbKHrBsW3ddFnOeva6wzSe/zB7T+xaxGc+RCpwo44wCmRixAIGRoIpmVgvzFzNJqQcO3/9RA==", "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-syntax-object-rest-spread": "^7.2.0" diff --git a/package.json b/package.json index 0cf3acf591..192044789b 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@atom/babel7-transpiler": "1.0.0-1", "@babel/generator": "7.3.4", "@babel/plugin-proposal-class-properties": "7.3.4", + "@babel/plugin-proposal-object-rest-spread": "7.3.4", "@babel/preset-env": "7.3.4", "@babel/preset-react": "7.0.0", "babel-plugin-relay": "3.0.0", From f8f041f9af09274df2c199104b0bcfddf8c82a95 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 10:14:22 -0500 Subject: [PATCH 2115/4053] Construct symlink-only patch bodies consistently with git At some point, git changed the way that it generated patches containing only changes related to symlink targets. This caused some odd, but ultimately harmless, discrepancies in our artificial patches. This brings our diff builder up to date with the behavior I'm observing with the current git: ``` $ git --version git version 2.21.0 $ git diff --no-prefix --no-ext-diff --no-renames --staged \ --diff-filter=u -- symlink-only-change.txt diff --git symlink-only-change.txt symlink-only-change.txt index a42ffcb..afef8f7 120000 --- symlink-only-change.txt +++ symlink-only-change.txt @@ -1 +1 @@ -lorem.md \ No newline at end of file +zzz.txt \ No newline at end of file ``` --- test/builder/patch.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/builder/patch.js b/test/builder/patch.js index bdc3046373..73a6819aeb 100644 --- a/test/builder/patch.js +++ b/test/builder/patch.js @@ -124,13 +124,12 @@ class FilePatchBuilder { const hb = new HunkBuilder(); if (this._oldSymlink !== null) { - hb.unchanged(this._oldSymlink); - } - if (this._oldSymlink !== null && this._newSymlink !== null) { - hb.unchanged('--'); + hb.deleted(this._oldSymlink); + hb.noNewline(); } if (this._newSymlink !== null) { - hb.unchanged(this._newSymlink); + hb.added(this._newSymlink); + hb.noNewline(); } rawPatch.hunks = [hb.build().raw]; From 50f0514356743c94a7138e90ea9d5e8194515e00 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 10:21:35 -0500 Subject: [PATCH 2116/4053] Render file header decorations with position: before for any empty patch When the final FilePatch within a MultiFilePatch is a mode change or symlink change diff with no content, the final patch's header content was incorrectly being placed in a Decoration with `position: after`. This broke the order in which the Decorations were rendered, because the `order` property is only respected within the set of block decorations at a given screen row that have the same position. This manifested in a glitch when collapsing and expanding FilePatches that include at least one with an empty mode change. --- lib/views/multi-file-patch-view.js | 3 +- test/views/multi-file-patch-view.test.js | 118 ++++++++++++++++------- 2 files changed, 87 insertions(+), 34 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 989ccf14e7..235d7de49c 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -391,8 +391,9 @@ export default class MultiFilePatchView extends React.Component { renderFilePatchDecorations = (filePatch, index) => { const isCollapsed = !filePatch.getRenderStatus().isVisible(); + const isEmpty = filePatch.getMarker().getRange().isEmpty(); const atEnd = filePatch.getStartRange().start.isEqual(this.props.multiFilePatch.getBuffer().getEndPosition()); - const position = isCollapsed && atEnd ? 'after' : 'before'; + const position = isEmpty && atEnd ? 'after' : 'before'; return ( diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index e96c3df4bc..4c057b5b22 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -88,44 +88,96 @@ describe('MultiFilePatchView', function() { assert.isTrue(wrapper.find('FilePatchHeaderView').exists()); }); - it('renders a final collapsed file header with position: after', function() { - const {multiFilePatch} = multiFilePatchBuilder() - .addFilePatch(fp => { - fp.renderStatus(COLLAPSED); - fp.setOldFile(f => f.path('0.txt')); - fp.addHunk(h => h.unchanged('a-0').added('a-1').unchanged('a-2')); - }) - .addFilePatch(fp => { - fp.setOldFile(f => f.path('1.txt')); - fp.addHunk(h => h.unchanged('b-0').added('b-1').unchanged('b-2')); - }) - .addFilePatch(fp => { - fp.renderStatus(COLLAPSED); - fp.setOldFile(f => f.path('2.txt')); - fp.addHunk(h => h.unchanged('c-0').added('c-1').unchanged('c-2')); - }) - .addFilePatch(fp => { - fp.setOldFile(f => f.path('3.txt')); - fp.addHunk(h => h.unchanged('d-0').added('d-1').unchanged('d-2')); - }) - .addFilePatch(fp => { - fp.renderStatus(COLLAPSED); - fp.setOldFile(f => f.path('4.txt')); - fp.addHunk(h => h.unchanged('e-0').added('e-1').unchanged('e-2')); - }) - .build(); - - const wrapper = shallow(buildApp({multiFilePatch})); + describe('file header decoration positioning', function() { + let wrapper; function decorationForFileHeader(fileName) { return wrapper.find('Decoration').filterWhere(dw => dw.exists(`FilePatchHeaderView[relPath="${fileName}"]`)); } - assert.strictEqual(decorationForFileHeader('0.txt').prop('position'), 'before'); - assert.strictEqual(decorationForFileHeader('1.txt').prop('position'), 'before'); - assert.strictEqual(decorationForFileHeader('2.txt').prop('position'), 'before'); - assert.strictEqual(decorationForFileHeader('3.txt').prop('position'), 'before'); - assert.strictEqual(decorationForFileHeader('4.txt').prop('position'), 'after'); + it('renders visible file headers with position: before', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('0.txt')); + fp.addHunk(h => h.unchanged('a-0').deleted('a-1').unchanged('a-2')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('1.txt')); + fp.addHunk(h => h.unchanged('b-0').added('b-1').unchanged('b-2')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('2.txt')); + fp.addHunk(h => h.unchanged('c-0').deleted('c-1').unchanged('c-2')); + }) + .build(); + + wrapper = shallow(buildApp({multiFilePatch})); + + assert.strictEqual(decorationForFileHeader('0.txt').prop('position'), 'before'); + assert.strictEqual(decorationForFileHeader('1.txt').prop('position'), 'before'); + assert.strictEqual(decorationForFileHeader('2.txt').prop('position'), 'before'); + }); + + it('renders a final collapsed file header with position: after', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.renderStatus(COLLAPSED); + fp.setOldFile(f => f.path('0.txt')); + fp.addHunk(h => h.unchanged('a-0').added('a-1').unchanged('a-2')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('1.txt')); + fp.addHunk(h => h.unchanged('b-0').added('b-1').unchanged('b-2')); + }) + .addFilePatch(fp => { + fp.renderStatus(COLLAPSED); + fp.setOldFile(f => f.path('2.txt')); + fp.addHunk(h => h.unchanged('c-0').added('c-1').unchanged('c-2')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('3.txt')); + fp.addHunk(h => h.unchanged('d-0').added('d-1').unchanged('d-2')); + }) + .addFilePatch(fp => { + fp.renderStatus(COLLAPSED); + fp.setOldFile(f => f.path('4.txt')); + fp.addHunk(h => h.unchanged('e-0').added('e-1').unchanged('e-2')); + }) + .build(); + + wrapper = shallow(buildApp({multiFilePatch})); + + assert.strictEqual(decorationForFileHeader('0.txt').prop('position'), 'before'); + assert.strictEqual(decorationForFileHeader('1.txt').prop('position'), 'before'); + assert.strictEqual(decorationForFileHeader('2.txt').prop('position'), 'before'); + assert.strictEqual(decorationForFileHeader('3.txt').prop('position'), 'before'); + assert.strictEqual(decorationForFileHeader('4.txt').prop('position'), 'after'); + }); + + it('renders a final mode change-only file header with position: after', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('0.txt')); + fp.setNewFile(f => f.path('0.txt').executable()); + fp.empty(); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('1.txt')); + fp.addHunk(h => h.unchanged('b-0').added('b-1').unchanged('b-2')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('2.txt').executable()); + fp.setNewFile(f => f.path('2.txt')); + fp.empty(); + }) + .build(); + + wrapper = shallow(buildApp({multiFilePatch})); + + assert.strictEqual(decorationForFileHeader('0.txt').prop('position'), 'before'); + assert.strictEqual(decorationForFileHeader('1.txt').prop('position'), 'before'); + assert.strictEqual(decorationForFileHeader('2.txt').prop('position'), 'after'); + }); }); it('undoes the last discard from the file header button', function() { From f28f86507725e923ef770308c3e0816b820d07f4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 11:26:28 -0500 Subject: [PATCH 2117/4053] Log destroyed and invalid markers in inspect() output --- lib/models/patch/hunk.js | 6 ++++++ lib/models/patch/patch.js | 9 ++++++++- lib/models/patch/region.js | 9 ++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/models/patch/hunk.js b/lib/models/patch/hunk.js index c4da9a48a8..1f5535b08c 100644 --- a/lib/models/patch/hunk.js +++ b/lib/models/patch/hunk.js @@ -171,6 +171,12 @@ export default class Hunk { } let inspectString = `${indentation}(Hunk marker=${this.marker.id}\n`; + if (this.marker.isDestroyed()) { + inspectString += ' [destroyed]'; + } + if (!this.marker.isValid()) { + inspectString += ' [invalid]'; + } for (const region of this.regions) { inspectString += region.inspect({indent: options.indent + 2}); } diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index ae07554b32..a44fba121e 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -343,7 +343,14 @@ export default class Patch { indentation += ' '; } - let inspectString = `${indentation}(Patch marker=${this.marker.id}\n`; + let inspectString = `${indentation}(Patch marker=${this.marker.id}`; + if (this.marker.isDestroyed()) { + inspectString += ' [destroyed]'; + } + if (!this.marker.isValid()) { + inspectString += ' [invalid]'; + } + inspectString += '\n'; for (const hunk of this.hunks) { inspectString += hunk.inspect({indent: options.indent + 2}); } diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index 85e05dbeef..b637ea6091 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -135,7 +135,14 @@ class Region { indentation += ' '; } - return `${indentation}(${this.constructor.name} marker=${this.marker.id})\n`; + let inspectString = `${indentation}(${this.constructor.name} marker=${this.marker.id})`; + if (this.marker.isDestroyed()) { + inspectString += ' [destroyed]'; + } + if (!this.marker.isValid()) { + inspectString += ' [invalid]'; + } + return inspectString + '\n'; } isChange() { From 726e09f14a6be89fb0edf6a0eb84f03d264b8a75 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 11:29:29 -0500 Subject: [PATCH 2118/4053] PatchBuffer::extractPatchBuffer() accepts a Set of boundary Markers Automatically excluding any zero-length boundary Markers didn't account for the case in which the range you're actually extracting is empty. This was causing a bug in which Markers belonging to empty (not non-collapsed) FilePatches were being destroyed and not updated on collapse. --- lib/models/patch/patch-buffer.js | 16 +++++++--------- test/models/patch/patch-buffer.test.js | 11 ++++++++--- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 182f9c381f..44aa501989 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -59,20 +59,18 @@ export default class PatchBuffer { return this.createInserterAt(this.getInsertionPoint()); } - extractPatchBuffer(rangeLike) { + extractPatchBuffer(rangeLike, options = {}) { + const opts = { + exclude: new Set(), + ...options, + }; + const range = Range.fromObject(rangeLike); const baseOffset = range.start.negate(); const movedMarkersByLayer = LAYER_NAMES.reduce((map, layerName) => { map[layerName] = this.layers[layerName] .findMarkers({containedInRange: range}) - .filter(m => { - // Manually exclude zero-length markers at the extraction range's beginning and end. - const r = m.getRange(); - if (!r.isEmpty()) { return true; } - - const point = r.start; - return !point.isEqual(range.start) && !point.isEqual(range.end); - }); + .filter(m => !opts.exclude.has(m)); return map; }, {}); const markerMap = new Map(); diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index ca4ea294f6..29bfc2379f 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -92,15 +92,18 @@ describe('PatchBuffer', function() { assert.deepEqual(markerMap.get(m3).getRange().serialize(), [[2, 0], [3, 1]]); }); - it("does not destroy zero-length markers at the extraction range's boundaries", function() { + it("does not destroy excluded markers at the extraction range's boundaries", function() { const before0 = patchBuffer.markRange('patch', [[2, 0], [2, 0]]); const before1 = patchBuffer.markRange('patch', [[2, 0], [2, 0]]); const within0 = patchBuffer.markRange('patch', [[2, 0], [2, 1]]); const within1 = patchBuffer.markRange('patch', [[3, 0], [3, 4]]); + const within2 = patchBuffer.markRange('patch', [[4, 0], [4, 0]]); const after0 = patchBuffer.markRange('patch', [[4, 0], [4, 0]]); const after1 = patchBuffer.markRange('patch', [[4, 0], [4, 0]]); - const {patchBuffer: subPatchBuffer, markerMap} = patchBuffer.extractPatchBuffer([[2, 0], [4, 0]]); + const {patchBuffer: subPatchBuffer, markerMap} = patchBuffer.extractPatchBuffer([[2, 0], [4, 0]], { + exclude: new Set([before0, before1, after0, after1]), + }); assert.strictEqual(patchBuffer.getBuffer().getText(), dedent` 0000 @@ -125,13 +128,14 @@ describe('PatchBuffer', function() { `); assert.deepEqual( subPatchBuffer.findMarkers('patch', {}).map(m => m.getRange().serialize()), - [[[0, 0], [0, 1]], [[1, 0], [1, 4]]], + [[[0, 0], [0, 1]], [[1, 0], [1, 4]], [[2, 0], [2, 0]]], ); assert.isFalse(markerMap.has(before0)); assert.isFalse(markerMap.has(before1)); assert.isTrue(markerMap.has(within0)); assert.isTrue(markerMap.has(within1)); + assert.isTrue(markerMap.has(within2)); assert.isFalse(markerMap.has(after0)); assert.isFalse(markerMap.has(after1)); @@ -139,6 +143,7 @@ describe('PatchBuffer', function() { assert.isFalse(before1.isDestroyed()); assert.isTrue(within0.isDestroyed()); assert.isTrue(within1.isDestroyed()); + assert.isTrue(within2.isDestroyed()); assert.isFalse(after0.isDestroyed()); assert.isFalse(after1.isDestroyed()); }); From 22666a1296f94f28537320477e0ad93c456223fc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 11:31:26 -0500 Subject: [PATCH 2119/4053] Pass a set of boundary markers in FilePatch::triggerCollapseIn() This allows us to gracefully handle the case in which a FilePatch's marker is itself empty, and distinguish between empty markers we want to include in the extracted PatchBuffer and the ones we do not. --- lib/models/patch/file-patch.js | 6 +++--- test/models/patch/file-patch.test.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index b474fa1a00..b7ebbf1589 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -123,14 +123,15 @@ export default class FilePatch { return this.patch.updateMarkers(map); } - triggerCollapseIn(patchBuffer) { + triggerCollapseIn(patchBuffer, {before, after}) { if (!this.patch.getRenderStatus().isVisible()) { return false; } const oldPatch = this.patch; const insertionPosition = oldPatch.getRange().start.copy(); - const {patchBuffer: subPatchBuffer, markerMap} = patchBuffer.extractPatchBuffer(oldPatch.getRange()); + const exclude = new Set([...before, ...after]); + const {patchBuffer: subPatchBuffer, markerMap} = patchBuffer.extractPatchBuffer(oldPatch.getRange(), {exclude}); oldPatch.destroyMarkers(); oldPatch.updateMarkers(markerMap); @@ -193,7 +194,6 @@ export default class FilePatch { .apply(); } - patchBuffer .createInserterAt(this.patch.getInsertionPoint()) .keepBefore(before) diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index f5fcd52922..e2743828d5 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -683,7 +683,7 @@ describe('FilePatch', function() { assert.strictEqual(EXPANDED, filePatch.getRenderStatus()); - filePatch.triggerCollapseIn(new PatchBuffer()); + multiFilePatch.collapseFilePatch(filePatch); assert.strictEqual(COLLAPSED, filePatch.getRenderStatus()); assert.isTrue(callback.calledWith(filePatch)); @@ -695,7 +695,7 @@ describe('FilePatch', function() { fp.renderStatus(TOO_LARGE); }).build(); const filePatch = multiFilePatch.getFilePatches()[0]; - assert.isFalse(filePatch.triggerCollapseIn(new PatchBuffer())); + assert.isFalse(filePatch.triggerCollapseIn(new PatchBuffer(), {before: [], after: []})); }); it('announces the expansion of a collapsed patch', function() { From 81f6b512f06dcf4561c83b1aa11b23c54793fd00 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 11:34:49 -0500 Subject: [PATCH 2120/4053] Pass {before, after} Marker sets to FilePatch::triggerCollapseIn() This allows the PatchBuffer::extractPatchBuffer() call within the collapse operation to correctly identify which boundary markers should be included in the extracted PatchBuffer and which should not, even when the FilePatch's marker is itself empty. I've also refactored out MultiFilePatch::getMarkersBefore() and MultiFilePatch::getMarkersAfter() methods to save some duplication among triggerExpandIn() and triggerCollapseIn() methods, and added a test case that minimally reproduces the lost marker. --- lib/models/patch/multi-file-patch.js | 34 +++++++++++++++------- test/models/patch/multi-file-patch.test.js | 32 ++++++++++++++++++++ 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 72fb5f937b..47e7e635e0 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -294,12 +294,17 @@ export default class MultiFilePatch { } collapseFilePatch(filePatch) { + const index = this.filePatches.indexOf(filePatch); + this.filePatchesByMarker.delete(filePatch.getMarker()); for (const hunk of filePatch.getHunks()) { this.hunksByMarker.delete(hunk.getMarker()); } - filePatch.triggerCollapseIn(this.patchBuffer); + const before = this.getMarkersBefore(index); + const after = this.getMarkersAfter(index); + + filePatch.triggerCollapseIn(this.patchBuffer, {before, after}); this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); @@ -318,8 +323,20 @@ export default class MultiFilePatch { this.hunksByMarker.delete(hunk.getMarker()); } + const before = this.getMarkersBefore(index); + const after = this.getMarkersAfter(index); + + filePatch.triggerExpandIn(this.patchBuffer, {before, after}); + + this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); + for (const hunk of filePatch.getHunks()) { + this.hunksByMarker.set(hunk.getMarker(), hunk); + } + } + + getMarkersBefore(filePatchIndex) { const before = []; - let beforeIndex = index - 1; + let beforeIndex = filePatchIndex - 1; while (beforeIndex >= 0) { const beforeFilePatch = this.filePatches[beforeIndex]; before.push(...beforeFilePatch.getEndingMarkers()); @@ -329,9 +346,12 @@ export default class MultiFilePatch { } beforeIndex--; } + return before; + } + getMarkersAfter(filePatchIndex) { const after = []; - let afterIndex = index + 1; + let afterIndex = filePatchIndex + 1; while (afterIndex < this.filePatches.length) { const afterFilePatch = this.filePatches[afterIndex]; after.push(...afterFilePatch.getStartingMarkers()); @@ -341,13 +361,7 @@ export default class MultiFilePatch { } afterIndex++; } - - filePatch.triggerExpandIn(this.patchBuffer, {before, after}); - - this.filePatchesByMarker.set(filePatch.getMarker(), filePatch); - for (const hunk of filePatch.getHunks()) { - this.hunksByMarker.set(hunk.getMarker(), hunk); - } + return after; } isPatchVisible = filePatchPath => { diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 0917a6e0e4..7ab1cde4cd 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -1176,5 +1176,37 @@ describe('MultiFilePatch', function() { } }); }); + + describe('when a file patch has no content', function() { + it('collapses and expands', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('0.txt').executable()); + fp.setNewFile(f => f.path('0.txt')); + fp.empty(); + }) + .build(); + + assert.strictEqual(multiFilePatch.getBuffer().getText(), ''); + + const [fp0] = multiFilePatch.getFilePatches(); + + multiFilePatch.collapseFilePatch(fp0); + + assert.strictEqual(multiFilePatch.getBuffer().getText(), ''); + assertInFilePatch(fp0, multiFilePatch.getBuffer()).hunks(); + assert.deepEqual(fp0.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); + assert.isTrue(fp0.getMarker().isValid()); + assert.isFalse(fp0.getMarker().isDestroyed()); + + multiFilePatch.expandFilePatch(fp0); + + assert.strictEqual(multiFilePatch.getBuffer().getText(), ''); + assertInFilePatch(fp0, multiFilePatch.getBuffer()).hunks(); + assert.deepEqual(fp0.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); + assert.isTrue(fp0.getMarker().isValid()); + assert.isFalse(fp0.getMarker().isDestroyed()); + }); + }); }); }); From 135ccce59b7c2b553bd7d3da321936183f691c93 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 13:13:19 -0500 Subject: [PATCH 2121/4053] Collapse and expand content-less FilePatches When collapsing and expanding FilePatches that have no content, ensure that separating newlines are preserved or omitted correctly. This prevents a glitch where the final newline will pop in and out as you collapse and expand a mode-only patch at the end of a MultiFilePatchView. --- lib/models/patch/file-patch.js | 12 ++++-- test/models/patch/multi-file-patch.test.js | 46 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index b7ebbf1589..6ae550af29 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -129,14 +129,17 @@ export default class FilePatch { } const oldPatch = this.patch; - const insertionPosition = oldPatch.getRange().start.copy(); + const oldRange = oldPatch.getRange().copy(); + const insertionPosition = oldRange.start; const exclude = new Set([...before, ...after]); - const {patchBuffer: subPatchBuffer, markerMap} = patchBuffer.extractPatchBuffer(oldPatch.getRange(), {exclude}); + const {patchBuffer: subPatchBuffer, markerMap} = patchBuffer.extractPatchBuffer(oldRange, {exclude}); oldPatch.destroyMarkers(); oldPatch.updateMarkers(markerMap); // Delete the separating newline after the collapsing patch, if any. - patchBuffer.getBuffer().deleteRow(insertionPosition.row); + if (!oldRange.isEmpty()) { + patchBuffer.getBuffer().deleteRow(insertionPosition.row); + } const patchMarker = patchBuffer.markPosition( Patch.layerName, @@ -159,6 +162,7 @@ export default class FilePatch { const {patch: nextPatch, patchBuffer: subPatchBuffer} = this.patch.show(); const atStart = this.patch.getInsertionPoint().isEqual([0, 0]); const atEnd = this.patch.getInsertionPoint().isEqual(patchBuffer.getBuffer().getEndPosition()); + const willHaveContent = !subPatchBuffer.getBuffer().isEmpty(); // The expanding patch's insertion point is just after the unmarked newline that separates adjacent visible // patches: @@ -174,7 +178,7 @@ export default class FilePatch { // (so it isn't also the end of the buffer). Insert a newline *after* the expanding patch when inserting anywhere // but the buffer's end. - if (atEnd && !atStart) { + if (willHaveContent && atEnd && !atStart) { const beforeNewline = []; const afterNewline = after.slice(); diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 7ab1cde4cd..580da3ddd5 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -1207,6 +1207,52 @@ describe('MultiFilePatch', function() { assert.isTrue(fp0.getMarker().isValid()); assert.isFalse(fp0.getMarker().isDestroyed()); }); + + it('does not insert a trailing newline when expanding a final content-less patch', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('0.txt')); + fp.addHunk(h => h.unchanged('0').added('1').unchanged('2')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('1.txt').executable()); + fp.setNewFile(f => f.path('1.txt')); + fp.empty(); + }) + .build(); + const [fp0, fp1] = multiFilePatch.getFilePatches(); + + assert.isTrue(fp1.getRenderStatus().isVisible()); + assert.strictEqual(multiFilePatch.getBuffer().getText(), dedent` + 0 + 1 + 2 + `); + assert.deepEqual(fp0.getMarker().getRange().serialize(), [[0, 0], [2, 1]]); + assert.deepEqual(fp1.getMarker().getRange().serialize(), [[2, 1], [2, 1]]); + + multiFilePatch.collapseFilePatch(fp1); + + assert.isFalse(fp1.getRenderStatus().isVisible()); + assert.strictEqual(multiFilePatch.getBuffer().getText(), dedent` + 0 + 1 + 2 + `); + assert.deepEqual(fp0.getMarker().getRange().serialize(), [[0, 0], [2, 1]]); + assert.deepEqual(fp1.getMarker().getRange().serialize(), [[2, 1], [2, 1]]); + + multiFilePatch.expandFilePatch(fp1); + + assert.isTrue(fp1.getRenderStatus().isVisible()); + assert.strictEqual(multiFilePatch.getBuffer().getText(), dedent` + 0 + 1 + 2 + `); + assert.deepEqual(fp0.getMarker().getRange().serialize(), [[0, 0], [2, 1]]); + assert.deepEqual(fp1.getMarker().getRange().serialize(), [[2, 1], [2, 1]]); + }); }); }); }); From e11e6877b5727036f3ee013d4b0b3bea1911db84 Mon Sep 17 00:00:00 2001 From: annthurium Date: Tue, 26 Feb 2019 11:04:10 -0800 Subject: [PATCH 2122/4053] support new reaction emoji from GitHub.com --- lib/views/emoji-reactions-view.js | 23 +++++++++++++++-------- test/views/emoji-reactions-view.test.js | 10 ++++++++++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/views/emoji-reactions-view.js b/lib/views/emoji-reactions-view.js index 240d55fd42..729566fd4a 100644 --- a/lib/views/emoji-reactions-view.js +++ b/lib/views/emoji-reactions-view.js @@ -9,6 +9,8 @@ const reactionTypeToEmoji = { HOORAY: '🎉', CONFUSED: '😕', HEART: '❤️', + ROCKET: '🚀', + EYES: '👀', }; export default class EmojiReactionsView extends React.Component { @@ -26,14 +28,19 @@ export default class EmojiReactionsView extends React.Component { render() { return (
    - {this.props.reactionGroups.map(group => ( - group.users.totalCount > 0 - ? - {reactionTypeToEmoji[group.content]}   {group.users.totalCount} - - : null - ))} + {this.props.reactionGroups.map(group => { + const emoji = reactionTypeToEmoji[group.content]; + if (!emoji) { + return null; + } + return ( + group.users.totalCount > 0 + ? + {reactionTypeToEmoji[group.content]}   {group.users.totalCount} + + : null); + })}
    ); } diff --git a/test/views/emoji-reactions-view.test.js b/test/views/emoji-reactions-view.test.js index 9c6e31e3b1..767116e6fa 100644 --- a/test/views/emoji-reactions-view.test.js +++ b/test/views/emoji-reactions-view.test.js @@ -7,6 +7,9 @@ describe('EmojiReactionsView', function() { const reactionGroups = [ {content: 'THUMBS_UP', users: {totalCount: 10}}, {content: 'THUMBS_DOWN', users: {totalCount: 5}}, + {content: 'ROCKET', users: {totalCount: 42}}, + {content: 'EYES', users: {totalCount: 13}}, + {content: 'AVOCADO', users: {totalCount: 11}}, {content: 'LAUGH', users: {totalCount: 0}}]; beforeEach(function() { wrapper = shallow(); @@ -15,6 +18,13 @@ describe('EmojiReactionsView', function() { const groups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); assert.lengthOf(groups.findWhere(n => /👍/u.test(n.text()) && /\b10\b/.test(n.text())), 1); assert.lengthOf(groups.findWhere(n => /👎/u.test(n.text()) && /\b5\b/.test(n.text())), 1); + assert.lengthOf(groups.findWhere(n => /🚀/u.test(n.text()) && /\b42\b/.test(n.text())), 1); + assert.lengthOf(groups.findWhere(n => /👀/u.test(n.text()) && /\b13\b/.test(n.text())), 1); assert.isFalse(groups.someWhere(n => /😆/u.test(n.text()))); }); + it('gracefully skips unknown emoji', function() { + assert.isFalse(wrapper.text().includes(11)); + const groups = wrapper.find('.github-IssueishDetailView-reactionsGroup'); + assert.lengthOf(groups, 4); + }); }); From 9a3c4dac12f416d0000ed9d4c75943856696c4a1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 14:27:22 -0500 Subject: [PATCH 2123/4053] Use IIFEs to bind variables in arrow functions --- lib/models/patch/builder.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index f378c3f838..2321141855 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -58,7 +58,9 @@ export function buildMultiFilePatch(diffs, options) { if (otherHalf) { // The second half. Complete the paired diff, or fail if they have unexpected statuses or modes. const [otherDiff, otherIndex] = otherHalf; - actions[otherIndex] = () => dualDiffFilePatch(diff, otherDiff, patchBuffer, opts); + actions[otherIndex] = (function(_diff, _otherDiff) { + return () => dualDiffFilePatch(_diff, _otherDiff, patchBuffer, opts); + })(diff, otherDiff); byPath.delete(thePath); } else { // The first half we've seen. @@ -66,14 +68,18 @@ export function buildMultiFilePatch(diffs, options) { index++; } } else { - actions[index] = () => singleDiffFilePatch(diff, patchBuffer, opts); + actions[index] = (function(_diff) { + return () => singleDiffFilePatch(_diff, patchBuffer, opts); + })(diff); index++; } } // Populate unpaired diffs that looked like they could be part of a pair, but weren't. for (const [unpairedDiff, originalIndex] of byPath.values()) { - actions[originalIndex] = () => singleDiffFilePatch(unpairedDiff, patchBuffer, opts); + actions[originalIndex] = (function(_unpairedDiff) { + return () => singleDiffFilePatch(_unpairedDiff, patchBuffer, opts); + })(unpairedDiff); } const filePatches = actions.map(action => action()); From 817143ee065059ff94a8cb092b6ff188523bcdb3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 14:27:44 -0500 Subject: [PATCH 2124/4053] Only measure the contentChangeDiff when determining if it's "too large" --- lib/models/patch/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index 2321141855..aecd09aca0 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -188,7 +188,7 @@ function dualDiffFilePatch(diff1, diff2, patchBuffer, opts) { const newFile = new File({path: filePath, mode: newMode, symlink: newSymlink}); const renderStatus = opts.renderStatusOverrides[filePath] || - (isDiffLarge([diff1, diff2], opts) && TOO_LARGE) || + (isDiffLarge([contentChangeDiff], opts) && TOO_LARGE) || EXPANDED; if (!renderStatus.isVisible()) { From 69b8a7370b8292ce62b5acb60e7e110994fd2f85 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 14:39:27 -0500 Subject: [PATCH 2125/4053] Lock dependencies with exact ranges --- package-lock.json | 118 ++++++++++++++++++++++++++++++---------------- package.json | 10 ++-- 2 files changed, 82 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8b3ce3a84d..3800777f48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1767,20 +1767,62 @@ } }, "babel-traverse": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", - "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", + "babel-code-frame": "^6.26.0", "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-types": "^6.25.0", - "babylon": "^6.17.2", - "debug": "^2.2.0", - "globals": "^9.0.0", - "invariant": "^2.2.0", - "lodash": "^4.2.0" + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + } } }, "babel-types": { @@ -2034,9 +2076,9 @@ "dev": true }, "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, "cache-base": { "version": "1.0.1", @@ -2778,9 +2820,9 @@ } }, "dugite": { - "version": "1.81.0", - "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.81.0.tgz", - "integrity": "sha512-aH1cVzbEXOHqpiub9PWJUN+R2p7H+tvN+VqyAYHR9Tj/axLDccWJk5aKDN1/US82DkaIYWUZz8x0lAbjfqrq4Q==", + "version": "1.84.0", + "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.84.0.tgz", + "integrity": "sha512-TKBfeQAq4f4bcrPRtb9k1LnI/dssl8K85A5LxnJIaKdhGUbOtmcJacSzNSiokz0OFbSOEIYxtgZMRnkVONIURg==", "requires": { "checksum": "^0.1.1", "mkdirp": "^0.5.1", @@ -2791,9 +2833,9 @@ }, "dependencies": { "ajv": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", - "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.2.tgz", + "integrity": "sha512-4UFy0/LgDo7Oa/+wOAlj44tp9K78u38E5/359eSrqEp1Z5PdVfimCcs7SluXMP755RUQu6d2b4AvF0R1C9RZjg==", "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -2831,16 +2873,16 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" }, "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", "requires": { - "mime-db": "~1.37.0" + "mime-db": "~1.38.0" } }, "oauth-sign": { @@ -2848,11 +2890,6 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" - }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -4121,7 +4158,7 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, "globby": { @@ -5111,9 +5148,9 @@ } }, "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha1-maktZcAnLevoyWtgV7yPv6O+1RE=", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "lodash.escape": { @@ -5691,9 +5728,9 @@ } }, "node-emoji": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.8.1.tgz", - "integrity": "sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", + "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", "requires": { "lodash.toarray": "^4.4.0" } @@ -7225,8 +7262,7 @@ "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" }, "progress-stream": { "version": "1.2.0", diff --git a/package.json b/package.json index 192044789b..8090ae522e 100644 --- a/package.json +++ b/package.json @@ -48,17 +48,17 @@ "@babel/preset-react": "7.0.0", "babel-plugin-relay": "3.0.0", "bintrees": "1.0.2", - "bytes": "^3.0.0", + "bytes": "3.1.0", "classnames": "2.2.6", "compare-sets": "1.0.1", - "dugite": "^1.81.0", + "dugite": "1.84.0", "event-kit": "2.5.3", "fs-extra": "4.0.3", "graphql": "14.1.1", "keytar": "4.4.0", "lodash.memoize": "4.1.2", "moment": "2.23.0", - "node-emoji": "^1.8.1", + "node-emoji": "1.10.0", "prop-types": "15.7.2", "react": "16.8.3", "react-dom": "16.8.3", @@ -95,9 +95,9 @@ "hock": "1.3.3", "lodash.isequalwith": "4.4.0", "mkdirp": "0.5.1", - "mocha": "^5.2.0", + "mocha": "5.2.0", "mocha-junit-reporter": "1.18.0", - "mocha-multi-reporters": "^1.1.7", + "mocha-multi-reporters": "1.1.7", "mocha-stress": "1.0.0", "node-fetch": "2.3.0", "nyc": "13.3.0", From 4839a108975760ff0484b6b785896c608b03ebe8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 14:40:38 -0500 Subject: [PATCH 2126/4053] Update devDependencies that have drifted --- package-lock.json | 73 ++++++++++++++++++++++++++--------------------- package.json | 4 +-- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3800777f48..2d9cf2bcf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2720,12 +2720,11 @@ "dev": true }, "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", "dev": true, "requires": { - "arrify": "^1.0.1", "path-type": "^3.0.0" }, "dependencies": { @@ -3046,9 +3045,9 @@ "integrity": "sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==" }, "emoji-regex": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", - "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, "encoding": { @@ -3435,17 +3434,17 @@ "dev": true }, "eslint-plugin-jsx-a11y": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.2.tgz", - "integrity": "sha512-7gSSmwb3A+fQwtw0arguwMdOdzmKUgnUcbSNlo+GjKLAQFuC2EZxWqG9XHRI8VscBJD5a8raz3RuxQNFW+XJbw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz", + "integrity": "sha512-cjN2ObWrRz0TTw7vEcGQrx+YltMvZoOEx4hWU8eEERDnBIU00OTq7Vr+jA7DFKxiwLNv4tTh5Pq2GUNEa8b6+w==", "dev": true, "requires": { "aria-query": "^3.0.0", "array-includes": "^3.0.3", "ast-types-flow": "^0.0.7", - "axobject-query": "^2.0.1", + "axobject-query": "^2.0.2", "damerau-levenshtein": "^1.0.4", - "emoji-regex": "^6.5.1", + "emoji-regex": "^7.0.2", "has": "^1.0.3", "jsx-ast-utils": "^2.0.1" }, @@ -4162,30 +4161,38 @@ "dev": true }, "globby": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", - "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.0.0.tgz", + "integrity": "sha512-q0qiO/p1w/yJ0hk8V9x1UXlgsXUxlGd0AHUOXZVXBO6aznDtpx7M8D1kBrCAItoPm+4l8r6ATXV1JpjY2SBQOw==", "dev": true, "requires": { - "array-union": "^1.0.1", - "dir-glob": "2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" + "array-union": "^1.0.2", + "dir-glob": "^2.2.1", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" }, "dependencies": { - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true } } @@ -8059,9 +8066,9 @@ } }, "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, "slice-ansi": { diff --git a/package.json b/package.json index 8090ae522e..4561e5f43b 100644 --- a/package.json +++ b/package.json @@ -90,8 +90,8 @@ "enzyme-adapter-react-16": "1.7.1", "eslint": "5.14.1", "eslint-config-fbjs-opensource": "1.0.0", - "eslint-plugin-jsx-a11y": "6.1.2", - "globby": "8.0.2", + "eslint-plugin-jsx-a11y": "6.2.1", + "globby": "9.0.0", "hock": "1.3.3", "lodash.isequalwith": "4.4.0", "mkdirp": "0.5.1", From 9cec6ee53fedf3979525de45a243e83a6ca49cf2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 14:43:09 -0500 Subject: [PATCH 2127/4053] Update non-dev dependencies --- package-lock.json | 85 ++++++++++++++++++++++++++--------------------- package.json | 4 +-- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2d9cf2bcf0..9b0775d449 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1246,36 +1246,6 @@ "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, - "@shiftkey/prebuild-install": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@shiftkey/prebuild-install/-/prebuild-install-5.2.4.tgz", - "integrity": "sha512-42L/pSGD/+diCg8SwhZaXjDlkAWV10u42UozyG7rqDdyPW7HDp2/j/RYRZ3x0sXFf7hAUtLYvI9HdACWdjyfVw==", - "requires": { - "detect-libc": "^1.0.3", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "napi-build-utils": "^1.0.1", - "node-abi": "^2.2.0", - "noop-logger": "^0.1.1", - "npmlog": "^4.0.1", - "os-homedir": "^1.0.1", - "pump": "^2.0.1", - "rc": "^1.2.7", - "simple-get": "^2.7.0", - "tar-fs": "^1.13.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" - }, - "dependencies": { - "expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" - } - } - }, "@sinonjs/commons": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", @@ -4991,12 +4961,12 @@ "dev": true }, "keytar": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-4.4.0.tgz", - "integrity": "sha512-IX6rvzrXVCWwQDGxf0FmF1IYDU2UuKsTPl1rhMCvoFfvkpRyKQYHYNqmKISwTseh/JVk4VPRlLsP4L3J25odBg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-4.4.1.tgz", + "integrity": "sha512-6xEe7ybXSR5EZC+z0GI2yqLYZjV1tyPQY2xSZ8rGsBxrrLEh8VR/Lfqv59uGX+I+W+OZxH0jCXN1dU1++ify4g==", "requires": { - "@shiftkey/prebuild-install": "5.2.4", - "nan": "2.12.1" + "nan": "2.12.1", + "prebuild-install": "5.2.4" } }, "kind-of": { @@ -5612,9 +5582,9 @@ "dev": true }, "moment": { - "version": "2.23.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz", - "integrity": "sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA==" + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "moo": { "version": "0.4.3", @@ -5730,6 +5700,7 @@ "version": "2.4.5", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.5.tgz", "integrity": "sha512-aa/UC6Nr3+tqhHGRsAuw/edz7/q9nnetBrKWxj6rpTtm+0X9T1qU7lIEHMS3yN9JwAbRiKUbRRFy1PLz/y3aaA==", + "dev": true, "requires": { "semver": "^5.4.1" } @@ -7234,6 +7205,44 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, + "prebuild-install": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.2.4.tgz", + "integrity": "sha512-CG3JnpTZXdmr92GW4zbcba4jkDha6uHraJ7hW4Fn8j0mExxwOKK20hqho8ZuBDCKYCHYIkFM1P2jhtG+KpP4fg==", + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "os-homedir": "^1.0.1", + "pump": "^2.0.1", + "rc": "^1.2.7", + "simple-get": "^2.7.0", + "tar-fs": "^1.13.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + }, + "dependencies": { + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, + "node-abi": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.7.1.tgz", + "integrity": "sha512-OV8Bq1OrPh6z+Y4dqwo05HqrRL9YNF7QVMRfq1/pguwKLG+q9UB/Lk0x5qXjO23JjJg+/jqCHSTaG1P3tfKfuw==", + "requires": { + "semver": "^5.4.1" + } + } + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", diff --git a/package.json b/package.json index 4561e5f43b..13e84b5618 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,9 @@ "event-kit": "2.5.3", "fs-extra": "4.0.3", "graphql": "14.1.1", - "keytar": "4.4.0", + "keytar": "4.4.1", "lodash.memoize": "4.1.2", - "moment": "2.23.0", + "moment": "2.24.0", "node-emoji": "1.10.0", "prop-types": "15.7.2", "react": "16.8.3", From 0b7811d8fac32d2dea161416967bc73aabdf9aa5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 15:20:24 -0500 Subject: [PATCH 2128/4053] Patch :100: --- lib/models/patch/patch.js | 11 ++++++----- test/models/patch/patch.test.js | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/models/patch/patch.js b/lib/models/patch/patch.js index a44fba121e..2e75f666cd 100644 --- a/lib/models/patch/patch.js +++ b/lib/models/patch/patch.js @@ -4,18 +4,21 @@ import Hunk from './hunk'; import {Unchanged, Addition, Deletion, NoNewline} from './region'; export const EXPANDED = { + /* istanbul ignore next */ toString() { return 'RenderStatus(expanded)'; }, isVisible() { return true; }, }; export const COLLAPSED = { + /* istanbul ignore next */ toString() { return 'RenderStatus(collapsed)'; }, isVisible() { return false; }, }; export const TOO_LARGE = { + /* istanbul ignore next */ toString() { return 'RenderStatus(too-large)'; }, isVisible() { return false; }, @@ -331,8 +334,8 @@ export default class Patch { /* * Construct a String containing internal diagnostic information. */ + /* istanbul ignore next */ inspect(opts = {}) { - /* istanbul ignore next */ const options = { indent: 0, ...opts, @@ -386,6 +389,7 @@ class HiddenPatch extends Patch { /* * Construct a String containing internal diagnostic information. */ + /* istanbul ignore next */ inspect(opts = {}) { const options = { indent: 0, @@ -486,6 +490,7 @@ class NullPatch { /* * Construct a String containing internal diagnostic information. */ + /* istanbul ignore next */ inspect(opts = {}) { const options = { indent: 0, @@ -593,8 +598,4 @@ class BufferBuilder { return {regions: [], marker: null}; } - - getLayeredBuffer() { - return this.nextPatchBuffer; - } } diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 09648d39a2..5fe2e3050e 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -684,6 +684,7 @@ describe('Patch', function() { const nullPatch = Patch.createNull(); assert.isNull(nullPatch.getStatus()); assert.deepEqual(nullPatch.getMarker().getRange().serialize(), [[0, 0], [0, 0]]); + assert.deepEqual(nullPatch.getRange().serialize(), [[0, 0], [0, 0]]); assert.deepEqual(nullPatch.getStartRange().serialize(), [[0, 0], [0, 0]]); assert.deepEqual(nullPatch.getHunks(), []); assert.strictEqual(nullPatch.getChangedLineCount(), 0); @@ -692,6 +693,8 @@ describe('Patch', function() { assert.deepEqual(nullPatch.getFirstChangeRange().serialize(), [[0, 0], [0, 0]]); assert.strictEqual(nullPatch.toStringIn(), ''); assert.isFalse(nullPatch.isPresent()); + assert.lengthOf(nullPatch.getStartingMarkers(), 0); + assert.lengthOf(nullPatch.getEndingMarkers(), 0); }); }); From 6e4ae952e36abd83569cfb5d609efe22d61aae83 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 15:21:55 -0500 Subject: [PATCH 2129/4053] Region :100: --- lib/models/patch/region.js | 1 + test/models/patch/region.test.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/models/patch/region.js b/lib/models/patch/region.js index b637ea6091..39f0414b0f 100644 --- a/lib/models/patch/region.js +++ b/lib/models/patch/region.js @@ -124,6 +124,7 @@ class Region { /* * Construct a String containing internal diagnostic information. */ + /* istanbul ignore next */ inspect(opts = {}) { const options = { indent: 0, diff --git a/test/models/patch/region.test.js b/test/models/patch/region.test.js index 105a8746b5..8bbc7961c5 100644 --- a/test/models/patch/region.test.js +++ b/test/models/patch/region.test.js @@ -1,7 +1,7 @@ import {TextBuffer} from 'atom'; import {Addition, Deletion, NoNewline, Unchanged} from '../../../lib/models/patch/region'; -describe('Regions', function() { +describe('Region', function() { let buffer, marker; beforeEach(function() { From 2681e23da4359f756d455b760db31c2a992469b5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 15:41:46 -0500 Subject: [PATCH 2130/4053] FilePatch :100: --- lib/models/patch/file-patch.js | 1 + test/models/patch/file-patch.test.js | 30 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/models/patch/file-patch.js b/lib/models/patch/file-patch.js index 6ae550af29..87764c0d41 100644 --- a/lib/models/patch/file-patch.js +++ b/lib/models/patch/file-patch.js @@ -324,6 +324,7 @@ export default class FilePatch { /* * Construct a String containing diagnostic information about the internal state of this FilePatch. */ + /* istanbul ignore next */ inspect(opts = {}) { const options = { indent: 0, diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index e2743828d5..4ee102cd27 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -23,7 +23,7 @@ describe('FilePatch', function() { ], }), ]; - const marker = markRange(layers.patch); + const marker = markRange(layers.patch, 0, 2); const patch = new Patch({status: 'modified', hunks, marker}); const oldFile = new File({path: 'a.txt', mode: '120000', symlink: 'dest.txt'}); const newFile = new File({path: 'b.txt', mode: '100755'}); @@ -42,6 +42,14 @@ describe('FilePatch', function() { assert.strictEqual(filePatch.getMarker(), marker); assert.strictEqual(filePatch.getMaxLineNumberWidth(), 1); + + assert.deepEqual(filePatch.getFirstChangeRange().serialize(), [[1, 0], [1, Infinity]]); + assert.isTrue(filePatch.containsRow(0)); + assert.isFalse(filePatch.containsRow(3)); + + const nMarker = markRange(layers.patch, 0, 2); + filePatch.updateMarkers(new Map([[marker, nMarker]])); + assert.strictEqual(filePatch.getMarker(), nMarker); }); it('accesses a file path from either side of the patch', function() { @@ -698,6 +706,26 @@ describe('FilePatch', function() { assert.isFalse(filePatch.triggerCollapseIn(new PatchBuffer(), {before: [], after: []})); }); + it('triggerCollapseIn does not delete the trailing line if the collapsed patch has no content', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => { + fp.setOldFile(f => f.path('0.txt')); + fp.addHunk(h => h.added('0')); + }) + .addFilePatch(fp => { + fp.setOldFile(f => f.path('1.txt')); + fp.setNewFile(f => f.path('1.txt').executable()); + fp.empty(); + }) + .build(); + + assert.strictEqual(multiFilePatch.getBuffer().getText(), '0'); + + multiFilePatch.collapseFilePatch(multiFilePatch.getFilePatches()[1]); + + assert.strictEqual(multiFilePatch.getBuffer().getText(), '0'); + }); + it('announces the expansion of a collapsed patch', function() { const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(fp => { From 7dc1b78e18eb01e9a34efc46d7d5a78fef84a3d8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Tue, 26 Feb 2019 15:52:52 -0500 Subject: [PATCH 2131/4053] Starting on MultiFilePatchView test coverage --- lib/views/multi-file-patch-view.js | 21 +++++++++++++++------ test/views/multi-file-patch-view.test.js | 6 ++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 235d7de49c..c86aad5906 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -121,6 +121,7 @@ export default class MultiFilePatchView extends React.Component { }), this.props.onDidUpdatePatch(nextPatch => { this.refEditor.map(editor => { + /* istanbul ignore else */ if (lastSelectionIndex !== null) { const nextSelectionRange = nextPatch.getSelectionRangeForIndex(lastSelectionIndex); if (this.props.selectionMode === 'line') { @@ -132,6 +133,7 @@ export default class MultiFilePatchView extends React.Component { .map(row => nextPatch.getHunkAt(row)) .filter(Boolean), ); + /* istanbul ignore next */ const nextRanges = nextHunks.size > 0 ? Array.from(nextHunks, hunk => hunk.getRange()) : [[[0, 0], [0, 0]]]; @@ -140,7 +142,11 @@ export default class MultiFilePatchView extends React.Component { editor.setSelectedBufferRanges(nextRanges); } } + + /* istanbul ignore else */ if (lastScrollTop !== null) { editor.getElement().setScrollTop(lastScrollTop); } + + /* istanbul ignore else */ if (lastScrollLeft !== null) { editor.getElement().setScrollLeft(lastScrollLeft); } return null; }); @@ -170,11 +176,11 @@ export default class MultiFilePatchView extends React.Component { }); this.subs.add( - this.props.config.onDidChange('github.showDiffIconGutter', ({newValue}) => this.forceUpdate()), + this.props.config.onDidChange('github.showDiffIconGutter', () => this.forceUpdate()), ); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps) { this.measurePerformance('update'); if (prevProps.refInitialFocus !== this.props.refInitialFocus) { @@ -260,7 +266,7 @@ export default class MultiFilePatchView extends React.Component { {stageModeCommand} {stageSymlinkCommand} - {atom.inDevMode() && + {/* istanbul ignore next */ atom.inDevMode() && { // eslint-disable-next-line no-console console.log(this.props.multiFilePatch.getLayeredBuffer().inspect({ @@ -269,7 +275,7 @@ export default class MultiFilePatchView extends React.Component { }} /> } - {atom.inDevMode() && + {/* istanbul ignore next */ atom.inDevMode() && { // eslint-disable-next-line no-console console.log(this.props.multiFilePatch.getLayeredBuffer().inspect({ @@ -278,7 +284,7 @@ export default class MultiFilePatchView extends React.Component { }} /> } - {atom.inDevMode() && + {/* istanbul ignore next */ atom.inDevMode() && { // eslint-disable-next-line no-console console.log(this.props.multiFilePatch.inspect()); @@ -707,6 +713,7 @@ export default class MultiFilePatchView extends React.Component { undoLastDiscardFromCoreUndo = () => { if (this.props.hasUndoHistory) { const selectedFilePatches = Array.from(this.getSelectedFilePatches()); + /* istanbul ignore else */ if (this.props.itemType === ChangedFileItem) { this.props.undoLastDiscard(selectedFilePatches[0], {eventSource: {command: 'core:undo'}}); } @@ -923,6 +930,7 @@ export default class MultiFilePatchView extends React.Component { let firstChangeRow = Infinity; for (const hunk of selectedHunks) { const [firstChange] = hunk.getChanges(); + /* istanbul ignore else */ if (firstChange && (!firstChangeRow || firstChange.getStartBufferRow() < firstChangeRow)) { firstChangeRow = firstChange.getStartBufferRow(); } @@ -977,7 +985,7 @@ export default class MultiFilePatchView extends React.Component { }); } - didOpenFile({selectedFilePatch} = {}) { + didOpenFile({selectedFilePatch}) { const cursorsByFilePatch = new Map(); this.refEditor.map(editor => { @@ -1205,6 +1213,7 @@ export default class MultiFilePatchView extends React.Component { } measurePerformance(action) { + /* istanbul ignore else */ if ((action === 'update' || action === 'mount') && performance.getEntriesByName(`MultiFilePatchView-${action}-start`).length > 0) { performance.mark(`MultiFilePatchView-${action}-end`); diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 4c057b5b22..8af61c38ff 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -88,6 +88,12 @@ describe('MultiFilePatchView', function() { assert.isTrue(wrapper.find('FilePatchHeaderView').exists()); }); + it('populates an externally provided refEditor', async function() { + const refEditor = new RefHolder(); + mount(buildApp({refEditor})); + assert.isDefined(await refEditor.getPromise()); + }); + describe('file header decoration positioning', function() { let wrapper; From f01deb9a1d5b39fe542156d241aae4c3d7205cde Mon Sep 17 00:00:00 2001 From: annthurium Date: Tue, 26 Feb 2019 13:49:11 -0800 Subject: [PATCH 2132/4053] respect core.commentChar in commit message templates --- lib/git-shell-out-strategy.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 37b64d8307..eee64471bf 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -519,7 +519,14 @@ export default class GitShellOutStrategy { // to be consistent with command line git. const template = await this.fetchCommitMessageTemplate(); if (template) { - msg = msg.split('\n').filter(line => !line.startsWith('#')).join('\n'); + + // respecting the comment character from user settings or fall back to # as default. + // https://git-scm.com/docs/git-config#git-config-corecommentChar + let commentChar = await this.getConfig('core.commentChar'); + if (!commentChar) { + commentChar = '#'; + } + msg = msg.split('\n').filter(line => !line.startsWith(commentChar)).join('\n'); } // Determine the cleanup mode. From 10d33b9923e78cfbb181d982f51916b6b44acb74 Mon Sep 17 00:00:00 2001 From: annthurium Date: Tue, 26 Feb 2019 14:11:33 -0800 Subject: [PATCH 2133/4053] add unit test for respecting core.commentChar --- test/git-strategies.test.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index ef3851b5a1..7d395883e6 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1198,6 +1198,25 @@ import * as reporterProxy from '../lib/reporter-proxy'; // message body should not contain the template text assert.strictEqual(lastCommit.messageBody, 'neither should this one'); }); + it('respects core.commentChar from git settings when determining which comment to strip', async function() { + const workingDirPath = await cloneRepository('three-files'); + const git = createTestStrategy(workingDirPath); + const templateText = 'templates are just the best'; + + const commitMsgTemplatePath = path.join(workingDirPath, '.gitmessage'); + await fs.writeFile(commitMsgTemplatePath, templateText, {encoding: 'utf8'}); + + await git.setConfig('commit.template', commitMsgTemplatePath); + await git.setConfig('commit.cleanup', 'default'); + await git.setConfig('core.commentChar', '$'); + + const commitMessage = ['# this line should not be stripped', '$ but this one should', '', 'ch-ch-changes'].join('\n'); + await git.commit(commitMessage, {allowEmpty: true, verbatim: true}); + + const lastCommit = await git.getHeadCommit(); + assert.strictEqual(lastCommit.messageSubject, '# this line should not be stripped'); + assert.strictEqual(lastCommit.messageBody, 'ch-ch-changes'); + }); }); describe('when amend option is true', function() { From 507f686adede3d0034cd5583f747d0a11c03fbfa Mon Sep 17 00:00:00 2001 From: annthurium Date: Tue, 26 Feb 2019 14:12:39 -0800 Subject: [PATCH 2134/4053] make sure that comment char stripping hits the commit message body --- test/git-strategies.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 7d395883e6..b853a3809a 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -1190,7 +1190,7 @@ import * as reporterProxy from '../lib/reporter-proxy'; await git.setConfig('commit.template', commitMsgTemplatePath); await git.setConfig('commit.cleanup', 'default'); - const commitMessage = ['this line should not be stripped', '', 'neither should this one', templateText].join('\n'); + const commitMessage = ['this line should not be stripped', '', 'neither should this one', '', '# but this one should', templateText].join('\n'); await git.commit(commitMessage, {allowEmpty: true, verbatim: true}); const lastCommit = await git.getHeadCommit(); From 5406bf1d4b3da550836fa380e1992e0d56564fe2 Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 27 Feb 2019 15:08:16 +0900 Subject: [PATCH 2135/4053] Style suggested changes --- styles/file-patch-view.less | 30 ++++---------- styles/pr-comment.less | 82 +++++++++++++++++++++++++++++++++++++ styles/variables.less | 10 +++++ 3 files changed, 99 insertions(+), 23 deletions(-) diff --git a/styles/file-patch-view.less b/styles/file-patch-view.less index 1075c8ac3a..4a4d05d2ec 100644 --- a/styles/file-patch-view.less +++ b/styles/file-patch-view.less @@ -183,32 +183,16 @@ // Line decorations &-line { - // mixin - .hunk-line-mixin(@color) { - background-color: fade(@color, 15%); - + &--deleted { + background-color: @github-diff-deleted; &.line.cursor-line { - background-color: fade(@color, 25%); - } - } - - // light themes - & when (lightness(@syntax-background-color) >= 50) { - &--deleted { - .hunk-line-mixin( hsl(353, 100%, 55%) ); // close to .com's hsl(353, 100%, 93%) - } - &--added { - .hunk-line-mixin( hsl(133, 100%, 50%) ); // close to .com's hsl(133, 100%, 90%) + background-color: fadein(@github-diff-deleted, 3%); } } - - // dark themes - & when (lightness(@syntax-background-color) < 50) { - &--deleted { - .hunk-line-mixin( hsl(353, 100%, 65%) ); // close to .com's hsl(353, 100%, 93%) - } - &--added { - .hunk-line-mixin( hsl(133, 40%, 60%) ); // close to .com's hsl(133, 100%, 90%) + &--added { + background-color: @github-diff-added; + &.line.cursor-line { + background-color: fadein(@github-diff-added, 3%); } } } diff --git a/styles/pr-comment.less b/styles/pr-comment.less index cec3c1c8b0..1076b07343 100644 --- a/styles/pr-comment.less +++ b/styles/pr-comment.less @@ -58,4 +58,86 @@ vertical-align: middle; } + + // Suggested changes + .js-suggested-changes-blob { + font-size: .9em; + border: 1px solid @base-border-color; + border-radius: @component-border-radius; + + .d-flex { display: flex; } + .d-inline-block { display: inline-block; } + .flex-auto { flex: 1 1 auto; } + .p-1 { padding: @component-padding/2; } + .px-1 { padding-left: @component-padding/2; padding-right: @component-padding/2; } + .p-2 { padding: @component-padding; } + .text-gray { color: @text-color-subtle; } + .lh-condensed { line-height: 1.25; } + .rounded-1 { border-radius: @component-border-radius; } + .border { border: 1px solid; } + .border-bottom { border-bottom: 1px solid @base-border-color; } + .border-green { border-color: @text-color-success; } + .octicon { vertical-align: -4px; fill: currentColor; } + + .blob-wrapper > table { + width: 100%; + margin: 0; + font-family: var(--editor-font-family); + font-size: var(--editor-font-size); + line-height: var(--editor-line-height); + + td { + border: none; + padding: 0 .5em; + } + } + + .blob-num { + padding: 0 .5em; + color: @text-color-subtle; + &:before { + content: attr(data-line-number); + } + &-addition { + background-color: fadein(@github-diff-added, 10%); + } + &-deletion { + background-color: fadein(@github-diff-deleted, 10%); + } + } + + .blob-code { + &-inner { + &:before { margin-right: .5em; } + .x { + &-first { + border-bottom-left-radius: @component-border-radius; + border-top-left-radius: @component-border-radius; + } + &-last { + border-bottom-right-radius: @component-border-radius; + border-top-right-radius: @component-border-radius; + } + } + } + + &-addition { + background-color: @github-diff-added; + &:before { content: "+"; } + .x { + background-color: @github-diff-added-highlight; + } + } + + &-deletion { + background-color: @github-diff-deleted; + &:before { content: "-"; } + .x { + background-color: @github-diff-deleted-highlight; + } + } + } + + } + } diff --git a/styles/variables.less b/styles/variables.less index 4919d1e60e..86ebf431f8 100644 --- a/styles/variables.less +++ b/styles/variables.less @@ -7,3 +7,13 @@ @gh-background-color-red: #dc3545; @gh-background-color-purple: #6f42c1; @gh-background-color-green: #28a745; + + +// diff colors ----------------- +// Needs to be semi transparent make it work with selections and different themes + +@github-diff-deleted: fade(hsl(353, 100%, 66%), 15%); // similar to .com's hsl(353, 100%, 97%) +@github-diff-added: fade(hsl(137, 100%, 55%), 15%); // similar to .com's hsl(137, 100%, 95%) + +@github-diff-deleted-highlight: fade(hsl(353, 95%, 66%), 25%); // similar to .com's hsl(353, 95%, 86%) +@github-diff-added-highlight: fade(hsl(135, 73%, 55%), 25%); // similar to .com's hsl(135, 73%, 81%) From 81a2d2c6337f8de32b48947d6694bb0e3ae5444c Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 27 Feb 2019 15:36:49 +0900 Subject: [PATCH 2136/4053] :memo: typo --- styles/variables.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/styles/variables.less b/styles/variables.less index 86ebf431f8..d218b8cc7d 100644 --- a/styles/variables.less +++ b/styles/variables.less @@ -10,7 +10,7 @@ // diff colors ----------------- -// Needs to be semi transparent make it work with selections and different themes +// Needs to be semi transparent to make it work with selections and different themes @github-diff-deleted: fade(hsl(353, 100%, 66%), 15%); // similar to .com's hsl(353, 100%, 97%) @github-diff-added: fade(hsl(137, 100%, 55%), 15%); // similar to .com's hsl(137, 100%, 95%) From 521fb201e442b9742259a11d5bc473311d6904d7 Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 27 Feb 2019 17:29:35 +0900 Subject: [PATCH 2137/4053] Style info tooltip --- styles/pr-comment.less | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/styles/pr-comment.less b/styles/pr-comment.less index 1076b07343..77fd06db85 100644 --- a/styles/pr-comment.less +++ b/styles/pr-comment.less @@ -138,6 +138,71 @@ } } + // These styles are mostly copied from the Primer tooltips + .tooltipped { + position: relative; + + // This is the tooltip bubble + &::after { + position: absolute; + z-index: 1000000; + display: none; + width: max-content; + max-width: 22em; + padding: .5em .6em; + font-weight: 500; + color: @base-background-color; + text-align: center; + pointer-events: none; + content: attr(aria-label); + background: @background-color-info; + border-radius: @component-border-radius; + } + + // This is the tooltip arrow + &::before { + position: absolute; + z-index: 1000001; + display: none; + width: 0; + height: 0; + color: @background-color-info; + pointer-events: none; + content: ""; + border: 6px solid transparent; + } + + // This will indicate when we'll activate the tooltip + &:hover, + &:active, + &:focus { + &::before, + &::after { + display: inline-block; + text-decoration: none; + } + } + + // Tooltipped south + &::after { + top: 100%; + right: 50%; + margin-top: 6px; + } + &::before { + top: auto; + right: 50%; + bottom: -7px; + margin-right: -6px; + border-bottom-color: @background-color-info; + } + + // Move the tooltip body to the center of the object. + &::after { + transform: translateX(50%); + } + } + } } From e758dc6fbd31f8ee74b84a6490c0d21e0ab3b041 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 27 Feb 2019 18:11:52 +0800 Subject: [PATCH 2138/4053] add test for unsub --- test/containers/pr-changed-files-container.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/containers/pr-changed-files-container.test.js b/test/containers/pr-changed-files-container.test.js index 47896dcfb4..70cf28333a 100644 --- a/test/containers/pr-changed-files-container.test.js +++ b/test/containers/pr-changed-files-container.test.js @@ -115,6 +115,17 @@ describe('PullRequestChangedFilesContainer', function() { assert.isTrue(wrapper.instance().state.isLoading); await assert.async.strictEqual(window.fetch.callCount, 2); }); + it('disposes MFP subscription on unmount', async function() { + const wrapper = shallow(buildApp()); + await assert.async.isTrue(wrapper.update().find('MultiFilePatchController').exists()); + + const mfp = wrapper.find('MultiFilePatchController').prop('multiFilePatch'); + const [fp] = mfp.getFilePatches(); + assert.strictEqual(fp.emitter.listenerCountForEventName('change-render-status'), 1); + + wrapper.unmount(); + assert.strictEqual(fp.emitter.listenerCountForEventName('change-render-status'), 0); + }); }); describe('error states', function() { From f4954a795511be7a27a24f3f9d66a19b54443b51 Mon Sep 17 00:00:00 2001 From: simurai Date: Wed, 27 Feb 2019 20:15:17 +0900 Subject: [PATCH 2139/4053] Fix margins --- styles/github-dotcom-markdown.less | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/styles/github-dotcom-markdown.less b/styles/github-dotcom-markdown.less index d076f884d3..67f8e0e8f0 100644 --- a/styles/github-dotcom-markdown.less +++ b/styles/github-dotcom-markdown.less @@ -2,7 +2,7 @@ // This styles Markdown used in issueish panes. -@margin: 1.5em; +@margin: 1em; .github-DotComMarkdownHtml { @@ -179,4 +179,8 @@ color: @text-color-subtle; // same as .cross-referenced-event-label-number } + .js-suggested-changes-blob { // gets used for suggested changes + margin: @margin 0; + } + } From e456400d7d804de5143c37f0e20b1f0566463e90 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 27 Feb 2019 12:16:42 +0000 Subject: [PATCH 2140/4053] chore(package): update sinon to version 7.2.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 13e84b5618..3bce85d377 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "nyc": "13.3.0", "relay-compiler": "3.0.0", "semver": "5.6.0", - "sinon": "7.2.4", + "sinon": "7.2.5", "test-until": "1.1.1" }, "consumedServices": { From 76a54d0fa0086bb15bb1572e21aacb7ef45874da Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 27 Feb 2019 12:16:47 +0000 Subject: [PATCH 2141/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b0775d449..2496c48f57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1265,22 +1265,14 @@ } }, "@sinonjs/samsam": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.1.1.tgz", - "integrity": "sha512-ILlwvQUwAiaVBzr3qz8oT1moM7AIUHqUc2UmEjQcH9lLe+E+BZPwUMuc9FFojMswRK4r96x5zDTTrowMLw/vuA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.2.0.tgz", + "integrity": "sha512-j5F1rScewLtx6pbTK0UAjA3jJj4RYiSKOix53YWv+Jzy/AZ69qHxUpU8fwVLjyKbEEud9QrLpv6Ggs7WqTimYw==", "dev": true, "requires": { "@sinonjs/commons": "^1.0.2", "array-from": "^2.1.1", "lodash": "^4.17.11" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } } }, "@sinonjs/text-encoding": { @@ -8049,14 +8041,14 @@ } }, "sinon": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.4.tgz", - "integrity": "sha512-FGlcfrkiBRfaEIKRw8s/9mk4nP4AMGswvKFixLo+AzsOhskjaBCHAHGLMd8pCJpQGS+9ZI71px6qoQUyvADeyA==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.5.tgz", + "integrity": "sha512-1c2KK6g5NQr9XNYCEcUbeFtBpKZD1FXEw0VX7gNhWUBtkchguT2lNdS7XmS7y64OpQWfSNeeV/f8py3NNcQ63Q==", "dev": true, "requires": { "@sinonjs/commons": "^1.3.0", "@sinonjs/formatio": "^3.1.0", - "@sinonjs/samsam": "^3.1.1", + "@sinonjs/samsam": "^3.2.0", "diff": "^3.5.0", "lolex": "^3.1.0", "nise": "^1.4.10", From fafc766e035da60314f3c9a8184f2c619a45b090 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 27 Feb 2019 12:05:28 -0500 Subject: [PATCH 2142/4053] Use @atom/mocha-test-runner instead of my fork --- package-lock.json | 1005 ++++++++++++++++++++++++++++++++++----------- package.json | 4 +- 2 files changed, 770 insertions(+), 239 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2496c48f57..4944fe8e6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,69 @@ "@babel/core": "7.x" } }, + "@atom/mocha-test-runner": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@atom/mocha-test-runner/-/mocha-test-runner-1.5.0.tgz", + "integrity": "sha512-Ctyw49G4rHv7v8YNyP01YyW1lPyMskv/gR6cxDsR+NDYSlpH8th07LTZDoESynhfLEYBuje0oYIrlosLvNPQ6g==", + "dev": true, + "requires": { + "diff": "4.0.1", + "etch": "0.14.0", + "grim": "^2.0.1", + "klaw-sync": "6.0.0", + "less": "3.9.0", + "mocha": "6.0.2", + "temp": "0.9.0", + "tmp": "0.0.33" + }, + "dependencies": { + "diff": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", + "dev": true + }, + "etch": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/etch/-/etch-0.14.0.tgz", + "integrity": "sha512-puqbFxz7lSm+YK6Q+bvRkNndRv6PRvGscSEhcFjmtL4nX/Az5rRCNPvK3aVTde85c/L5X0vI5kqfnpYddRalJQ==", + "dev": true + }, + "less": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", + "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.4.1", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + } + } + }, "@babel/code-frame": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", @@ -1281,20 +1344,6 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "@smashwilson/atom-mocha-test-runner": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@smashwilson/atom-mocha-test-runner/-/atom-mocha-test-runner-1.4.0.tgz", - "integrity": "sha512-Zp50XTy2QZEk53PUxXQ1kLTAkSwEuM2X7JXtMGLRWuU68piFghkXGaopTrjXK3CwgzmmFi26m65sTCrXg3zqbg==", - "dev": true, - "requires": { - "diff": "3.5.0", - "etch": "^0.8.0", - "grim": "^2.0.1", - "less": "^3.7.1", - "mocha": "^5.2.0", - "tmp": "0.0.31" - } - }, "@types/node": { "version": "10.12.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.12.tgz", @@ -1343,6 +1392,12 @@ "json-schema-traverse": "^0.3.0" } }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, "ansi-escapes": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", @@ -1975,16 +2030,10 @@ } } }, - "browser-split": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/browser-split/-/browser-split-0.0.1.tgz", - "integrity": "sha1-ewl1dPjj6tYG+0Zk5krf3aKYGpM=", - "dev": true - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha1-uqVZ7hTO1zRSIputcyZGfGH6vWA=", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "browserslist": { @@ -2118,12 +2167,6 @@ } } }, - "camelize": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=", - "dev": true - }, "caniuse-lite": { "version": "1.0.30000939", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000939.tgz", @@ -2670,6 +2713,12 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -2749,12 +2798,6 @@ } } }, - "dom-walk": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", - "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", - "dev": true - }, "domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", @@ -3133,17 +3176,6 @@ "prr": "~1.0.1" } }, - "error": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/error/-/error-4.4.0.tgz", - "integrity": "sha1-v2n/JR+0onnBmtzNqmth6Q2b8So=", - "dev": true, - "requires": { - "camelize": "^1.0.0", - "string-template": "~0.2.0", - "xtend": "~4.0.0" - } - }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", @@ -3517,24 +3549,6 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, - "etch": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/etch/-/etch-0.8.0.tgz", - "integrity": "sha1-VPYZV0NG+KPueXP1T7vQG1YnItY=", - "dev": true, - "requires": { - "virtual-dom": "^2.0.1" - } - }, - "ev-store": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ev-store/-/ev-store-7.0.0.tgz", - "integrity": "sha1-GrDH+CE2UF3XSzHRdwHLK+bSZVg=", - "dev": true, - "requires": { - "individual": "^3.0.0" - } - }, "event-kit": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.5.3.tgz", @@ -3596,6 +3610,15 @@ "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==", "dev": true }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", @@ -3867,6 +3890,46 @@ "locate-path": "^2.0.0" } }, + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + } + } + }, "flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -4106,14 +4169,28 @@ "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", "dev": true }, - "global": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", - "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", "dev": true, "requires": { - "min-document": "^2.19.0", - "process": "~0.5.1" + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" } }, "globals": { @@ -4269,9 +4346,9 @@ } }, "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, "hock": { @@ -4284,6 +4361,15 @@ "url-equal": "0.1.2-1" } }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, "hosted-git-info": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", @@ -4424,12 +4510,6 @@ "repeating": "^2.0.0" } }, - "individual": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/individual/-/individual-3.0.0.tgz", - "integrity": "sha1-58pPhfiVewGHNPKFdQ3CLsL5hi0=", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -4702,12 +4782,6 @@ "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", "dev": true }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", - "dev": true - }, "is-odd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", @@ -4967,6 +5041,15 @@ "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true }, + "klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11" + } + }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -4976,32 +5059,6 @@ "invert-kv": "^1.0.0" } }, - "less": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/less/-/less-3.8.1.tgz", - "integrity": "sha512-8HFGuWmL3FhQR0aH89escFNBQH/nEiYPP2ltDFdQw2chE28Yx2E3lhAIq9Y2saYwLSwa699s4dBVEfCY8Drf7Q==", - "dev": true, - "requires": { - "clone": "^2.1.2", - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.4.1", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "request": "^2.83.0", - "source-map": "~0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, "level-codec": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.0.tgz", @@ -5161,6 +5218,46 @@ "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "lolex": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz", @@ -5203,6 +5300,15 @@ } } }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -5395,15 +5501,6 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", - "dev": true, - "requires": { - "dom-walk": "^0.1.0" - } - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -5478,55 +5575,34 @@ } }, "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.0.2.tgz", + "integrity": "sha512-RtTJsmmToGyeTznSOMoM6TPEk1A84FQaHIciKrRqARZx+B5ccJ5tXlmJzEKGBxZdqk9UjpRsesZTUkZmR5YnuQ==", "dev": true, "requires": { + "ansi-colors": "3.2.3", "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", + "debug": "3.2.6", "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "glob": "7.1.2", + "findup-sync": "2.0.0", + "glob": "7.1.3", "growl": "1.10.5", - "he": "1.1.1", + "he": "1.2.0", + "js-yaml": "3.12.0", + "log-symbols": "2.2.0", "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "mocha-junit-reporter": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.18.0.tgz", - "integrity": "sha512-y3XuqKa2+HRYtg0wYyhW/XsLm2Ps+pqf9HaTAt7+MVUAKFJaNAHOrNseTZo9KCxjfIbxUWwckP5qCDDPUmjSWA==", - "dev": true, - "requires": { - "debug": "^2.2.0", - "md5": "^2.1.0", - "mkdirp": "~0.5.1", - "strip-ansi": "^4.0.0", - "xml": "^1.0.0" + "ms": "2.1.1", + "node-environment-flags": "1.0.4", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "12.0.5", + "yargs-parser": "11.1.1", + "yargs-unparser": "1.5.0" }, "dependencies": { "ansi-regex": { @@ -5535,34 +5611,295 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } - } - } - }, - "mocha-multi-reporters": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz", - "integrity": "sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI=", - "dev": true, - "requires": { - "debug": "^3.1.0", - "lodash": "^4.16.4" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mem": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "mocha-junit-reporter": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.18.0.tgz", + "integrity": "sha512-y3XuqKa2+HRYtg0wYyhW/XsLm2Ps+pqf9HaTAt7+MVUAKFJaNAHOrNseTZo9KCxjfIbxUWwckP5qCDDPUmjSWA==", + "dev": true, + "requires": { + "debug": "^2.2.0", + "md5": "^2.1.0", + "mkdirp": "~0.5.1", + "strip-ansi": "^4.0.0", + "xml": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "mocha-multi-reporters": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz", + "integrity": "sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI=", + "dev": true, + "requires": { + "debug": "^3.1.0", + "lodash": "^4.16.4" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" } } } @@ -5655,12 +5992,6 @@ "semver": "^5.4.1" } }, - "next-tick": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-0.2.2.tgz", - "integrity": "sha1-ddpKkn7liH45BliABltzNkE7MQ0=", - "dev": true - }, "nice-try": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", @@ -5705,6 +6036,15 @@ "lodash.toarray": "^4.4.0" } }, + "node-environment-flags": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.4.tgz", + "integrity": "sha512-M9rwCnWVLW7PX+NUWe3ejEdiLYinRpsEre9hMkU/6NS4h+EEulYaDH1gCEZ2gyXsmw+RXYDaV2JkkTNcsPDJ0Q==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, "node-fetch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", @@ -6948,6 +7288,16 @@ "has": "^1.0.1" } }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -7038,12 +7388,24 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -7086,6 +7448,12 @@ "error-ex": "^1.2.0" } }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, "parse5": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", @@ -7256,12 +7624,6 @@ "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=" }, - "process": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", - "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", - "dev": true - }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -7846,6 +8208,16 @@ "path-parse": "^1.0.6" } }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -8337,12 +8709,6 @@ } } }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", - "dev": true - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -8799,15 +9165,6 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" }, - "tmp": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", - "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - }, "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", @@ -9156,22 +9513,6 @@ "extsprintf": "^1.2.0" } }, - "virtual-dom": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/virtual-dom/-/virtual-dom-2.1.1.tgz", - "integrity": "sha1-gO2i1IG57eDASRGM78tKBfIdE3U=", - "dev": true, - "requires": { - "browser-split": "0.0.1", - "error": "^4.3.0", - "ev-store": "^7.0.0", - "global": "^4.3.0", - "is-object": "^1.0.1", - "next-tick": "^0.2.2", - "x-is-array": "0.1.0", - "x-is-string": "0.1.0" - } - }, "what-the-diff": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/what-the-diff/-/what-the-diff-0.5.0.tgz", @@ -9260,18 +9601,6 @@ "mkdirp": "^0.5.1" } }, - "x-is-array": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/x-is-array/-/x-is-array-0.1.0.tgz", - "integrity": "sha1-3lIBcdR7P0FvVYfWKbidJrEtwp0=", - "dev": true - }, - "x-is-string": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", - "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", - "dev": true - }, "xml": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", @@ -9324,6 +9653,208 @@ "camelcase": "^4.1.0" } }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mem": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-limit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "yauzl": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", diff --git a/package.json b/package.json index 3bce85d377..5cca104dbc 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "yubikiri": "1.0.0" }, "devDependencies": { - "@smashwilson/atom-mocha-test-runner": "1.4.0", + "@atom/mocha-test-runner": "1.5.0", "babel-plugin-istanbul": "5.1.1", "chai": "4.2.0", "chai-as-promised": "7.1.1", @@ -95,7 +95,7 @@ "hock": "1.3.3", "lodash.isequalwith": "4.4.0", "mkdirp": "0.5.1", - "mocha": "5.2.0", + "mocha": "6.0.2", "mocha-junit-reporter": "1.18.0", "mocha-multi-reporters": "1.1.7", "mocha-stress": "1.0.0", From 641164dfed5142c39b0a865ae3aebc6524aa815e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 27 Feb 2019 12:05:40 -0500 Subject: [PATCH 2143/4053] Correct the import --- test/runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runner.js b/test/runner.js index 431f8f4036..af37ac161a 100644 --- a/test/runner.js +++ b/test/runner.js @@ -1,4 +1,4 @@ -import {createRunner} from '@smashwilson/atom-mocha-test-runner'; +import {createRunner} from '@atom/mocha-test-runner'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import path from 'path'; From c2041e1f38e45b3eb6cd751fc7acfe8de8c79a14 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 27 Feb 2019 13:27:19 -0500 Subject: [PATCH 2144/4053] Regenerate package-lock --- package-lock.json | 3786 +++++++++++++++------------------------------ 1 file changed, 1238 insertions(+), 2548 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4944fe8e6a..b0dc9c9763 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,53 +40,6 @@ "mocha": "6.0.2", "temp": "0.9.0", "tmp": "0.0.33" - }, - "dependencies": { - "diff": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", - "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", - "dev": true - }, - "etch": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/etch/-/etch-0.14.0.tgz", - "integrity": "sha512-puqbFxz7lSm+YK6Q+bvRkNndRv6PRvGscSEhcFjmtL4nX/Az5rRCNPvK3aVTde85c/L5X0vI5kqfnpYddRalJQ==", - "dev": true - }, - "less": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", - "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==", - "dev": true, - "requires": { - "clone": "^2.1.2", - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.4.1", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "request": "^2.83.0", - "source-map": "~0.6.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - } } }, "@babel/code-frame": { @@ -116,62 +69,6 @@ "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" - }, - "dependencies": { - "@babel/parser": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", - "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==" - }, - "@babel/traverse": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", - "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.3.4", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.3.4", - "@babel/types": "^7.3.4", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.11" - } - }, - "@babel/types": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", - "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "globals": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", - "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } } }, "@babel/generator": { @@ -184,23 +81,6 @@ "lodash": "^4.17.11", "source-map": "^0.5.0", "trim-right": "^1.0.1" - }, - "dependencies": { - "@babel/types": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", - "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } } }, "@babel/helper-annotate-as-pure": { @@ -227,23 +107,6 @@ "requires": { "@babel/types": "^7.3.0", "esutils": "^2.0.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.3.tgz", - "integrity": "sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ==", - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } } }, "@babel/helper-call-delegate": { @@ -267,73 +130,6 @@ "@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-replace-supers": "^7.3.4", "@babel/helper-split-export-declaration": "^7.0.0" - }, - "dependencies": { - "@babel/helper-replace-supers": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.3.4.tgz", - "integrity": "sha512-pvObL9WVf2ADs+ePg0jrqlhHoxRXlOa+SHRHzAXIz2xkYuOHfGl+fKxPMaS4Fq+uje8JQPobnertBBvyrWnQ1A==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.0.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.3.4", - "@babel/types": "^7.3.4" - } - }, - "@babel/parser": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", - "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==" - }, - "@babel/traverse": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", - "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.3.4", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.3.4", - "@babel/types": "^7.3.4", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.11" - } - }, - "@babel/types": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", - "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "globals": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", - "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } } }, "@babel/helper-define-map": { @@ -344,13 +140,6 @@ "@babel/helper-function-name": "^7.1.0", "@babel/types": "^7.0.0", "lodash": "^4.17.10" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } } }, "@babel/helper-explode-assignable-expression": { @@ -415,13 +204,6 @@ "@babel/template": "^7.2.2", "@babel/types": "^7.2.2", "lodash": "^4.17.10" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } } }, "@babel/helper-optimise-call-expression": { @@ -443,13 +225,6 @@ "integrity": "sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==", "requires": { "lodash": "^4.17.10" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } } }, "@babel/helper-remap-async-to-generator": { @@ -465,14 +240,14 @@ } }, "@babel/helper-replace-supers": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz", - "integrity": "sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.3.4.tgz", + "integrity": "sha512-pvObL9WVf2ADs+ePg0jrqlhHoxRXlOa+SHRHzAXIz2xkYuOHfGl+fKxPMaS4Fq+uje8JQPobnertBBvyrWnQ1A==", "requires": { "@babel/helper-member-expression-to-functions": "^7.0.0", "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.2.3", - "@babel/types": "^7.0.0" + "@babel/traverse": "^7.3.4", + "@babel/types": "^7.3.4" } }, "@babel/helper-simple-access": { @@ -511,23 +286,6 @@ "@babel/template": "^7.1.2", "@babel/traverse": "^7.1.5", "@babel/types": "^7.3.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", - "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } } }, "@babel/highlight": { @@ -538,45 +296,12 @@ "chalk": "^2.0.0", "esutils": "^2.0.2", "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } } }, "@babel/parser": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.3.tgz", - "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==" + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", + "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==" }, "@babel/plugin-proposal-async-generator-functions": { "version": "7.2.0", @@ -719,45 +444,27 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.2.0.tgz", - "integrity": "sha512-vDTgf19ZEV6mx35yiPJe4fS02mPQUUcBNwWQSZFXSzTSbsJFQvHt7DqyS3LK8oOWALFOsJ+8bbqBgkirZteD5Q==", - "dev": true, + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.3.4.tgz", + "integrity": "sha512-blRr2O8IOZLAOJklXLV4WhcEzpYafYQKSGT3+R26lWG41u/FODJuBggehtOwilVAcFu393v3OFj+HmaE6tVjhA==", "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "lodash": "^4.17.10" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } + "lodash": "^4.17.11" } }, "@babel/plugin-transform-classes": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.3.3.tgz", - "integrity": "sha512-n0CLbsg7KOXsMF4tSTLCApNMoXk0wOPb0DYfsOO1e7SfIb9gOyfbpKI2MZ+AXfqvlfzq2qsflJ1nEns48Caf2w==", - "dev": true, + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.3.4.tgz", + "integrity": "sha512-J9fAvCFBkXEvBimgYxCjvaVDzL6thk0j0dBvCeZmIUDBwyt+nv6HfbImsSrWsYXfDNDivyANgJlFXDUWRTZBuA==", "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", "@babel/helper-define-map": "^7.1.0", "@babel/helper-function-name": "^7.1.0", "@babel/helper-optimise-call-expression": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.1.0", + "@babel/helper-replace-supers": "^7.3.4", "@babel/helper-split-export-declaration": "^7.0.0", "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", - "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", - "dev": true - } } }, "@babel/plugin-transform-computed-properties": { @@ -804,9 +511,9 @@ } }, "@babel/plugin-transform-flow-strip-types": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.2.3.tgz", - "integrity": "sha512-xnt7UIk9GYZRitqCnsVMjQK1O2eKZwFB3CvvHjf5SGx6K6vr/MScCKQDnf1DxRaj501e3pXjti+inbSXX2ZUoQ==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.3.4.tgz", + "integrity": "sha512-PmQC9R7DwpBFA+7ATKMyzViz3zCaMNouzZMPZN2K5PnbBbtL3AXFYTkDk+Hey5crQq2A90UG5Uthz0mel+XZrA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1032,20 +739,6 @@ "requires": { "core-js": "^2.5.7", "regenerator-runtime": "^0.12.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", - "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", - "dev": true - } } }, "@babel/preset-env": { @@ -1096,106 +789,6 @@ "invariant": "^2.2.2", "js-levenshtein": "^1.1.3", "semver": "^5.3.0" - }, - "dependencies": { - "@babel/helper-replace-supers": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.3.4.tgz", - "integrity": "sha512-pvObL9WVf2ADs+ePg0jrqlhHoxRXlOa+SHRHzAXIz2xkYuOHfGl+fKxPMaS4Fq+uje8JQPobnertBBvyrWnQ1A==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.0.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.3.4", - "@babel/types": "^7.3.4" - } - }, - "@babel/parser": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", - "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==" - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.4.tgz", - "integrity": "sha512-j7VQmbbkA+qrzNqbKHrBsW3ddFnOeva6wzSe/zB7T+xaxGc+RCpwo44wCmRixAIGRoIpmVgvzFzNJqQcO3/9RA==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.3.4.tgz", - "integrity": "sha512-blRr2O8IOZLAOJklXLV4WhcEzpYafYQKSGT3+R26lWG41u/FODJuBggehtOwilVAcFu393v3OFj+HmaE6tVjhA==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "lodash": "^4.17.11" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.3.4.tgz", - "integrity": "sha512-J9fAvCFBkXEvBimgYxCjvaVDzL6thk0j0dBvCeZmIUDBwyt+nv6HfbImsSrWsYXfDNDivyANgJlFXDUWRTZBuA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-define-map": "^7.1.0", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.3.4", - "@babel/helper-split-export-declaration": "^7.0.0", - "globals": "^11.1.0" - } - }, - "@babel/traverse": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", - "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.3.4", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.3.4", - "@babel/types": "^7.3.4", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.11" - } - }, - "@babel/types": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", - "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "globals": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", - "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } } }, "@babel/preset-react": { @@ -1211,18 +804,11 @@ } }, "@babel/runtime": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz", - "integrity": "sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.4.tgz", + "integrity": "sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g==", "requires": { "regenerator-runtime": "^0.12.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", - "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" - } } }, "@babel/template": { @@ -1236,61 +822,29 @@ } }, "@babel/traverse": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", - "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", + "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", "requires": { "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.2.2", + "@babel/generator": "^7.3.4", "@babel/helper-function-name": "^7.1.0", "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.2.3", - "@babel/types": "^7.2.2", + "@babel/parser": "^7.3.4", + "@babel/types": "^7.3.4", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.10" - }, - "dependencies": { - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "globals": { - "version": "11.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", - "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==" - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } + "lodash": "^4.17.11" } }, "@babel/types": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.2.tgz", - "integrity": "sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", + "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } } }, "@mrmlnc/readdir-enhanced": { @@ -1345,9 +899,9 @@ "dev": true }, "@types/node": { - "version": "10.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.12.tgz", - "integrity": "sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==", + "version": "11.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.5.tgz", + "integrity": "sha512-vVjM0SVzgaOUpflq4GYBvCpozes8OgIIS5gVXVka+OfK3hvnkC1i93U8WiY2OtNE4XUWyyy/86Kf6e0IHTQw1Q==", "dev": true }, "abstract-leveldown": { @@ -1360,9 +914,9 @@ } }, "acorn": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", - "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true }, "acorn-jsx": { @@ -1381,15 +935,14 @@ } }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.2.tgz", + "integrity": "sha512-4UFy0/LgDo7Oa/+wOAlj44tp9K78u38E5/359eSrqEp1Z5PdVfimCcs7SluXMP755RUQu6d2b4AvF0R1C9RZjg==", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ansi-colors": { @@ -1410,15 +963,17 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "are-we-there-yet": { "version": "1.1.5", @@ -1462,7 +1017,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, "arr-union": { @@ -1547,9 +1102,12 @@ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", @@ -1602,9 +1160,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, "aws-sign2": { @@ -1613,10 +1171,9 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha1-1NDpudv8p3vwjusKikcVUP454ok=", - "dev": true + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "axobject-query": { "version": "2.0.2", @@ -1636,9 +1193,42 @@ "chalk": "^1.1.3", "esutils": "^2.0.2", "js-tokens": "^3.0.2" - } - }, - "babel-eslint": { + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-eslint": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", @@ -1668,51 +1258,6 @@ "find-up": "^3.0.0", "istanbul-lib-instrument": "^3.0.0", "test-exclude": "^5.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", - "dev": true - } } }, "babel-plugin-macros": { @@ -1774,13 +1319,21 @@ } }, "babel-runtime": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.25.0.tgz", - "integrity": "sha1-M7mOql1IK7AajRqmtDetKwGuxBw=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { "core-js": "^2.4.0", - "regenerator-runtime": "^0.10.0" + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } } }, "babel-traverse": { @@ -1800,58 +1353,39 @@ "lodash": "^4.17.4" }, "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" + "ms": "2.0.0" } }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } } }, "babel-types": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz", - "integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", + "babel-runtime": "^6.26.0", "esutils": "^2.0.2", - "lodash": "^4.2.0", - "to-fast-properties": "^1.0.1" + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" }, "dependencies": { "to-fast-properties": { @@ -1863,9 +1397,9 @@ } }, "babylon": { - "version": "6.17.4", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz", - "integrity": "sha512-kChlV+0SXkjE0vUn9OZ7pBMWRFd8uq3mZe8x1K6jhuNcAFAtEnjchFAqB+dYEXKyd+JpT6eppRR78QAr5gTsUw==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, "balanced-match": { @@ -1876,7 +1410,7 @@ "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { "cache-base": "^1.0.1", @@ -1929,10 +1463,9 @@ } }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "requires": { "tweetnacl": "^0.14.3" } @@ -1951,39 +1484,10 @@ "bl": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha1-oWCRFxcQPAdBDO9j71Gzl8Alr5w=", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "boolbase": { @@ -1993,9 +1497,9 @@ "dev": true }, "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2080,12 +1584,6 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -2094,7 +1592,7 @@ "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { "collection-visit": "^1.0.0", @@ -2120,13 +1618,6 @@ "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "requires": { "callsites": "^2.0.0" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" - } } }, "caller-path": { @@ -2138,15 +1629,14 @@ } }, "callsites": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", - "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" }, "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", "dev": true }, "camelcase-keys": { @@ -2194,23 +1684,20 @@ "chai-as-promised": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha1-CGRdgl3rhpbuYXJdv1kMAS6wDKA=", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", "dev": true, "requires": { "check-error": "^1.0.2" } }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "chardet": { @@ -2254,14 +1741,14 @@ } }, "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -2302,25 +1789,45 @@ "dev": true }, "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", "wrap-ansi": "^2.0.0" }, "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -2331,12 +1838,6 @@ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -2378,24 +1879,18 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "colors": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", - "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=", - "dev": true - }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "requires": { "delayed-stream": "~1.0.0" } }, "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", "dev": true }, "compare-sets": { @@ -2446,9 +1941,9 @@ "dev": true }, "core-js": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.0.tgz", - "integrity": "sha1-VpwFCRi+ZIazg3VSAorgRmtxcIY=" + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" }, "core-util-is": { "version": "1.0.2", @@ -2465,31 +1960,6 @@ "js-yaml": "^3.9.0", "lodash.get": "^4.4.2", "parse-json": "^4.0.0" - }, - "dependencies": { - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - } } }, "cross-env": { @@ -2500,30 +1970,17 @@ "requires": { "cross-spawn": "^6.0.5", "is-windows": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - } } }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } @@ -2553,9 +2010,9 @@ } }, "css-what": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz", - "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", "dev": true }, "currently-unhandled": { @@ -2582,12 +2039,11 @@ } }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", - "dev": true, + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "decamelize": { @@ -2653,19 +2109,18 @@ } }, "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, "requires": { - "foreach": "^2.0.5", - "object-keys": "^1.0.8" + "object-keys": "^1.0.12" } }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { "is-descriptor": "^1.0.2", @@ -2725,9 +2180,9 @@ "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", + "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", "dev": true }, "dir-glob": { @@ -2737,23 +2192,6 @@ "dev": true, "requires": { "path-type": "^3.0.0" - }, - "dependencies": { - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } } }, "discontinuous-range": { @@ -2763,39 +2201,22 @@ "dev": true }, "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } + "esutils": "^2.0.2" } }, "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", "dev": true, "requires": { - "domelementtype": "~1.1.1", - "entities": "~1.1.1" - }, - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", - "dev": true - } + "domelementtype": "^1.3.0", + "entities": "^1.1.1" } }, "domelementtype": { @@ -2834,126 +2255,15 @@ "request": "^2.88.0", "rimraf": "^2.5.4", "tar": "^4.4.7" - }, - "dependencies": { - "ajv": { - "version": "6.9.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.2.tgz", - "integrity": "sha512-4UFy0/LgDo7Oa/+wOAlj44tp9K78u38E5/359eSrqEp1Z5PdVfimCcs7SluXMP755RUQu6d2b4AvF0R1C9RZjg==", - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "mime-db": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", - "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" - }, - "mime-types": { - "version": "2.1.22", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", - "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", - "requires": { - "mime-db": "~1.38.0" - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - } } }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "electron-devtools-installer": { @@ -3001,12 +2311,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true } } }, @@ -3024,14 +2328,6 @@ "recast": "^0.12.6", "resolve": "^1.5.0", "source-map": "^0.5.6" - }, - "dependencies": { - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - } } }, "electron-mksnapshot": { @@ -3079,7 +2375,7 @@ "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "requires": { "once": "^1.4.0" } @@ -3121,23 +2417,6 @@ "raf": "^3.4.0", "rst-selector-parser": "^2.2.3", "string.prototype.trim": "^1.1.2" - }, - "dependencies": { - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true - } } }, "enzyme-adapter-react-16": { @@ -3156,13 +2435,14 @@ } }, "enzyme-adapter-utils": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.9.0.tgz", - "integrity": "sha512-uMe4xw4l/Iloh2Fz+EO23XUYMEQXj5k/5ioLUXCNOUCI8Dml5XQMO9+QwUq962hBsY5qftfHHns+d990byWHvg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.10.0.tgz", + "integrity": "sha512-VnIXJDYVTzKGbdW+lgK8MQmYHJquTQZiGzu/AseCZ7eHtOMAj4Rtvk8ZRopodkfPves0EXaHkXBDkVhPa3t0jA==", "dev": true, "requires": { "function.prototype.name": "^1.1.0", "object.assign": "^4.1.0", + "object.fromentries": "^2.0.0", "prop-types": "^15.6.2", "semver": "^5.6.0" } @@ -3177,46 +2457,47 @@ } }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "requires": { "is-arrayish": "^0.2.1" } }, "es-abstract": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz", - "integrity": "sha1-zOh9UY8Elok7GjDNhGGDVTVIBoE=", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", "dev": true, "requires": { - "es-to-primitive": "^1.1.1", + "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" } }, "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "dev": true, "requires": { - "is-callable": "^1.1.1", + "is-callable": "^1.1.4", "is-date-object": "^1.0.1", - "is-symbol": "^1.0.1" + "is-symbol": "^1.0.2" } }, "es6-promise": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", - "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", + "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==", "dev": true }, "es6-promisify": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { @@ -3272,103 +2553,26 @@ "text-table": "^0.2.0" }, "dependencies": { - "ajv": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", - "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "doctrine": { + "import-fresh": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", "dev": true, "requires": { - "esutils": "^2.0.2" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "globals": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", - "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, "strip-ansi": { @@ -3379,15 +2583,6 @@ "requires": { "ansi-regex": "^3.0.0" } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -3413,18 +2608,18 @@ "dev": true }, "eslint-plugin-flowtype": { - "version": "2.46.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.46.3.tgz", - "integrity": "sha1-foQTHYfvGLSWsYEESFkzdIYLTo4=", + "version": "2.50.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.50.3.tgz", + "integrity": "sha512-X+AoKVOr7Re0ko/yEXyM5SSZ0tazc6ffdIOocp2fFUlWoDt7DV0Bz99mngOkAFLOAWjqRA5jPwqUCbrx13XoxQ==", "dev": true, "requires": { - "lodash": "^4.15.0" + "lodash": "^4.17.10" } }, "eslint-plugin-jasmine": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jasmine/-/eslint-plugin-jasmine-2.9.3.tgz", - "integrity": "sha1-Bb86uCfXkWkc7ujCGVm5wNVqaww=", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jasmine/-/eslint-plugin-jasmine-2.10.1.tgz", + "integrity": "sha1-VzO3CedR9LxA4x4cFpib0s377Jc=", "dev": true }, "eslint-plugin-jsx-a11y": { @@ -3443,15 +2638,6 @@ "jsx-ast-utils": "^2.0.1" }, "dependencies": { - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, "jsx-ast-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", @@ -3480,6 +2666,18 @@ "has": "^1.0.1", "jsx-ast-utils": "^1.3.4", "object.assign": "^4.0.4" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + } } }, "eslint-scope": { @@ -3516,9 +2714,9 @@ } }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.0.1", @@ -3549,19 +2747,25 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, + "etch": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/etch/-/etch-0.14.0.tgz", + "integrity": "sha512-puqbFxz7lSm+YK6Q+bvRkNndRv6PRvGscSEhcFjmtL4nX/Az5rRCNPvK3aVTde85c/L5X0vI5kqfnpYddRalJQ==", + "dev": true + }, "event-kit": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.5.3.tgz", "integrity": "sha512-b7Qi1JNzY4BfAYfnIRanLk0DOD1gdkWHT4GISIn8Q2tAf3LpU8SP2CMwWaq40imYoKWbtN4ZhbSRxvsnikooZQ==" }, "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", @@ -3584,6 +2788,15 @@ "to-regex": "^3.0.1" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -3601,14 +2814,19 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, "expand-template": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz", - "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" }, "expand-tilde": { "version": "2.0.2", @@ -3620,10 +2838,9 @@ } }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -3638,7 +2855,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -3655,32 +2872,12 @@ "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - } } }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { "array-unique": "^0.3.2", @@ -3752,6 +2949,23 @@ "debug": "2.6.9", "mkdirp": "0.5.1", "yauzl": "2.4.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "extsprintf": { @@ -3760,10 +2974,9 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-future": { "version": "1.0.2", @@ -3783,6 +2996,17 @@ "is-glob": "^4.0.0", "merge2": "^1.2.3", "micromatch": "^3.1.10" + }, + "dependencies": { + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + } } }, "fast-json-stable-stringify": { @@ -3882,12 +3106,12 @@ } }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "^3.0.0" } }, "findup-sync": { @@ -3900,17 +3124,6 @@ "is-glob": "^3.1.0", "micromatch": "^3.0.4", "resolve-dir": "^1.0.1" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } } }, "flat": { @@ -3939,31 +3152,6 @@ "flatted": "^2.0.0", "rimraf": "2.6.3", "write": "1.0.3" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } } }, "flatted": { @@ -3978,24 +3166,18 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "1.0.6", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, @@ -4011,7 +3193,7 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=" + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { "version": "4.0.3", @@ -4072,18 +3254,6 @@ "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wide-align": "^1.1.0" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } } }, "get-caller-file": { @@ -4105,10 +3275,25 @@ "dev": true }, "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } }, "get-value": { "version": "2.0.6", @@ -4130,9 +3315,9 @@ "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4150,17 +3335,6 @@ "requires": { "is-glob": "^3.1.0", "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } } }, "glob-to-regexp": { @@ -4194,10 +3368,9 @@ } }, "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" }, "globby": { "version": "9.0.0", @@ -4214,20 +3387,6 @@ "slash": "^2.0.0" }, "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -4237,9 +3396,9 @@ } }, "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "graphql": { "version": "14.1.1", @@ -4270,22 +3429,21 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "dev": true, + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "^5.1.0", + "ajv": "^6.5.5", "har-schema": "^2.0.0" } }, "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "^1.0.2" + "function-bind": "^1.1.1" } }, "has-ansi": { @@ -4371,44 +3529,35 @@ } }, "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, "htmlparser2": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", - "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", "dev": true, "requires": { - "domelementtype": "^1.3.0", + "domelementtype": "^1.3.1", "domhandler": "^2.3.0", "domutils": "^1.5.1", "entities": "^1.1.1", "inherits": "^2.0.1", - "readable-stream": "^3.0.6" + "readable-stream": "^3.1.1" }, "dependencies": { "readable-stream": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz", - "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } - }, - "string_decoder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", - "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } } } }, @@ -4440,12 +3589,6 @@ "requires": { "ms": "^2.1.1" } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true } } }, @@ -4486,13 +3629,12 @@ "dev": true }, "import-fresh": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", - "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" } }, "imurmurhash": { @@ -4502,13 +3644,10 @@ "dev": true }, "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true }, "inflight": { "version": "1.0.6", @@ -4527,7 +3666,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { "version": "6.2.2", @@ -4551,37 +3690,38 @@ }, "dependencies": { "ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, "strip-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", @@ -4589,15 +3729,14 @@ "dev": true, "requires": { "ansi-regex": "^4.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", + "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "dev": true + } } } } @@ -4605,15 +3744,15 @@ "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "requires": { "loose-envify": "^1.0.0" } }, "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "is-accessor-descriptor": { @@ -4650,22 +3789,13 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } - }, "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, "is-data-descriptor": { @@ -4748,12 +3878,12 @@ } }, "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "^2.1.0" } }, "is-number": { @@ -4782,27 +3912,11 @@ "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", "dev": true }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha1-dkZiRnH9fqVYzNmieVGC8pWPGyQ=", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", - "dev": true - } - } - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, "requires": { "isobject": "^3.0.1" } @@ -4840,10 +3954,13 @@ "dev": true }, "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } }, "is-typedarray": { "version": "1.0.0", @@ -4859,14 +3976,13 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -4877,7 +3993,8 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true }, "isomorphic-fetch": { "version": "2.2.1", @@ -4936,14 +4053,14 @@ "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" }, "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", - "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", + "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -4952,8 +4069,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsesc": { "version": "2.5.2", @@ -4971,10 +4087,9 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -5038,7 +4153,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, "klaw-sync": { @@ -5051,12 +4166,38 @@ } }, "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "less": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", + "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==", "dev": true, "requires": { - "invert-kv": "^1.0.0" + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "mime": "^1.4.1", + "mkdirp": "^0.5.0", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } } }, "level-codec": { @@ -5098,9 +4239,15 @@ "prebuild-install": "^4.0.0" }, "dependencies": { + "expand-template": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz", + "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==", + "dev": true + }, "nan": { "version": "2.10.0", - "resolved": "http://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", "dev": true }, @@ -5152,32 +4299,31 @@ } }, "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", + "parse-json": "^4.0.0", + "pify": "^3.0.0", "strip-bom": "^3.0.0" } }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "lodash.escape": { "version": "4.0.1", @@ -5225,37 +4371,6 @@ "dev": true, "requires": { "chalk": "^2.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "lolex": { @@ -5265,11 +4380,11 @@ "dev": true }, "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "requires": { - "js-tokens": "^3.0.0" + "js-tokens": "^3.0.0 || ^4.0.0" } }, "loud-rejection": { @@ -5342,12 +4457,14 @@ } }, "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" } }, "meow": { @@ -5380,7 +4497,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -5391,6 +4508,15 @@ "strip-bom": "^2.0.0" } }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", @@ -5411,6 +4537,12 @@ "pinkie-promise": "^2.0.0" } }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -5478,22 +4610,22 @@ "optional": true }, "mime-db": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz", - "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=" + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" }, "mime-types": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz", - "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", "requires": { - "mime-db": "~1.29.0" + "mime-db": "~1.38.0" } }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "mimic-response": { @@ -5504,7 +4636,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } @@ -5521,13 +4653,6 @@ "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } } }, "minizlib": { @@ -5541,7 +4666,7 @@ "mixin-deep": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha1-pJ5yaNzhoNlpjkUybFYm3zVD0P4=", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, "requires": { "for-in": "^1.0.2", @@ -5551,7 +4676,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -5605,42 +4730,6 @@ "yargs-unparser": "1.5.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", - "dev": true - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -5650,57 +4739,10 @@ "ms": "^2.1.1" } }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "js-yaml": { @@ -5713,163 +4755,49 @@ "esprima": "^4.0.0" } }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", "dev": true, "requires": { - "invert-kv": "^2.0.0" + "has-flag": "^3.0.0" } - }, - "locate-path": { + } + } + }, + "mocha-junit-reporter": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.18.0.tgz", + "integrity": "sha512-y3XuqKa2+HRYtg0wYyhW/XsLm2Ps+pqf9HaTAt7+MVUAKFJaNAHOrNseTZo9KCxjfIbxUWwckP5qCDDPUmjSWA==", + "dev": true, + "requires": { + "debug": "^2.2.0", + "md5": "^2.1.0", + "mkdirp": "~0.5.1", + "strip-ansi": "^4.0.0", + "xml": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, - "mem": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", - "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", - "p-is-promise": "^2.0.0" + "ms": "2.0.0" } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "mocha-junit-reporter": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.18.0.tgz", - "integrity": "sha512-y3XuqKa2+HRYtg0wYyhW/XsLm2Ps+pqf9HaTAt7+MVUAKFJaNAHOrNseTZo9KCxjfIbxUWwckP5qCDDPUmjSWA==", - "dev": true, - "requires": { - "debug": "^2.2.0", - "md5": "^2.1.0", - "mkdirp": "~0.5.1", - "strip-ansi": "^4.0.0", - "xml": "^1.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "strip-ansi": { @@ -5894,12 +4822,12 @@ }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } } } @@ -5907,7 +4835,7 @@ "mocha-stress": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mocha-stress/-/mocha-stress-1.0.0.tgz", - "integrity": "sha1-cBx8fAQTvpn2QkvOUPxVFyIY1Kc=", + "integrity": "sha512-AeEgizAGFl+V6Bp3qzvjr9PcErlBw6qQOemDXKpbYIQaUvwKQscZmoBxq38ey0sLW9o3wwidbvaFSRQ3VApd+Q==", "dev": true }, "moment": { @@ -5922,10 +4850,9 @@ "dev": true }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "mute-stream": { "version": "0.0.7", @@ -5939,9 +4866,9 @@ "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==" }, "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha1-h59xUMstq3pHElkGbBBO7m4Pp8I=", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -5949,23 +4876,12 @@ "define-property": "^2.0.2", "extend-shallow": "^3.0.2", "fragment-cache": "^0.2.1", - "is-odd": "^2.0.0", "is-windows": "^1.0.2", "kind-of": "^6.0.2", "object.pick": "^1.3.0", "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", - "requires": { - "is-plain-object": "^2.0.4" - } - } } }, "napi-build-utils": { @@ -5980,22 +4896,22 @@ "dev": true }, "nearley": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.15.1.tgz", - "integrity": "sha512-8IUY/rUrKz2mIynUGh8k+tul1awMKEjeHHC5G3FHvvyAW6oq4mQfNp2c0BMea+sYZJvYcrrM6GmZVIle/GRXGw==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.16.0.tgz", + "integrity": "sha512-Tr9XD3Vt/EujXbZBv6UAHYoLUSMQAxSsTnm9K3koXzjzNWY195NqALeyrzLZBKzAkL3gl92BcSogqrHjD8QuUg==", "dev": true, "requires": { + "commander": "^2.19.0", "moo": "^0.4.3", - "nomnom": "~1.6.2", "railroad-diagrams": "^1.0.0", "randexp": "0.4.6", "semver": "^5.4.1" } }, "nice-try": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", - "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, "nise": { @@ -6020,10 +4936,9 @@ } }, "node-abi": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.5.tgz", - "integrity": "sha512-aa/UC6Nr3+tqhHGRsAuw/edz7/q9nnetBrKWxj6rpTtm+0X9T1qU7lIEHMS3yN9JwAbRiKUbRRFy1PLz/y3aaA==", - "dev": true, + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.7.1.tgz", + "integrity": "sha512-OV8Bq1OrPh6z+Y4dqwo05HqrRL9YNF7QVMRfq1/pguwKLG+q9UB/Lk0x5qXjO23JjJg+/jqCHSTaG1P3tfKfuw==", "requires": { "semver": "^5.4.1" } @@ -6065,29 +4980,19 @@ "semver": "^5.3.0" } }, - "nomnom": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz", - "integrity": "sha1-hKZqJgF0QI/Ft3oY+IjszET7aXE=", - "dev": true, - "requires": { - "colors": "0.5.x", - "underscore": "~1.4.4" - } - }, "noop-logger": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" }, "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", + "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } @@ -6104,7 +5009,7 @@ "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -6134,6 +5039,23 @@ "request": "^2.45.0", "single-line-log": "^1.1.2", "throttleit": "0.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "nullthrows": { @@ -6513,21 +5435,6 @@ "append-transform": "^1.0.0" } }, - "istanbul-lib-instrument": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", - "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", - "dev": true, - "requires": { - "@babel/generator": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", - "istanbul-lib-coverage": "^2.0.3", - "semver": "^5.5.0" - } - }, "istanbul-lib-report": { "version": "2.0.4", "bundled": true, @@ -7196,10 +6103,9 @@ } }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", @@ -7250,9 +6156,9 @@ "dev": true }, "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", + "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", "dev": true }, "object-visit": { @@ -7267,7 +6173,7 @@ "object.assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha1-lovxEA15Vrs8oIbwBvhGs7xACNo=", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { "define-properties": "^1.1.2", @@ -7277,14 +6183,26 @@ } }, "object.entries": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz", - "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", + "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", "dev": true, "requires": { "define-properties": "^1.1.2", - "es-abstract": "^1.6.1", - "function-bind": "^1.1.0", + "es-abstract": "^1.11.0", + "function-bind": "^1.1.1", "has": "^1.0.1" } }, @@ -7308,15 +6226,15 @@ } }, "object.values": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.0.4.tgz", - "integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.6.1", - "function-bind": "^1.1.0", - "has": "^1.0.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" } }, "once": { @@ -7372,14 +6290,14 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" } }, "os-tmpdir": { @@ -7407,27 +6325,27 @@ "dev": true }, "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.0.0" } }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", "dev": true }, "parent-module": { @@ -7437,15 +6355,23 @@ "dev": true, "requires": { "callsites": "^3.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", + "dev": true + } } }, "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "requires": { - "error-ex": "^1.2.0" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, "parse-passwd": { @@ -7510,15 +6436,23 @@ "dev": true, "requires": { "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } } }, "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "pify": "^2.0.0" + "pify": "^3.0.0" } }, "pathval": { @@ -7539,9 +6473,9 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "pinkie": { @@ -7586,21 +6520,6 @@ "tar-fs": "^1.13.0", "tunnel-agent": "^0.6.0", "which-pm-runs": "^1.0.0" - }, - "dependencies": { - "expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" - }, - "node-abi": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.7.1.tgz", - "integrity": "sha512-OV8Bq1OrPh6z+Y4dqwo05HqrRL9YNF7QVMRfq1/pguwKLG+q9UB/Lk0x5qXjO23JjJg+/jqCHSTaG1P3tfKfuw==", - "requires": { - "semver": "^5.4.1" - } - } } }, "prelude-ls": { @@ -7622,7 +6541,7 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=" + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" }, "process-nextick-args": { "version": "2.0.0", @@ -7647,7 +6566,7 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "requires": { "asap": "~2.0.3" } @@ -7660,21 +6579,6 @@ "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.8.1" - }, - "dependencies": { - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "react-is": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.1.tgz", - "integrity": "sha512-ioMCzVDWvCvKD8eeT+iukyWrBGrA3DiFYkXfBsVYIRdaREZuBjENG+KjrikavCLasozqRWTwFUagU/O4vPpRMA==" - } } }, "prr": { @@ -7697,22 +6601,21 @@ "pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha1-Ejma3W5M91Jtlzy8i1zi4pCLOQk=", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=", - "dev": true + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "raf": { "version": "3.4.1", @@ -7759,17 +6662,6 @@ "object-assign": "^4.1.1", "prop-types": "^15.6.2", "scheduler": "^0.13.3" - }, - "dependencies": { - "scheduler": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz", - "integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - } } }, "react-dom": { @@ -7781,48 +6673,20 @@ "object-assign": "^4.1.1", "prop-types": "^15.6.2", "scheduler": "^0.13.3" - }, - "dependencies": { - "scheduler": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz", - "integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - } } }, "react-input-autosize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.1.tgz", - "integrity": "sha1-7EKPoVsVkplPtfmqFbsetrr0IPg=", + "integrity": "sha512-3+K4CD13iE4lQQ2WlF8PuV5htfmTRLH6MDnfndHM6LuBRszuXnuyIfE7nhSKt8AzRBZ50bu0sAhkNMeS5pxQQA==", "requires": { "prop-types": "^15.5.8" - }, - "dependencies": { - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - } } }, "react-is": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.3.tgz", - "integrity": "sha512-u7FDWtthB4rWibG/+mFbVd5FvdI20yde86qKGx4lVUTWmPlSWQ4QxbBIrrs+HnXGbxOUlUzTAP/VDmvCwaP2yA==", - "dev": true + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.3.tgz", + "integrity": "sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA==" }, "react-relay": { "version": "3.0.0", @@ -7839,27 +6703,11 @@ "react-select": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/react-select/-/react-select-1.2.1.tgz", - "integrity": "sha1-ov5YpWnrFNyqZUOBYmC5flOBINE=", + "integrity": "sha512-vaCgT2bEl+uTyE/uKOEgzE5Dc/wLtzhnBvoHCeuLoJWc4WuadN6WQDhoL42DW+TziniZK2Gaqe/wUXydI3NSaQ==", "requires": { "classnames": "^2.2.4", "prop-types": "^15.5.8", "react-input-autosize": "^2.1.2" - }, - "dependencies": { - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - } } }, "react-tabs": { @@ -7872,62 +6720,50 @@ } }, "react-test-renderer": { - "version": "16.6.3", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.6.3.tgz", - "integrity": "sha512-B5bCer+qymrQz/wN03lT0LppbZUDRq6AMfzMKrovzkGzfO81a9T+PWQW6MzkWknbwODQH/qpJno/yFQLX5IWrQ==", + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.3.tgz", + "integrity": "sha512-rjJGYebduKNZH0k1bUivVrRLX04JfIQ0FKJLPK10TAb06XWhfi4gTobooF9K/DEFNW98iGac3OSxkfIJUN9Mdg==", "dev": true, "requires": { "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "react-is": "^16.6.3", - "scheduler": "^0.11.2" + "react-is": "^16.8.3", + "scheduler": "^0.13.3" } }, "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { - "load-json-file": "^2.0.0", + "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" + "path-type": "^3.0.0" } }, "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" } }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", + "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", + "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - } } }, "recast": { @@ -7965,6 +6801,17 @@ "requires": { "indent-string": "^2.1.0", "strip-indent": "^1.0.1" + }, + "dependencies": { + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + } } }, "regenerate": { @@ -7981,10 +6828,9 @@ } }, "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==", - "dev": true + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==" }, "regenerator-transform": { "version": "0.13.4", @@ -7997,21 +6843,11 @@ "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", - "requires": { - "is-plain-object": "^2.0.4" - } - } } }, "regexp-tree": { @@ -8082,33 +6918,270 @@ "yargs": "^9.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "dev": true, "requires": { - "color-convert": "^1.9.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { - "has-flag": "^3.0.0" + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", + "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" } } } @@ -8123,9 +7196,9 @@ } }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", "dev": true }, "repeat-string": { @@ -8144,48 +7217,30 @@ } }, "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", - "dev": true, + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" - }, - "dependencies": { - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha1-o0kgUKXLm2NFBUHjnZeI0icng9s=", - "dev": true - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha1-bzI/YKg9ERRvgx/xH9ZuL+VQO7g=", - "dev": true, - "requires": { - "mime-db": "~1.33.0" - } - } + "uuid": "^3.3.2" } }, "require-directory": { @@ -8201,9 +7256,9 @@ "dev": true }, "resolve": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", - "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "requires": { "path-parse": "^1.0.6" } @@ -8219,10 +7274,9 @@ } }, "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, "resolve-url": { "version": "0.2.1", @@ -8243,15 +7297,15 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" } }, "rst-selector-parser": { @@ -8283,9 +7337,9 @@ } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -8302,10 +7356,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "scheduler": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.3.tgz", - "integrity": "sha512-i9X9VRRVZDd3xZw10NY5Z2cVMbdYg6gqFecfj79USv1CFN+YrJ3gIPRKf1qlY+Sxly4djoKdfx1T+m9dnRB8kQ==", - "dev": true, + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz", + "integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -8324,7 +7377,7 @@ "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha1-ca5KiPD+77v1LR6mBPP7MV67YnQ=", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -8383,7 +7436,7 @@ "simple-get": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", - "integrity": "sha1-DiLpHUV12HYgYgvJEwjVenf0S10=", + "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", "requires": { "decompress-response": "^3.3.0", "once": "^1.3.1", @@ -8397,19 +7450,6 @@ "dev": true, "requires": { "string-width": "^1.0.1" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } } }, "sinon": { @@ -8427,14 +7467,11 @@ "supports-color": "^5.5.0" }, "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true } } }, @@ -8455,15 +7492,6 @@ "is-fullwidth-code-point": "^2.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -8488,6 +7516,15 @@ "use": "^3.1.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -8505,13 +7542,19 @@ "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, "snapdragon-node": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { "define-property": "^1.0.0", @@ -8562,7 +7605,7 @@ "snapdragon-util": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { "kind-of": "^3.2.0" @@ -8585,12 +7628,12 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", - "integrity": "sha1-etD1k/IoFZjoVN+A8ZquS5LXoRo=", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, "requires": { - "atob": "^2.0.0", + "atob": "^2.1.1", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", @@ -8604,9 +7647,9 @@ "dev": true }, "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -8614,9 +7657,9 @@ } }, "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", "dev": true }, "spdx-expression-parse": { @@ -8630,9 +7673,9 @@ } }, "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", + "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", "dev": true }, "speedometer": { @@ -8644,7 +7687,7 @@ "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha1-YFvZvjA6pZ+zX5Ip++oN3snqB9k=", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "requires": { "through": "2" } @@ -8652,20 +7695,10 @@ "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { "extend-shallow": "^3.0.0" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", - "requires": { - "is-plain-object": "^2.0.4" - } - } } }, "sprintf-js": { @@ -8674,9 +7707,9 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -8685,6 +7718,7 @@ "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" } }, @@ -8710,32 +7744,13 @@ } }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string.prototype.trim": { @@ -8750,9 +7765,9 @@ } }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } @@ -8798,13 +7813,32 @@ "dev": true, "requires": { "debug": "^2.2.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } }, "table": { "version": "5.2.3", @@ -8818,54 +7852,18 @@ "string-width": "^3.0.0" }, "dependencies": { - "ajv": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", - "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-regex": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", "dev": true }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, "string-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.0.0.tgz", @@ -8900,18 +7898,6 @@ "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", "yallist": "^3.0.2" - }, - "dependencies": { - "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } } }, "tar-fs": { @@ -8959,14 +7945,6 @@ "https-proxy-agent": "^2.2.1", "node-fetch": "^2.2.0", "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true - } } }, "temp": { @@ -8987,109 +7965,6 @@ "minimatch": "^3.0.4", "read-pkg-up": "^4.0.0", "require-main-filename": "^1.0.1" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", - "dev": true - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", - "dev": true, - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" - } - } } }, "test-until": { @@ -9125,6 +8000,12 @@ "xtend": "~2.1.1" }, "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, "object-keys": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", @@ -9133,7 +8014,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { @@ -9165,10 +8046,19 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=" + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" }, "to-fast-properties": { "version": "2.0.0", @@ -9198,23 +8088,13 @@ "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", "regex-not": "^1.0.2", "safe-regex": "^1.1.0" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", - "requires": { - "is-plain-object": "^2.0.4" - } - } } }, "to-regex-range": { @@ -9228,12 +8108,19 @@ } }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha1-7GDO44rGdQY//JelwYlwV47oNlU=", - "dev": true, + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } } }, "tree-kill": { @@ -9269,8 +8156,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-check": { "version": "0.3.2", @@ -9299,10 +8185,9 @@ "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==" }, "underscore": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", - "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=", - "dev": true + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" }, "underscore-plus": { "version": "1.6.8", @@ -9310,13 +8195,6 @@ "integrity": "sha512-88PrCeMKeAAC1L4xjSiiZ3Fg6kZOYrLpLGVPPeqKq/662DfQe/KTSKdSR/Q/tucKNnfW2MNAUGSCkDf8HmXC5Q==", "requires": { "underscore": "~1.8.3" - }, - "dependencies": { - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - } } }, "unicode-canonical-property-names-ecmascript": { @@ -9379,9 +8257,9 @@ } }, "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "unset-value": { "version": "1.0.0", @@ -9420,12 +8298,6 @@ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true } } }, @@ -9435,13 +8307,6 @@ "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "requires": { "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } } }, "urix": { @@ -9474,13 +8339,10 @@ "dev": true }, "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "util-deprecate": { "version": "1.0.2", @@ -9488,15 +8350,14 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", - "dev": true + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -9521,7 +8382,7 @@ "what-the-status": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/what-the-status/-/what-the-status-1.0.3.tgz", - "integrity": "sha1-lP3NAR/7U6Ijnnb6+NrL78mHdRA=", + "integrity": "sha512-6zNdYtQtHTpLVPomSrr+Eyt5Ci4H40ytwScwp7Moi2iqxztV6+juQV9Orj2szAo0ZrV9tphk6WtL+BY3ukCS/Q==", "requires": { "split": "^1.0.0" } @@ -9532,9 +8393,9 @@ "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" }, "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { "isexe": "^2.0.0" @@ -9572,19 +8433,6 @@ "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } } }, "wrappy": { @@ -9613,9 +8461,9 @@ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, "yallist": { @@ -9624,44 +8472,23 @@ "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" }, "yargs": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", - "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "dev": true, "requires": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", + "os-locale": "^3.0.0", "require-directory": "^2.1.1", "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", "string-width": "^2.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" - } - }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - }, - "yargs-unparser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", - "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", - "dev": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.11", - "yargs": "^12.0.5" + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" }, "dependencies": { "ansi-regex": { @@ -9670,148 +8497,20 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", - "dev": true - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "mem": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", - "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", - "p-is-promise": "^2.0.0" - } - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { + "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } }, "strip-ansi": { @@ -9822,39 +8521,30 @@ "requires": { "ansi-regex": "^3.0.0" } - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + } + }, "yauzl": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", From 2c261c8ba03b37f8cd9f67b48a0ec9d1c0dae96f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 27 Feb 2019 13:41:48 -0500 Subject: [PATCH 2145/4053] Cover the last uncovered line in MultiFilePatchView --- test/views/multi-file-patch-view.test.js | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/views/multi-file-patch-view.test.js b/test/views/multi-file-patch-view.test.js index 8af61c38ff..eaabc059d6 100644 --- a/test/views/multi-file-patch-view.test.js +++ b/test/views/multi-file-patch-view.test.js @@ -236,6 +236,36 @@ describe('MultiFilePatchView', function() { assert.isFalse(wrapper.find('FilePatchHeaderView[relPath="1"]').prop('hasMultipleFileSelections')); }); + it('triggers a FilePatch collapse from file headers', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => fp.setOldFile(f => f.path('0'))) + .addFilePatch(fp => fp.setOldFile(f => f.path('1'))) + .build(); + const fp1 = multiFilePatch.getFilePatches()[1]; + + const wrapper = shallow(buildApp({multiFilePatch})); + + sinon.stub(multiFilePatch, 'collapseFilePatch'); + + wrapper.find('FilePatchHeaderView[relPath="1"]').prop('triggerCollapse')(); + assert.isTrue(multiFilePatch.collapseFilePatch.calledWith(fp1)); + }); + + it('triggers a FilePatch expansion from file headers', function() { + const {multiFilePatch} = multiFilePatchBuilder() + .addFilePatch(fp => fp.setOldFile(f => f.path('0'))) + .addFilePatch(fp => fp.setOldFile(f => f.path('1'))) + .build(); + const fp0 = multiFilePatch.getFilePatches()[0]; + + const wrapper = shallow(buildApp({multiFilePatch})); + + sinon.stub(multiFilePatch, 'expandFilePatch'); + + wrapper.find('FilePatchHeaderView[relPath="0"]').prop('triggerExpand')(); + assert.isTrue(multiFilePatch.expandFilePatch.calledWith(fp0)); + }); + it('renders a PullRequestsReviewsContainer if itemType is IssueishDetailItem', function() { const wrapper = shallow(buildApp({itemType: IssueishDetailItem})); assert.lengthOf(wrapper.find('ForwardRef(Relay(PullRequestReviewsController))'), 1); From 776a440207ccc18b1595980ea7ed690a6859f27f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 27 Feb 2019 14:04:49 -0500 Subject: [PATCH 2146/4053] Manually test cache invalidation from FOCUS on Linux --- test/models/repository.test.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/models/repository.test.js b/test/models/repository.test.js index a0e68c9eb1..6f9ce245b8 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -11,6 +11,7 @@ import {LargeRepoError} from '../../lib/git-shell-out-strategy'; import {nullCommit} from '../../lib/models/commit'; import {nullOperationStates} from '../../lib/models/operation-states'; import Author from '../../lib/models/author'; +import {FOCUS} from '../../lib/models/workspace-change-observer'; import * as reporterProxy from '../../lib/reporter-proxy'; import { @@ -2306,6 +2307,37 @@ describe('Repository', function() { }); }); }); + + it('manually invalidates some keys when the WorkspaceChangeObserver indicates the window is focused', async function() { + const workdir = await cloneRepository('three-files'); + const repository = new Repository(workdir); + await repository.getLoadPromise(); + + const readerMethods = await getCacheReaderMethods({repository}); + function readerValues() { + return new Map( + Array.from(readerMethods.entries(), method => { + return [method[0], method[1]()]; + }), + ); + } + + const before = readerValues(); + repository.observeFilesystemChange([{special: FOCUS}]); + const after = readerValues(); + + const invalidated = Array.from(readerMethods.keys()).filter(key => before.get(key) !== after.get(key)); + + assert.sameMembers(invalidated, [ + 'getStatusBundle', + 'getFilePatchForPath {unstaged} a.txt', + 'getFilePatchForPath {unstaged} b.txt', + 'getFilePatchForPath {unstaged} c.txt', + 'getFilePatchForPath {unstaged} subdir-1/a.txt', + 'getFilePatchForPath {unstaged} subdir-1/b.txt', + 'getFilePatchForPath {unstaged} subdir-1/c.txt', + ]); + }); }); describe('commit message', function() { From 97720b03ccf3cf82671dfff7e0b179f59ded3bc0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 27 Feb 2019 14:06:18 -0500 Subject: [PATCH 2147/4053] That .catch on win32 is probably important? --- test/models/repository.test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 6f9ce245b8..7cd9b768b8 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -2316,8 +2316,12 @@ describe('Repository', function() { const readerMethods = await getCacheReaderMethods({repository}); function readerValues() { return new Map( - Array.from(readerMethods.entries(), method => { - return [method[0], method[1]()]; + Array.from(readerMethods.entries(), ([name, call]) => { + const promise = call(); + if (process.platform === 'win32') { + promise.catch(() => {}); + } + return [name, promise]; }), ); } From 893489b6c6620df8782acf81c54fbab2681cf4d2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 27 Feb 2019 14:46:30 -0500 Subject: [PATCH 2148/4053] PatchBuffer :100: --- lib/models/patch/patch-buffer.js | 5 +- test/models/patch/patch-buffer.test.js | 86 ++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/lib/models/patch/patch-buffer.js b/lib/models/patch/patch-buffer.js index 44aa501989..e93a48f865 100644 --- a/lib/models/patch/patch-buffer.js +++ b/lib/models/patch/patch-buffer.js @@ -118,6 +118,7 @@ export default class PatchBuffer { return markerMap; } + /* istanbul ignore next */ inspect(opts = {}) { /* istanbul ignore next */ const options = { @@ -237,9 +238,7 @@ class Inserter { } } - if (opts.callback) { - this.markerMapCallbacks.push({markerMap: subMarkerMap, callback: opts.callback}); - } + this.markerMapCallbacks.push({markerMap: subMarkerMap, callback: opts.callback}); return this; } diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index 29bfc2379f..8289c2c55a 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -13,14 +13,16 @@ describe('PatchBuffer', function() { it('has simple accessors', function() { assert.strictEqual(patchBuffer.getBuffer().getText(), TEXT); assert.deepEqual(patchBuffer.getInsertionPoint().serialize(), [10, 0]); + assert.isDefined(patchBuffer.getLayer('patch')); }); it('creates and finds markers on specified layers', function() { const patchMarker = patchBuffer.markRange('patch', [[1, 0], [2, 4]]); const hunkMarker = patchBuffer.markRange('hunk', [[2, 0], [3, 4]]); - assert.deepEqual(patchBuffer.findMarkers('patch', {}), [patchMarker]); - assert.deepEqual(patchBuffer.findMarkers('hunk', {}), [hunkMarker]); + assert.sameDeepMembers(patchBuffer.findMarkers('patch', {}), [patchMarker]); + assert.sameDeepMembers(patchBuffer.findMarkers('hunk', {}), [hunkMarker]); + assert.sameDeepMembers(patchBuffer.findAllMarkers({}), [patchMarker, hunkMarker]); }); it('clears markers from all layers at once', function() { @@ -179,6 +181,67 @@ describe('PatchBuffer', function() { }); }); + describe('adopt', function() { + it('destroys existing content and markers and replaces them with those from the argument', function() { + const original = new PatchBuffer(); + original.getBuffer().setText(dedent` + before 0 + before 1 + before 2 + `); + + const originalMarkers = [ + original.markRange('patch', [[0, 0], [2, 3]]), + original.markRange('patch', [[1, 3], [2, 0]]), + original.markRange('hunk', [[0, 0], [0, 7]]), + ]; + + const adoptee = new PatchBuffer(); + adoptee.getBuffer().setText(dedent` + after 0 + after 1 + after 2 + after 3 + `); + + const adopteeMarkers = [ + adoptee.markRange('addition', [[2, 0], [3, 7]]), + adoptee.markRange('patch', [[1, 0], [2, 0]]), + ]; + + const map = original.adopt(adoptee); + + assert.strictEqual(original.getBuffer().getText(), dedent` + after 0 + after 1 + after 2 + after 3 + `); + + assert.sameDeepMembers( + original.findMarkers('patch', {}).map(m => m.getRange().serialize()), + [[[1, 0], [2, 0]]], + ); + assert.sameDeepMembers( + original.findMarkers('addition', {}).map(m => m.getRange().serialize()), + [[[2, 0], [3, 7]]], + ); + assert.lengthOf(original.findMarkers('hunk', {}), 0); + + for (const originalMarker of originalMarkers) { + assert.isFalse(map.has(originalMarker)); + assert.isTrue(originalMarker.isDestroyed()); + } + + for (const adopteeMarker of adopteeMarkers) { + assert.isTrue(map.has(adopteeMarker)); + assert.isFalse(adopteeMarker.isDestroyed()); + assert.isFalse(map.get(adopteeMarker).isDestroyed()); + assert.deepEqual(adopteeMarker.getRange().serialize(), map.get(adopteeMarker).getRange().serialize()); + } + }); + }); + describe('deferred-marking modifications', function() { it('performs multiple modifications and only creates markers at the end', function() { const inserter = patchBuffer.createInserterAtEnd(); @@ -263,13 +326,20 @@ describe('PatchBuffer', function() { }); it('preserves markers that should be before or after the modification region', function() { + const invalidBefore = patchBuffer.markRange('patch', [[1, 0], [1, 0]]); const before0 = patchBuffer.markRange('patch', [[1, 0], [4, 0]], {exclusive: true}); const before1 = patchBuffer.markRange('hunk', [[4, 0], [4, 0]], {exclusive: true}); + const before2 = patchBuffer.markRange('addition', [[3, 0], [4, 0]], {exclusive: true, reversed: true}); + const before3 = patchBuffer.markRange('nonewline', [[4, 0], [4, 0]], {exclusive: true, reversed: true}); const after0 = patchBuffer.markPosition('patch', [4, 0], {exclusive: true}); + const after1 = patchBuffer.markRange('patch', [[4, 0], [4, 3]], {exclusive: true}); + const after2 = patchBuffer.markRange('deletion', [[4, 0], [5, 0]], {exclusive: true, reversed: true}); + const after3 = patchBuffer.markRange('nonewline', [[4, 0], [4, 0]], {exclusive: true, reversed: true}); + const invalidAfter = patchBuffer.markRange('hunk', [[5, 0], [6, 0]]); const inserter = patchBuffer.createInserterAt([4, 0]); - inserter.keepBefore([before0, before1]); - inserter.keepAfter([after0]); + inserter.keepBefore([before0, before1, before2, before3, invalidBefore]); + inserter.keepAfter([after0, after1, after2, after3, invalidAfter]); let marker = null; const callback = m => { marker = m; }; @@ -279,8 +349,16 @@ describe('PatchBuffer', function() { assert.deepEqual(before0.getRange().serialize(), [[1, 0], [4, 0]]); assert.deepEqual(before1.getRange().serialize(), [[4, 0], [4, 0]]); + assert.deepEqual(before2.getRange().serialize(), [[3, 0], [4, 0]]); + assert.deepEqual(before3.getRange().serialize(), [[4, 0], [4, 0]]); assert.deepEqual(marker.getRange().serialize(), [[4, 0], [9, 0]]); assert.deepEqual(after0.getRange().serialize(), [[9, 0], [9, 0]]); + assert.deepEqual(after1.getRange().serialize(), [[9, 0], [9, 3]]); + assert.deepEqual(after2.getRange().serialize(), [[9, 0], [10, 0]]); + assert.deepEqual(after3.getRange().serialize(), [[9, 0], [9, 0]]); + + assert.deepEqual(invalidBefore.getRange().serialize(), [[1, 0], [1, 0]]); + assert.deepEqual(invalidAfter.getRange().serialize(), [[10, 0], [11, 0]]); }); it('appends another PatchBuffer at its insertion point', function() { From d58ca26d2785723e595f5ea269e39e7e171cfd1b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 27 Feb 2019 14:54:15 -0500 Subject: [PATCH 2149/4053] AtomTextEditor :100: --- lib/atom/atom-text-editor.js | 2 +- test/atom/atom-text-editor.test.js | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index 3e4425c0d3..fb4e52e7db 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -97,7 +97,7 @@ export default class AtomTextEditor extends React.Component { }); } - componentDidUpdate(prevProps) { + componentDidUpdate() { const modelProps = extractProps(this.props, editorUpdateProps); this.getRefModel().map(editor => editor.update(modelProps)); this.observeEmptiness(); diff --git a/test/atom/atom-text-editor.test.js b/test/atom/atom-text-editor.test.js index 194cd5d9b0..ce2fc700cd 100644 --- a/test/atom/atom-text-editor.test.js +++ b/test/atom/atom-text-editor.test.js @@ -130,6 +130,31 @@ describe('AtomTextEditor', function() { }); }); + it('defaults to no-op handlers', function() { + mount( + , + ); + + const editor = refModel.get(); + + // Trigger didChangeCursorPosition + editor.setCursorBufferPosition([2, 3]); + + // Trigger didAddSelection + editor.addSelectionForBufferRange([[1, 0], [3, 3]]); + + // Trigger didChangeSelectionRange + const [selection] = editor.getSelections(); + selection.setBufferRange([[2, 2], [2, 3]]); + + // Trigger didDestroySelection + editor.setSelectedBufferRange([[1, 0], [1, 2]]); + }); + it('triggers didChangeCursorPosition when the cursor position changes', function() { mount( Date: Wed, 27 Feb 2019 15:35:54 -0500 Subject: [PATCH 2150/4053] State :100: --- lib/models/repository-states/present.js | 8 ++++++++ lib/models/repository-states/state.js | 9 ++++++++- test/models/repository.test.js | 26 +++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 42d77844ce..233477e19c 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -875,6 +875,10 @@ export default class Present extends State { }); } + directGetConfig(key, options) { + return this.getConfig(key, options); + } + // Direct blob access getBlobContents(sha) { @@ -883,6 +887,10 @@ export default class Present extends State { }); } + directGetBlobContents(sha) { + return this.getBlobContents(sha); + } + // Discard history hasDiscardHistory(partialDiscardFilePath = null) { diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index d76548c6fb..2053cc4cfb 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -104,6 +104,7 @@ export default class State { return this.transitionTo('Destroyed'); } + /* istanbul ignore next */ refresh() { // No-op } @@ -112,9 +113,9 @@ export default class State { this.repository.refresh(); } + /* istanbul ignore next */ updateCommitMessageAfterFileSystemChange() { // this is only used in unit tests, we don't need no stinkin coverage - /* istanbul ignore next */ this.repository.refresh(); } @@ -440,6 +441,7 @@ export default class State { // Initiate a transition to another state. transitionTo(stateName, ...payload) { const StateConstructor = stateConstructors.get(stateName); + /* istanbul ignore if */ if (StateConstructor === undefined) { throw new Error(`Attempt to transition to unrecognized state ${stateName}`); } @@ -459,22 +461,27 @@ export default class State { // Direct git access // Non-delegated git operations for internal use within states. + /* istanbul ignore next */ directResolveDotGitDir() { return Promise.resolve(null); } + /* istanbul ignore next */ directGetConfig(key, options = {}) { return Promise.resolve(null); } + /* istanbul ignore next */ directGetBlobContents() { return Promise.reject(new Error('Not a valid object name')); } + /* istanbul ignore next */ directInit() { return Promise.resolve(); } + /* istanbul ignore next */ directClone(remoteUrl, options) { return Promise.resolve(); } diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 7cd9b768b8..7ea6c8a8f6 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -124,6 +124,7 @@ describe('Repository', function() { 'push', 'setConfig', 'unsetConfig', 'createBlob', 'expandBlobToFile', 'createDiscardHistoryBlob', 'updateDiscardHistory', 'storeBeforeAndAfterBlobs', 'restoreLastDiscardInTempFiles', 'popDiscardHistory', 'clearDiscardHistory', 'discardWorkDirChangesForPaths', 'addRemote', 'setCommitMessage', + 'fetchCommitMessageTemplate', ]) { await assert.isRejected(repository[method](), new RegExp(`${method} is not available in Destroyed state`)); } @@ -1603,6 +1604,31 @@ describe('Repository', function() { const repo2 = new Repository(workingDirPath); await repo2.getLoadPromise(); }); + + it('passes unexpected git errors to the caller', async function() { + const workingDirPath = await cloneRepository('three-files'); + const repo = new Repository(workingDirPath); + await repo.getLoadPromise(); + await repo.setConfig('atomGithub.historySha', '1111111111111111111111111111111111111111'); + + repo.refresh(); + sinon.stub(repo.git, 'getBlobContents').rejects(new Error('oh no')); + + await assert.isRejected(repo.updateDiscardHistory(), /oh no/); + }); + + it('is resilient to malformed history blobs', async function() { + const workingDirPath = await cloneRepository('three-files'); + const repo = new Repository(workingDirPath); + await repo.getLoadPromise(); + await repo.setConfig('atomGithub.historySha', '1111111111111111111111111111111111111111'); + + repo.refresh(); + sinon.stub(repo.git, 'getBlobContents').resolves('lol not JSON'); + + // Should not throw + await repo.updateDiscardHistory(); + }); }); describe('cache invalidation', function() { From 0d1860157eca46d04d7267e992078e8997c6580a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 27 Feb 2019 16:04:37 -0500 Subject: [PATCH 2151/4053] Maybe it's the path separators? --- test/models/repository.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 7ea6c8a8f6..28988ed9ea 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -2363,9 +2363,9 @@ describe('Repository', function() { 'getFilePatchForPath {unstaged} a.txt', 'getFilePatchForPath {unstaged} b.txt', 'getFilePatchForPath {unstaged} c.txt', - 'getFilePatchForPath {unstaged} subdir-1/a.txt', - 'getFilePatchForPath {unstaged} subdir-1/b.txt', - 'getFilePatchForPath {unstaged} subdir-1/c.txt', + `getFilePatchForPath {unstaged} ${path.join('subdir-1/a.txt')}`, + `getFilePatchForPath {unstaged} ${path.join('subdir-1/b.txt')}`, + `getFilePatchForPath {unstaged} ${path.join('subdir-1/c.txt')}`, ]); }); }); From 33c88af095fe7e6ff4432fbaa72e353d3e157b7c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 28 Feb 2019 07:50:26 -0500 Subject: [PATCH 2152/4053] Actually :100: State --- lib/models/repository-states/state.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 2053cc4cfb..b364d85dde 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -109,6 +109,7 @@ export default class State { // No-op } + /* istanbul ignore next */ observeFilesystemChange(events) { this.repository.refresh(); } From 03b47156ac05d0b4966f3845e2b8fdf96a13da6c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 28 Feb 2019 16:25:47 +0000 Subject: [PATCH 2153/4053] fix(package): update dugite to version 1.85.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5cca104dbc..d9ab506843 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "bytes": "3.1.0", "classnames": "2.2.6", "compare-sets": "1.0.1", - "dugite": "1.84.0", + "dugite": "1.85.0", "event-kit": "2.5.3", "fs-extra": "4.0.3", "graphql": "14.1.1", From 52ba1b5559cd40e8888e1e15c287e7ce74cb0143 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 28 Feb 2019 16:25:51 +0000 Subject: [PATCH 2154/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b0dc9c9763..3a33c7b5a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2245,9 +2245,9 @@ } }, "dugite": { - "version": "1.84.0", - "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.84.0.tgz", - "integrity": "sha512-TKBfeQAq4f4bcrPRtb9k1LnI/dssl8K85A5LxnJIaKdhGUbOtmcJacSzNSiokz0OFbSOEIYxtgZMRnkVONIURg==", + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/dugite/-/dugite-1.85.0.tgz", + "integrity": "sha512-33YIKzzuSIoB8cRDrIPozvvIJiPs4+XQJGcb9g48O99Q0GkPb1ipy3YjknJTTU0TwTB+NyGjA6m1jBRoR3XvuQ==", "requires": { "checksum": "^0.1.1", "mkdirp": "^0.5.1", From a316c1b6534b94b8531d890711fb000a79f773e1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 28 Feb 2019 14:51:31 -0500 Subject: [PATCH 2155/4053] Our daily Nietzsche --- lib/atom/atom-text-editor.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/atom/atom-text-editor.js b/lib/atom/atom-text-editor.js index fb4e52e7db..aedfe29971 100644 --- a/lib/atom/atom-text-editor.js +++ b/lib/atom/atom-text-editor.js @@ -100,6 +100,8 @@ export default class AtomTextEditor extends React.Component { componentDidUpdate() { const modelProps = extractProps(this.props, editorUpdateProps); this.getRefModel().map(editor => editor.update(modelProps)); + + // When you look into the abyss, the abyss also looks into you this.observeEmptiness(); } From ceda6ef135f43a9d1c6478f723a0ff788f4757da Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 28 Feb 2019 14:52:43 -0500 Subject: [PATCH 2156/4053] :fire: obsolete TODO --- lib/models/patch/builder.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/models/patch/builder.js b/lib/models/patch/builder.js index aecd09aca0..b328a4da35 100644 --- a/lib/models/patch/builder.js +++ b/lib/models/patch/builder.js @@ -8,7 +8,6 @@ import MultiFilePatch from './multi-file-patch'; export const DEFAULT_OPTIONS = { // Number of lines after which we consider the diff "large" - // TODO: Set this based on performance measurements largeDiffThreshold: 800, // Map of file path (relative to repository root) to Patch render status (EXPANDED, COLLAPSED, TOO_LARGE) From 355ade8868f93054b442deca748a025b7b5a4659 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 28 Feb 2019 14:54:11 -0500 Subject: [PATCH 2157/4053] Rename getLayeredBuffer() to getPatchBuffer() --- lib/models/patch/multi-file-patch.js | 18 +++++++++--------- lib/views/multi-file-patch-view.js | 4 ++-- test/models/patch/multi-file-patch.test.js | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/models/patch/multi-file-patch.js b/lib/models/patch/multi-file-patch.js index 47e7e635e0..8cf13824a4 100644 --- a/lib/models/patch/multi-file-patch.js +++ b/lib/models/patch/multi-file-patch.js @@ -44,41 +44,41 @@ export default class MultiFilePatch { clone(opts = {}) { return new this.constructor({ - patchBuffer: opts.patchBuffer !== undefined ? opts.patchBuffer : this.getLayeredBuffer(), + patchBuffer: opts.patchBuffer !== undefined ? opts.patchBuffer : this.getPatchBuffer(), filePatches: opts.filePatches !== undefined ? opts.filePatches : this.getFilePatches(), }); } - getLayeredBuffer() { + getPatchBuffer() { return this.patchBuffer; } getBuffer() { - return this.getLayeredBuffer().getBuffer(); + return this.getPatchBuffer().getBuffer(); } getPatchLayer() { - return this.getLayeredBuffer().getLayer('patch'); + return this.getPatchBuffer().getLayer('patch'); } getHunkLayer() { - return this.getLayeredBuffer().getLayer('hunk'); + return this.getPatchBuffer().getLayer('hunk'); } getUnchangedLayer() { - return this.getLayeredBuffer().getLayer('unchanged'); + return this.getPatchBuffer().getLayer('unchanged'); } getAdditionLayer() { - return this.getLayeredBuffer().getLayer('addition'); + return this.getPatchBuffer().getLayer('addition'); } getDeletionLayer() { - return this.getLayeredBuffer().getLayer('deletion'); + return this.getPatchBuffer().getLayer('deletion'); } getNoNewlineLayer() { - return this.getLayeredBuffer().getLayer('nonewline'); + return this.getPatchBuffer().getLayer('nonewline'); } getFilePatches() { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index c86aad5906..ebbbc9f7fd 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -269,7 +269,7 @@ export default class MultiFilePatchView extends React.Component { {/* istanbul ignore next */ atom.inDevMode() && { // eslint-disable-next-line no-console - console.log(this.props.multiFilePatch.getLayeredBuffer().inspect({ + console.log(this.props.multiFilePatch.getPatchBuffer().inspect({ layerNames: ['patch', 'hunk'], })); }} @@ -278,7 +278,7 @@ export default class MultiFilePatchView extends React.Component { {/* istanbul ignore next */ atom.inDevMode() && { // eslint-disable-next-line no-console - console.log(this.props.multiFilePatch.getLayeredBuffer().inspect({ + console.log(this.props.multiFilePatch.getPatchBuffer().inspect({ layerNames: ['unchanged', 'deletion', 'addition', 'nonewline'], })); }} diff --git a/test/models/patch/multi-file-patch.test.js b/test/models/patch/multi-file-patch.test.js index 580da3ddd5..dc61a7dccd 100644 --- a/test/models/patch/multi-file-patch.test.js +++ b/test/models/patch/multi-file-patch.test.js @@ -53,7 +53,7 @@ describe('MultiFilePatch', function() { it('creates a copy with a new PatchBuffer', function() { const {multiFilePatch} = multiFilePatchBuilder().build(); - const dup = original.clone({patchBuffer: multiFilePatch.getLayeredBuffer()}); + const dup = original.clone({patchBuffer: multiFilePatch.getPatchBuffer()}); assert.strictEqual(dup.getBuffer(), multiFilePatch.getBuffer()); assert.strictEqual(dup.getPatchLayer(), multiFilePatch.getPatchLayer()); From fd990ed563a92591bfacc3414807a7c93212a6ee Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 28 Feb 2019 14:56:12 -0500 Subject: [PATCH 2158/4053] Use defaultProps for on(Will|Did)UpdatePatch handlers --- lib/views/multi-file-patch-view.js | 89 +++++++++++++++--------------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index ebbbc9f7fd..76fa7e199f 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -2,7 +2,7 @@ import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import {Range} from 'atom'; -import {CompositeDisposable} from 'event-kit'; +import {CompositeDisposable, Disposable} from 'event-kit'; import {autobind} from '../helpers'; import {addEvent} from '../reporter-proxy'; @@ -71,6 +71,11 @@ export default class MultiFilePatchView extends React.Component { itemType: ItemTypePropType.isRequired, } + static defaultProps = { + onWillUpdatePatch: () => new Disposable(), + onDidUpdatePatch: () => new Disposable(), + } + constructor(props) { super(props); autobind( @@ -108,53 +113,51 @@ export default class MultiFilePatchView extends React.Component { let lastScrollTop = null; let lastScrollLeft = null; let lastSelectionIndex = null; - if (this.props.onWillUpdatePatch && this.props.onDidUpdatePatch) { - this.subs.add( - this.props.onWillUpdatePatch(() => { - this.suppressChanges = true; - this.refEditor.map(editor => { - lastSelectionIndex = this.props.multiFilePatch.getMaxSelectionIndex(this.props.selectedRows); - lastScrollTop = editor.getElement().getScrollTop(); - lastScrollLeft = editor.getElement().getScrollLeft(); - return null; - }); - }), - this.props.onDidUpdatePatch(nextPatch => { - this.refEditor.map(editor => { - /* istanbul ignore else */ - if (lastSelectionIndex !== null) { - const nextSelectionRange = nextPatch.getSelectionRangeForIndex(lastSelectionIndex); - if (this.props.selectionMode === 'line') { - this.nextSelectionMode = 'line'; - editor.setSelectedBufferRange(nextSelectionRange); - } else { - const nextHunks = new Set( - Range.fromObject(nextSelectionRange).getRows() - .map(row => nextPatch.getHunkAt(row)) - .filter(Boolean), - ); + this.subs.add( + this.props.onWillUpdatePatch(() => { + this.suppressChanges = true; + this.refEditor.map(editor => { + lastSelectionIndex = this.props.multiFilePatch.getMaxSelectionIndex(this.props.selectedRows); + lastScrollTop = editor.getElement().getScrollTop(); + lastScrollLeft = editor.getElement().getScrollLeft(); + return null; + }); + }), + this.props.onDidUpdatePatch(nextPatch => { + this.refEditor.map(editor => { + /* istanbul ignore else */ + if (lastSelectionIndex !== null) { + const nextSelectionRange = nextPatch.getSelectionRangeForIndex(lastSelectionIndex); + if (this.props.selectionMode === 'line') { + this.nextSelectionMode = 'line'; + editor.setSelectedBufferRange(nextSelectionRange); + } else { + const nextHunks = new Set( + Range.fromObject(nextSelectionRange).getRows() + .map(row => nextPatch.getHunkAt(row)) + .filter(Boolean), + ); /* istanbul ignore next */ - const nextRanges = nextHunks.size > 0 - ? Array.from(nextHunks, hunk => hunk.getRange()) - : [[[0, 0], [0, 0]]]; + const nextRanges = nextHunks.size > 0 + ? Array.from(nextHunks, hunk => hunk.getRange()) + : [[[0, 0], [0, 0]]]; - this.nextSelectionMode = 'hunk'; - editor.setSelectedBufferRanges(nextRanges); - } + this.nextSelectionMode = 'hunk'; + editor.setSelectedBufferRanges(nextRanges); } + } - /* istanbul ignore else */ - if (lastScrollTop !== null) { editor.getElement().setScrollTop(lastScrollTop); } + /* istanbul ignore else */ + if (lastScrollTop !== null) { editor.getElement().setScrollTop(lastScrollTop); } - /* istanbul ignore else */ - if (lastScrollLeft !== null) { editor.getElement().setScrollLeft(lastScrollLeft); } - return null; - }); - this.suppressChanges = false; - this.didChangeSelectedRows(); - }), - ); - } + /* istanbul ignore else */ + if (lastScrollLeft !== null) { editor.getElement().setScrollLeft(lastScrollLeft); } + return null; + }); + this.suppressChanges = false; + this.didChangeSelectedRows(); + }), + ); } componentDidMount() { From 2ea5184e797f2a21c6060ba56771d2cc0802924c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 28 Feb 2019 14:57:09 -0500 Subject: [PATCH 2159/4053] :fire: unused propTypes --- lib/views/pr-review-comments-view.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comments-view.js index dfdefff8df..636b88d4b1 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comments-view.js @@ -11,13 +11,7 @@ import GithubDotcomMarkdown from './github-dotcom-markdown'; import Timeago from './timeago'; export default class PullRequestCommentsView extends React.Component { - // do we even need the relay props here? does not look like they are used static propTypes = { - relay: PropTypes.shape({ - hasMore: PropTypes.func.isRequired, - loadMore: PropTypes.func.isRequired, - isLoading: PropTypes.func.isRequired, - }).isRequired, commentThreads: PropTypes.arrayOf(PropTypes.shape({ rootCommentId: PropTypes.string.isRequired, comments: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -63,7 +57,6 @@ export default class PullRequestCommentsView extends React.Component { } export class PullRequestCommentView extends React.Component { - static propTypes = { switchToIssueish: PropTypes.func.isRequired, comment: PropTypes.shape({ From 6b4c770f040536390786ea29017bfc3a94ba5ab3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 28 Feb 2019 14:57:51 -0500 Subject: [PATCH 2160/4053] Touch up builder method spelling --- test/builder/pr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/builder/pr.js b/test/builder/pr.js index 52c72a95dc..4b2a0d3613 100644 --- a/test/builder/pr.js +++ b/test/builder/pr.js @@ -17,7 +17,7 @@ class CommentBuilder { return this; } - minmized(m) { + minimized(m) { this._isMinimized = m; return this; } From 3b65cfe3ab367b869f7f5b92fb32a3823b35c776 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 28 Feb 2019 15:01:43 -0500 Subject: [PATCH 2161/4053] Rename ...LayeredBuffer variables to ...PatchBuffer --- test/models/patch/file-patch.test.js | 54 +++++++++++----------- test/models/patch/patch-buffer.test.js | 2 +- test/models/patch/patch.test.js | 62 +++++++++++++------------- 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/test/models/patch/file-patch.test.js b/test/models/patch/file-patch.test.js index 4ee102cd27..aa7e8eed52 100644 --- a/test/models/patch/file-patch.test.js +++ b/test/models/patch/file-patch.test.js @@ -180,10 +180,10 @@ describe('FilePatch', function() { }); describe('buildStagePatchForLines()', function() { - let stagedLayeredBuffer; + let stagedPatchBuffer; beforeEach(function() { - stagedLayeredBuffer = new PatchBuffer(); + stagedPatchBuffer = new PatchBuffer(); }); it('returns a new FilePatch that applies only the selected lines', function() { @@ -207,12 +207,12 @@ describe('FilePatch', function() { const newFile = new File({path: 'file.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); - const stagedPatch = filePatch.buildStagePatchForLines(buffer, stagedLayeredBuffer, new Set([1, 3])); + const stagedPatch = filePatch.buildStagePatchForLines(buffer, stagedPatchBuffer, new Set([1, 3])); assert.strictEqual(stagedPatch.getStatus(), 'modified'); assert.strictEqual(stagedPatch.getOldFile(), oldFile); assert.strictEqual(stagedPatch.getNewFile(), newFile); - assert.strictEqual(stagedLayeredBuffer.buffer.getText(), '0000\n0001\n0003\n0004\n'); - assertInFilePatch(stagedPatch, stagedLayeredBuffer.buffer).hunks( + assert.strictEqual(stagedPatchBuffer.buffer.getText(), '0000\n0001\n0003\n0004\n'); + assertInFilePatch(stagedPatch, stagedPatchBuffer.buffer).hunks( { startRow: 0, endRow: 3, @@ -250,13 +250,13 @@ describe('FilePatch', function() { }); it('handles staging part of the file', function() { - const stagedPatch = deletionPatch.buildStagePatchForLines(buffer, stagedLayeredBuffer, new Set([1, 2])); + const stagedPatch = deletionPatch.buildStagePatchForLines(buffer, stagedPatchBuffer, new Set([1, 2])); assert.strictEqual(stagedPatch.getStatus(), 'modified'); assert.strictEqual(stagedPatch.getOldFile(), oldFile); assert.strictEqual(stagedPatch.getNewFile(), oldFile); - assert.strictEqual(stagedLayeredBuffer.buffer.getText(), '0000\n0001\n0002\n'); - assertInFilePatch(stagedPatch, stagedLayeredBuffer.buffer).hunks( + assert.strictEqual(stagedPatchBuffer.buffer.getText(), '0000\n0001\n0002\n'); + assertInFilePatch(stagedPatch, stagedPatchBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -270,12 +270,12 @@ describe('FilePatch', function() { }); it('handles staging all lines, leaving nothing unstaged', function() { - const stagedPatch = deletionPatch.buildStagePatchForLines(buffer, stagedLayeredBuffer, new Set([0, 1, 2])); + const stagedPatch = deletionPatch.buildStagePatchForLines(buffer, stagedPatchBuffer, new Set([0, 1, 2])); assert.strictEqual(stagedPatch.getStatus(), 'deleted'); assert.strictEqual(stagedPatch.getOldFile(), oldFile); assert.isFalse(stagedPatch.getNewFile().isPresent()); - assert.strictEqual(stagedLayeredBuffer.buffer.getText(), '0000\n0001\n0002\n'); - assertInFilePatch(stagedPatch, stagedLayeredBuffer.buffer).hunks( + assert.strictEqual(stagedPatchBuffer.buffer.getText(), '0000\n0001\n0002\n'); + assertInFilePatch(stagedPatch, stagedPatchBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -305,7 +305,7 @@ describe('FilePatch', function() { const newFile = new File({path: 'file.txt', mode: '120000'}); const replacePatch = new FilePatch(oldFile, newFile, patch); - const stagedPatch = replacePatch.buildStagePatchForLines(nBuffer, stagedLayeredBuffer, new Set([0, 1, 2])); + const stagedPatch = replacePatch.buildStagePatchForLines(nBuffer, stagedPatchBuffer, new Set([0, 1, 2])); assert.strictEqual(stagedPatch.getOldFile(), oldFile); assert.isFalse(stagedPatch.getNewFile().isPresent()); }); @@ -313,10 +313,10 @@ describe('FilePatch', function() { }); describe('getUnstagePatchForLines()', function() { - let unstageLayeredBuffer; + let unstagePatchBuffer; beforeEach(function() { - unstageLayeredBuffer = new PatchBuffer(); + unstagePatchBuffer = new PatchBuffer(); }); it('returns a new FilePatch that unstages only the specified lines', function() { @@ -340,12 +340,12 @@ describe('FilePatch', function() { const newFile = new File({path: 'file.txt', mode: '100644'}); const filePatch = new FilePatch(oldFile, newFile, patch); - const unstagedPatch = filePatch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([1, 3])); + const unstagedPatch = filePatch.buildUnstagePatchForLines(buffer, unstagePatchBuffer, new Set([1, 3])); assert.strictEqual(unstagedPatch.getStatus(), 'modified'); assert.strictEqual(unstagedPatch.getOldFile(), newFile); assert.strictEqual(unstagedPatch.getNewFile(), newFile); - assert.strictEqual(unstageLayeredBuffer.buffer.getText(), '0000\n0001\n0002\n0003\n0004\n'); - assertInFilePatch(unstagedPatch, unstageLayeredBuffer.buffer).hunks( + assert.strictEqual(unstagePatchBuffer.buffer.getText(), '0000\n0001\n0002\n0003\n0004\n'); + assertInFilePatch(unstagedPatch, unstagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 4, @@ -384,11 +384,11 @@ describe('FilePatch', function() { }); it('handles unstaging part of the file', function() { - const unstagePatch = addedFilePatch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([2])); + const unstagePatch = addedFilePatch.buildUnstagePatchForLines(buffer, unstagePatchBuffer, new Set([2])); assert.strictEqual(unstagePatch.getStatus(), 'modified'); assert.strictEqual(unstagePatch.getOldFile(), newFile); assert.strictEqual(unstagePatch.getNewFile(), newFile); - assertInFilePatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( + assertInFilePatch(unstagePatch, unstagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -402,11 +402,11 @@ describe('FilePatch', function() { }); it('handles unstaging all lines, leaving nothing staged', function() { - const unstagePatch = addedFilePatch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([0, 1, 2])); + const unstagePatch = addedFilePatch.buildUnstagePatchForLines(buffer, unstagePatchBuffer, new Set([0, 1, 2])); assert.strictEqual(unstagePatch.getStatus(), 'deleted'); assert.strictEqual(unstagePatch.getOldFile(), newFile); assert.isFalse(unstagePatch.getNewFile().isPresent()); - assertInFilePatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( + assertInFilePatch(unstagePatch, unstagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -421,10 +421,10 @@ describe('FilePatch', function() { it('unsets the newFile when a symlink is deleted and a file is created in its place', function() { const oldSymlink = new File({path: 'file.txt', mode: '120000', symlink: 'wat.txt'}); const patch = new FilePatch(oldSymlink, newFile, addedPatch); - const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([0, 1, 2])); + const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstagePatchBuffer, new Set([0, 1, 2])); assert.strictEqual(unstagePatch.getOldFile(), newFile); assert.isFalse(unstagePatch.getNewFile().isPresent()); - assertInFilePatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( + assertInFilePatch(unstagePatch, unstagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -459,11 +459,11 @@ describe('FilePatch', function() { }); it('handles unstaging part of the file', function() { - const discardPatch = removedFilePatch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([1])); + const discardPatch = removedFilePatch.buildUnstagePatchForLines(buffer, unstagePatchBuffer, new Set([1])); assert.strictEqual(discardPatch.getStatus(), 'added'); assert.strictEqual(discardPatch.getOldFile(), nullFile); assert.strictEqual(discardPatch.getNewFile(), oldFile); - assertInFilePatch(discardPatch, unstageLayeredBuffer.buffer).hunks( + assertInFilePatch(discardPatch, unstagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 0, @@ -478,13 +478,13 @@ describe('FilePatch', function() { it('handles unstaging the entire file', function() { const discardPatch = removedFilePatch.buildUnstagePatchForLines( buffer, - unstageLayeredBuffer, + unstagePatchBuffer, new Set([0, 1, 2]), ); assert.strictEqual(discardPatch.getStatus(), 'added'); assert.strictEqual(discardPatch.getOldFile(), nullFile); assert.strictEqual(discardPatch.getNewFile(), oldFile); - assertInFilePatch(discardPatch, unstageLayeredBuffer.buffer).hunks( + assertInFilePatch(discardPatch, unstagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 2, diff --git a/test/models/patch/patch-buffer.test.js b/test/models/patch/patch-buffer.test.js index 8289c2c55a..44d9e8f48b 100644 --- a/test/models/patch/patch-buffer.test.js +++ b/test/models/patch/patch-buffer.test.js @@ -36,7 +36,7 @@ describe('PatchBuffer', function() { }); describe('extractPatchBuffer', function() { - it('extracts a subset of the buffer and layers as a new LayeredBuffer', function() { + it('extracts a subset of the buffer and layers as a new PatchBuffer', function() { const m0 = patchBuffer.markRange('patch', [[1, 0], [3, 0]]); // before const m1 = patchBuffer.markRange('hunk', [[2, 0], [4, 2]]); // before, ending at the extraction point const m2 = patchBuffer.markRange('hunk', [[4, 2], [5, 0]]); // within diff --git a/test/models/patch/patch.test.js b/test/models/patch/patch.test.js index 5fe2e3050e..796bf2f12a 100644 --- a/test/models/patch/patch.test.js +++ b/test/models/patch/patch.test.js @@ -163,19 +163,19 @@ describe('Patch', function() { }); describe('stage patch generation', function() { - let stageLayeredBuffer; + let stagePatchBuffer; beforeEach(function() { - stageLayeredBuffer = new PatchBuffer(); + stagePatchBuffer = new PatchBuffer(); }); it('creates a patch that applies selected lines from only the first hunk', function() { const {patch, buffer: originalBuffer} = buildPatchFixture(); - const stagePatch = patch.buildStagePatchForLines(originalBuffer, stageLayeredBuffer, new Set([2, 3, 4, 5])); + const stagePatch = patch.buildStagePatchForLines(originalBuffer, stagePatchBuffer, new Set([2, 3, 4, 5])); // buffer rows: 0 1 2 3 4 5 6 const expectedBufferText = '0000\n0001\n0002\n0003\n0004\n0005\n0006\n'; - assert.strictEqual(stageLayeredBuffer.buffer.getText(), expectedBufferText); - assertInPatch(stagePatch, stageLayeredBuffer.buffer).hunks( + assert.strictEqual(stagePatchBuffer.buffer.getText(), expectedBufferText); + assertInPatch(stagePatch, stagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 6, @@ -192,11 +192,11 @@ describe('Patch', function() { it('creates a patch that applies selected lines from a single non-first hunk', function() { const {patch, buffer: originalBuffer} = buildPatchFixture(); - const stagePatch = patch.buildStagePatchForLines(originalBuffer, stageLayeredBuffer, new Set([8, 13, 14, 16])); + const stagePatch = patch.buildStagePatchForLines(originalBuffer, stagePatchBuffer, new Set([8, 13, 14, 16])); // buffer rows: 0 1 2 3 4 5 6 7 8 9 const expectedBufferText = '0007\n0008\n0010\n0011\n0012\n0013\n0014\n0015\n0016\n0018\n'; - assert.strictEqual(stageLayeredBuffer.buffer.getText(), expectedBufferText); - assertInPatch(stagePatch, stageLayeredBuffer.buffer).hunks( + assert.strictEqual(stagePatchBuffer.buffer.getText(), expectedBufferText); + assertInPatch(stagePatch, stagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 9, @@ -216,7 +216,7 @@ describe('Patch', function() { it('creates a patch that applies selected lines from several hunks', function() { const {patch, buffer: originalBuffer} = buildPatchFixture(); - const stagePatch = patch.buildStagePatchForLines(originalBuffer, stageLayeredBuffer, new Set([1, 5, 15, 16, 17, 25])); + const stagePatch = patch.buildStagePatchForLines(originalBuffer, stagePatchBuffer, new Set([1, 5, 15, 16, 17, 25])); const expectedBufferText = // buffer rows // 0 1 2 3 4 @@ -225,8 +225,8 @@ describe('Patch', function() { '0007\n0010\n0011\n0012\n0013\n0014\n0015\n0016\n0017\n0018\n' + // 15 16 17 '0024\n0025\n No newline at end of file\n'; - assert.strictEqual(stageLayeredBuffer.buffer.getText(), expectedBufferText); - assertInPatch(stagePatch, stageLayeredBuffer.buffer).hunks( + assert.strictEqual(stagePatchBuffer.buffer.getText(), expectedBufferText); + assertInPatch(stagePatch, stagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 4, @@ -265,12 +265,12 @@ describe('Patch', function() { it('marks ranges for each change region on the correct marker layer', function() { const {patch, buffer: originalBuffer} = buildPatchFixture(); - patch.buildStagePatchForLines(originalBuffer, stageLayeredBuffer, new Set([1, 5, 15, 16, 17, 25])); + patch.buildStagePatchForLines(originalBuffer, stagePatchBuffer, new Set([1, 5, 15, 16, 17, 25])); const layerRanges = [ Hunk.layerName, Unchanged.layerName, Addition.layerName, Deletion.layerName, NoNewline.layerName, ].reduce((obj, layerName) => { - obj[layerName] = stageLayeredBuffer.findMarkers(layerName, {}).map(marker => marker.getRange().serialize()); + obj[layerName] = stagePatchBuffer.findMarkers(layerName, {}).map(marker => marker.getRange().serialize()); return obj; }, {}); @@ -320,9 +320,9 @@ describe('Patch', function() { const patch = new Patch({status: 'deleted', hunks, marker}); - const stagedPatch = patch.buildStagePatchForLines(buffer, stageLayeredBuffer, new Set([1, 3, 4])); + const stagedPatch = patch.buildStagePatchForLines(buffer, stagePatchBuffer, new Set([1, 3, 4])); assert.strictEqual(stagedPatch.getStatus(), 'modified'); - assertInPatch(stagedPatch, stageLayeredBuffer.buffer).hunks( + assertInPatch(stagedPatch, stagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 5, @@ -353,7 +353,7 @@ describe('Patch', function() { const marker = markRange(layers.patch, 0, 2); const patch = new Patch({status: 'deleted', hunks, marker}); - const stagePatch0 = patch.buildStagePatchForLines(buffer, stageLayeredBuffer, new Set([0, 1, 2])); + const stagePatch0 = patch.buildStagePatchForLines(buffer, stagePatchBuffer, new Set([0, 1, 2])); assert.strictEqual(stagePatch0.getStatus(), 'deleted'); }); @@ -364,21 +364,21 @@ describe('Patch', function() { }); describe('unstage patch generation', function() { - let unstageLayeredBuffer; + let unstagePatchBuffer; beforeEach(function() { - unstageLayeredBuffer = new PatchBuffer(); + unstagePatchBuffer = new PatchBuffer(); }); it('creates a patch that updates the index to unapply selected lines from a single hunk', function() { const {patch, buffer: originalBuffer} = buildPatchFixture(); - const unstagePatch = patch.buildUnstagePatchForLines(originalBuffer, unstageLayeredBuffer, new Set([8, 12, 13])); + const unstagePatch = patch.buildUnstagePatchForLines(originalBuffer, unstagePatchBuffer, new Set([8, 12, 13])); assert.strictEqual( - unstageLayeredBuffer.buffer.getText(), + unstagePatchBuffer.buffer.getText(), // 0 1 2 3 4 5 6 7 8 '0007\n0008\n0009\n0010\n0011\n0012\n0013\n0017\n0018\n', ); - assertInPatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( + assertInPatch(unstagePatch, unstagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 8, @@ -396,9 +396,9 @@ describe('Patch', function() { it('creates a patch that updates the index to unapply lines from several hunks', function() { const {patch, buffer: originalBuffer} = buildPatchFixture(); - const unstagePatch = patch.buildUnstagePatchForLines(originalBuffer, unstageLayeredBuffer, new Set([1, 4, 5, 16, 17, 20, 25])); + const unstagePatch = patch.buildUnstagePatchForLines(originalBuffer, unstagePatchBuffer, new Set([1, 4, 5, 16, 17, 20, 25])); assert.strictEqual( - unstageLayeredBuffer.buffer.getText(), + unstagePatchBuffer.buffer.getText(), // 0 1 2 3 4 5 '0000\n0001\n0003\n0004\n0005\n0006\n' + // 6 7 8 9 10 11 12 13 @@ -408,7 +408,7 @@ describe('Patch', function() { // 17 18 19 '0024\n0025\n No newline at end of file\n', ); - assertInPatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( + assertInPatch(unstagePatch, unstagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 5, @@ -457,11 +457,11 @@ describe('Patch', function() { it('marks ranges for each change region on the correct marker layer', function() { const {patch, buffer: originalBuffer} = buildPatchFixture(); - patch.buildUnstagePatchForLines(originalBuffer, unstageLayeredBuffer, new Set([1, 4, 5, 16, 17, 20, 25])); + patch.buildUnstagePatchForLines(originalBuffer, unstagePatchBuffer, new Set([1, 4, 5, 16, 17, 20, 25])); const layerRanges = [ Hunk.layerName, Unchanged.layerName, Addition.layerName, Deletion.layerName, NoNewline.layerName, ].reduce((obj, layerName) => { - obj[layerName] = unstageLayeredBuffer.findMarkers(layerName, {}).map(marker => marker.getRange().serialize()); + obj[layerName] = unstagePatchBuffer.findMarkers(layerName, {}).map(marker => marker.getRange().serialize()); return obj; }, {}); @@ -512,10 +512,10 @@ describe('Patch', function() { ]; const marker = markRange(layers.patch, 0, 2); const patch = new Patch({status: 'added', hunks, marker}); - const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([1, 2])); + const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstagePatchBuffer, new Set([1, 2])); assert.strictEqual(unstagePatch.getStatus(), 'modified'); - assert.strictEqual(unstageLayeredBuffer.buffer.getText(), '0000\n0001\n0002\n'); - assertInPatch(unstagePatch, unstageLayeredBuffer.buffer).hunks( + assert.strictEqual(unstagePatchBuffer.buffer.getText(), '0000\n0001\n0002\n'); + assertInPatch(unstagePatch, unstagePatchBuffer.buffer).hunks( { startRow: 0, endRow: 2, @@ -546,7 +546,7 @@ describe('Patch', function() { const marker = markRange(layers.patch, 0, 2); const patch = new Patch({status: 'added', hunks, marker}); - const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([0, 1, 2])); + const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstagePatchBuffer, new Set([0, 1, 2])); assert.strictEqual(unstagePatch.getStatus(), 'deleted'); }); @@ -568,7 +568,7 @@ describe('Patch', function() { const marker = markRange(layers.patch, 0, 2); const patch = new Patch({status: 'deleted', hunks, marker}); - const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstageLayeredBuffer, new Set([0, 1, 2])); + const unstagePatch = patch.buildUnstagePatchForLines(buffer, unstagePatchBuffer, new Set([0, 1, 2])); assert.strictEqual(unstagePatch.getStatus(), 'added'); }); From 8db614da856158790e0d0802d8bbed5237f571c0 Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 28 Feb 2019 16:52:22 -0800 Subject: [PATCH 2162/4053] add details of third iteration --- docs/feature-requests/003-pull-request-review.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 3802a51dbb..d04793a091 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -119,7 +119,7 @@ Clicking "Finish your review" from a comment or clicking "Review Changes" in the This item is opened in the workspace's right dock when the user: * Clicks the review progress bar in the GitHub tab. -* Clicks the "open" button on the review summary footer of a `PullRequestDetailItem`. +* Clicks the "open reviews" button on the review summary footer of a `PullRequestDetailItem`. * Clicks the "<>" button on a review comment in the "Files Changed" tab of a `PullRequestDetailItem`. It shows a scrollable view of all of the reviews and comments associated with a specific pull request, @@ -201,6 +201,12 @@ It was a great improvement, but filtering the diff with radio buttons and checkb - Keep using an editable editor for the diffs, but add some padding. - Introduce a "Reviews" footer to all sub-views to allow creating/submit a review, no matter where you are. +#### Third iteration + +Long comments can disrupt the code editing experience. Our third iteration keeps the review comments in a dock, a la Google Docs. This helps code authors more easily address comments, because they can see the comments and also get them out of the way. + +Since this approach different from previous approaches, we performed a series of [usability studies](https://github.com/github/pe-editor-tools/blob/master/community/usability-testing/atom_rcid_research_summary.md) to validate that users would find this approach useful. + ## Unresolved questions From 37b09dc9da4c99aae316c41c17058de328f58009 Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 28 Feb 2019 17:00:04 -0800 Subject: [PATCH 2163/4053] remove section about review summary tab since we're not implementing that. --- docs/feature-requests/003-pull-request-review.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index d04793a091..70d461456c 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -57,15 +57,8 @@ Clicking on the "Files Changed" tab displays the full, multi-file diff associate ![files](https://user-images.githubusercontent.com/378023/51305826-43ab9d80-1a7f-11e9-8b41-42bc4812d214.png) -Clicking on the "Expand review summaries" control in the filter bar reveals an inline panel that displays the summary of each review created on this pull request, including the review's author, the review's current state, its summary comment, and a progress bar showing how many of the review comments associated with this review have been marked as resolved. -![review summary list panel](https://user-images.githubusercontent.com/378023/51305827-43ab9d80-1a7f-11e9-90be-902e541b805f.png) - -Clicking on "Comments" within each review summary block hides or reveals the review summary comments associated with that review in diff on this tab. Clicking the "Collapse review summaries" control conceals the review summary panel again. - -![review summary list panel with comments](https://user-images.githubusercontent.com/378023/51306485-ca14af00-1a80-11e9-8e52-bb4d55928736.png) - -Beneath the review summary panel is the pull request's combined diff. Diffs are editable, but _only_ if the pull request branch is checked out and the local branch history has not diverged incompatibly from the remote branch history. +Diffs are editable, but _only_ if the pull request branch is checked out and the local branch history has not diverged incompatibly from the remote branch history. For large diffs, the files can be collapsed to get a better overview. From 145d276b4d52256714794abd719344a0690cd758 Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 28 Feb 2019 17:01:47 -0800 Subject: [PATCH 2164/4053] "resolved conversation" buttons are now "mark as resolved" buttons. --- docs/feature-requests/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 70d461456c..095e6ae724 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -104,7 +104,7 @@ Clicking "Finish your review" from a comment or clicking "Review Changes" in the ![resolve a review](https://user-images.githubusercontent.com/378023/46927875-c08b3d80-d072-11e8-978b-024111312d79.png) -* Review comments can be resolved by clicking on the "Resolve conversation" buttons. +* Review comments can be resolved by clicking on the "Mark as resolved" buttons. * If the "reply..." editor has non-whitespace content, it is submitted as a final comment first. ### PullRequestReviewsItem From bbb5f2cdcc6684d724bce85fdab83c940bd2ed0a Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 28 Feb 2019 17:46:33 -0800 Subject: [PATCH 2165/4053] the tab is current "files" not "files changed" --- docs/feature-requests/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 095e6ae724..240a1bfc9d 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -51,7 +51,7 @@ A panel at the bottom of the pane shows the progress for resolved review comment When the pull request is checked out, an "open" button is shown in the review footer. Clicking "open" opens a `PullRequestReviewsItem` for this pull request's review comments as an item in the right workspace dock. -### Files Changed (tab) +### Files (tab) Clicking on the "Files Changed" tab displays the full, multi-file diff associated with the pull request. This is akin to the "Files changed" tab on dotcom. From bf18eb71672ee3b9c13813929bd5ce00bf8d8c34 Mon Sep 17 00:00:00 2001 From: annthurium Date: Thu, 28 Feb 2019 17:48:53 -0800 Subject: [PATCH 2166/4053] add clarifying point about migrating PullRequestDetailView to dock --- docs/feature-requests/003-pull-request-review.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 240a1bfc9d..7c2625d593 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -200,6 +200,8 @@ Long comments can disrupt the code editing experience. Our third iteration keep Since this approach different from previous approaches, we performed a series of [usability studies](https://github.com/github/pe-editor-tools/blob/master/community/usability-testing/atom_rcid_research_summary.md) to validate that users would find this approach useful. +We may at some point want to migrate the entire PullRequestDetailView from the pane item to the dock, so as not to duplicate information. However, in the interest of getting code review in the editor shipped, we'll keep the pane item around in the short term. + ## Unresolved questions From 3c4580d1f19eae2c6b8fbfa514c4077e1fd68e79 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 28 Feb 2019 17:55:06 -0800 Subject: [PATCH 2167/4053] add new screenshot of PullRequestReviewsItem --- docs/feature-requests/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 7c2625d593..36ba29914f 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -117,7 +117,7 @@ This item is opened in the workspace's right dock when the user: It shows a scrollable view of all of the reviews and comments associated with a specific pull request, -![pull request reviews item](https://user-images.githubusercontent.com/378023/51306720-6939a680-1a81-11e9-8eaa-ffd480e3d47f.png) +![pull request reviews item](https://user-images.githubusercontent.com/3781742/53610984-c85f0080-3b81-11e9-9a82-9df43b6410f3.png) Reviews are sorted by "urgency," showing reviews that still need to be addressed at the top. Within each group, sorting is done by "newest first". From 4718550ccef8d64f5a128ebc60b9b98f48aa7a1f Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 28 Feb 2019 18:08:59 -0800 Subject: [PATCH 2168/4053] add screenshot of expanded RCID comment --- docs/feature-requests/003-pull-request-review.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 36ba29914f..9af4f95f1e 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -129,7 +129,14 @@ Reviews are sorted by "urgency," showing reviews that still need to be addressed Clicking on a review summary comment expands or collapses the associated review comments. -Clicking on a review comment opens a `TextEditor` on the corresponding position of the file under review. The clicked review comment is highlighted as the "current" one. +screen shot 2019-02-28 at 6 03 50 pm + +In addition to the comment, users see an abbreviated version of the diff, with 4 context lines. + +Clicking on the "Jump To File" button opens a `TextEditor` on the corresponding position of the file under review. The clicked review comment is highlighted as the "current" one. + +Clicking on the "View Changes" button opens the "Files" tab of the `PullRequestDetailsView`, so the user can see the full diff. + #### Within an open TextEditor From 6dd375269107368d17e05bf950827d6761937b96 Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 28 Feb 2019 18:13:45 -0800 Subject: [PATCH 2169/4053] update footer screenshot --- docs/feature-requests/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index 9af4f95f1e..a4823c9aad 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -45,7 +45,7 @@ Below the tabs is a "tools bar" with controls to toggle review comments or colla #### Footer -![reviews panel](https://user-images.githubusercontent.com/378023/51305353-f2e77500-1a7d-11e9-96f0-da879d49da3a.png) +![reviews panel](https://user-images.githubusercontent.com/3781742/53611708-5805ae80-3b84-11e9-915d-fb29476e3001.png) A panel at the bottom of the pane shows the progress for resolved review comments. It also has a "Review Changes" button to create a new review. This panel is persistent throughout all sub-views. It allows creating new reviews no matter where you are. From 51e2f848b86a75341b9f3f4da57be990f7506b5e Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Thu, 28 Feb 2019 18:16:36 -0800 Subject: [PATCH 2170/4053] clarify button copy --- docs/feature-requests/003-pull-request-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/feature-requests/003-pull-request-review.md b/docs/feature-requests/003-pull-request-review.md index a4823c9aad..920072e4bd 100644 --- a/docs/feature-requests/003-pull-request-review.md +++ b/docs/feature-requests/003-pull-request-review.md @@ -49,7 +49,7 @@ Below the tabs is a "tools bar" with controls to toggle review comments or colla A panel at the bottom of the pane shows the progress for resolved review comments. It also has a "Review Changes" button to create a new review. This panel is persistent throughout all sub-views. It allows creating new reviews no matter where you are. -When the pull request is checked out, an "open" button is shown in the review footer. Clicking "open" opens a `PullRequestReviewsItem` for this pull request's review comments as an item in the right workspace dock. +When the pull request is checked out, an "Open Reviews" button is shown in the review footer. Clicking "Open Reviews" opens a `PullRequestReviewsItem` for this pull request's review comments as an item in the right workspace dock. ### Files (tab) From d1cf331cf5bc23aec91b8521749dfeef624d4475 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 09:32:49 -0500 Subject: [PATCH 2171/4053] Stub out "Reviews" component tree --- lib/containers/reviews-container.js | 7 +++++++ lib/controllers/reviews-controller.js | 7 +++++++ lib/items/reviews-item.js | 7 +++++++ lib/views/reviews-view.js | 7 +++++++ 4 files changed, 28 insertions(+) create mode 100644 lib/containers/reviews-container.js create mode 100644 lib/controllers/reviews-controller.js create mode 100644 lib/items/reviews-item.js create mode 100644 lib/views/reviews-view.js diff --git a/lib/containers/reviews-container.js b/lib/containers/reviews-container.js new file mode 100644 index 0000000000..8a531f7521 --- /dev/null +++ b/lib/containers/reviews-container.js @@ -0,0 +1,7 @@ +import React from 'react'; + +export default class ReviewsContainer extends React.Component { + render() { + return null; + } +} diff --git a/lib/controllers/reviews-controller.js b/lib/controllers/reviews-controller.js new file mode 100644 index 0000000000..cfac131b33 --- /dev/null +++ b/lib/controllers/reviews-controller.js @@ -0,0 +1,7 @@ +import React from 'react'; + +export default class ReviewsController extends React.Component { + render() { + return null; + } +} diff --git a/lib/items/reviews-item.js b/lib/items/reviews-item.js new file mode 100644 index 0000000000..83d44c4fcc --- /dev/null +++ b/lib/items/reviews-item.js @@ -0,0 +1,7 @@ +import React from 'react'; + +export default class ReviewsItem extends React.Component { + render() { + return null; + } +} diff --git a/lib/views/reviews-view.js b/lib/views/reviews-view.js new file mode 100644 index 0000000000..4e92166fae --- /dev/null +++ b/lib/views/reviews-view.js @@ -0,0 +1,7 @@ +import React from 'react'; + +export default class ReviewsView extends React.Component { + render() { + return null; + } +} From 2840bd44c86a191e030783509730cf9a8bcdd8aa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 10:16:19 -0500 Subject: [PATCH 2172/4053] Item boilerplate for ReviewsItem --- lib/items/reviews-item.js | 76 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/lib/items/reviews-item.js b/lib/items/reviews-item.js index 83d44c4fcc..17c85428cb 100644 --- a/lib/items/reviews-item.js +++ b/lib/items/reviews-item.js @@ -1,7 +1,81 @@ import React from 'react'; +import PropTypes from 'prop-types'; +import {Emitter} from 'event-kit'; + +import {GithubLoginModelPropType, WorkdirContextPoolPropType} from '../prop-types'; +import ReviewsContainer from '../containers/reviews-container'; export default class ReviewsItem extends React.Component { + static propTypes = { + // Parsed from URI + host: PropTypes.string.isRequired, + owner: PropTypes.string.isRequired, + repo: PropTypes.string.isRequired, + number: PropTypes.number.isRequired, + workdir: PropTypes.string.isRequired, + + // Package models + workdirContextPool: WorkdirContextPoolPropType.isRequired, + loginModel: GithubLoginModelPropType.isRequired, + } + + static uriPattern = 'atom-github://reviews/{host}/{owner}/{repo}/{number}?workdir={workdir}' + + static buildURI(host, owner, repo, number, workdir) { + return 'atom-github://reviews/' + + encodeURIComponent(host) + '/' + + encodeURIComponent(owner) + '/' + + encodeURIComponent(repo) + '/' + + encodeURIComponent(number) + '?workdir=' + encodeURIComponent(workdir); + } + + constructor(props) { + super(props); + + this.emitter = new Emitter(); + this.isDestroyed = false; + } + render() { - return null; + return ( + + ); + } + + getTitle() { + return `Reviews #${this.props.number}`; + } + + getDefaultLocation() { + return 'right'; + } + + getPreferredWidth() { + return 400; + } + + destroy() { + /* istanbul ignore else */ + if (!this.isDestroyed) { + this.emitter.emit('did-destroy'); + this.isDestroyed = true; + } + } + + onDidDestroy(callback) { + return this.emitter.on('did-destroy', callback); + } + + serialize() { + return { + deserializer: 'ReviewsStub', + uri: this.constructor.buildURI( + this.props.host, + this.props.owner, + this.props.repo, + this.props.number, + this.props.workdir, + ), + }; } } From 66658d7e63d73886e9c84f62ea758597c1b54ab1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 10:57:06 -0500 Subject: [PATCH 2173/4053] ReviewsContainer boilerplate: API token, repository data, and GraphQL --- lib/containers/reviews-container.js | 142 +++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/lib/containers/reviews-container.js b/lib/containers/reviews-container.js index 8a531f7521..7de75d094e 100644 --- a/lib/containers/reviews-container.js +++ b/lib/containers/reviews-container.js @@ -1,7 +1,147 @@ import React from 'react'; +import PropTypes from 'prop-types'; +import yubikiri from 'yubikiri'; +import {QueryRenderer, graphql} from 'react-relay'; + +import {PAGE_SIZE} from '../helpers'; +import {GithubLoginModelPropType, EndpointPropType} from '../prop-types'; +import {UNAUTHENTICATED, INSUFFICIENT} from '../shared/keytar-strategy'; +import ObserveModel from '../views/observe-model'; +import LoadingView from '../views/loading-view'; +import GithubLoginView from '../views/github-login-view'; +import QueryErrorView from '../views/query-error-view'; +import RelayNetworkLayerManager from '../relay-network-layer-manager'; +import RelayEnvironment from '../views/relay-environment'; +import ReviewsController from '../controllers/reviews-controller'; export default class ReviewsContainer extends React.Component { + static propTypes = { + // Connection + endpoint: EndpointPropType.isRequired, + + // Pull request selection criteria + owner: PropTypes.string.isRequired, + repo: PropTypes.string.isRequired, + number: PropTypes.number.isRequired, + + // Package models + repository: PropTypes.object.isRequired, + loginModel: GithubLoginModelPropType.isRequired, + } + render() { - return null; + return ( + + {tokenData => this.renderWithToken(tokenData)} + + ); + } + + renderWithToken(tokenData) { + if (!tokenData) { + return ; + } + + if (tokenData.token === UNAUTHENTICATED) { + return ; + } + + if (tokenData.token === INSUFFICIENT) { + return ( + +

    + Your token no longer has sufficient authorizations. Please re-authenticate and generate a new one. +

    +
    + ); + } + + return ( + + {repoData => this.renderWithRepositoryData(repoData, tokenData.token)} + + ); } + + renderWithRepositoryData(repoData, token) { + if (!repoData) { + return ; + } + + const environment = RelayNetworkLayerManager.getEnvironmentForHost(this.props.endpoint, token); + const query = graphql` + query reviewsContainerQuery + ( + $repoOwner: String! + $repoName: String! + $prNumber: Int! + $reviewCount: Int! + $reviewCursor: String + $commentCount: Int! + $commentCursor: String + ) { + repository(owner: $repoOwner, name: $repoName) { + id + } + } + `; + const variables = { + repoOwner: this.props.owner, + repoName: this.props.repo, + prNumber: this.props.number, + reviewCount: PAGE_SIZE, + reviewCursor: null, + commentCount: PAGE_SIZE, + commentCursor: null, + }; + + return ( + + this.renderWithResult(queryResult, repoData)} + /> + + ); + } + + renderWithResult({error, props, retry}, repoData) { + if (error) { + return ( + + ); + } + + if (!props) { + return ; + } + + return ( + + ); + } + + fetchToken = loginModel => loginModel.getToken(this.props.endpoint.getLoginAccount()); + + fetchRepositoryData = repository => { + return yubikiri({ + branches: repository.getBranches(), + isLoading: repository.isLoading(), + isPresent: repository.isPresent(), + }); + } + + handleLogin = token => this.props.loginModel.setToken(this.props.endpoint.getLoginAccount(), token); + + handleLogout = this.props.loginModel.removeToken(this.props.endpoint.getLoginAccount()); } From d82a26893c1dc258fad8cf68230c62e2c7b6d7c6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 10:57:25 -0500 Subject: [PATCH 2174/4053] Derive and pass repository and endpoint in ReviewsItem --- lib/items/reviews-item.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/items/reviews-item.js b/lib/items/reviews-item.js index 17c85428cb..f8fbb0df01 100644 --- a/lib/items/reviews-item.js +++ b/lib/items/reviews-item.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import {Emitter} from 'event-kit'; import {GithubLoginModelPropType, WorkdirContextPoolPropType} from '../prop-types'; +import {getEndpoint} from '../models/endpoint'; import ReviewsContainer from '../containers/reviews-container'; export default class ReviewsItem extends React.Component { @@ -37,8 +38,15 @@ export default class ReviewsItem extends React.Component { } render() { + const endpoint = getEndpoint(this.props.host); + const repository = this.props.workdirContextPool.add(this.props.workdir).getRepository(); + return ( - + ); } From df61f33237104c69042b82276cdd22f37174ac9f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 10:57:36 -0500 Subject: [PATCH 2175/4053] Render ReviewsView from ReviewsController --- lib/controllers/reviews-controller.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/controllers/reviews-controller.js b/lib/controllers/reviews-controller.js index cfac131b33..e586fda16f 100644 --- a/lib/controllers/reviews-controller.js +++ b/lib/controllers/reviews-controller.js @@ -1,7 +1,13 @@ import React from 'react'; +import ReviewsView from '../views/reviews-view'; + export default class ReviewsController extends React.Component { render() { - return null; + return ( + + ); } } From 44f214aa0bc4bc4a72569711a58695ada739aac8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 10:57:44 -0500 Subject: [PATCH 2176/4053] Placeholder ReviewsView --- lib/views/reviews-view.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/views/reviews-view.js b/lib/views/reviews-view.js index 4e92166fae..ce8ec6b45d 100644 --- a/lib/views/reviews-view.js +++ b/lib/views/reviews-view.js @@ -1,7 +1,13 @@ import React from 'react'; +import {inspect} from 'util'; export default class ReviewsView extends React.Component { render() { - return null; + return ( +
    +

    HELL YEAH PR REVIEW COMMENTS

    +
    {inspect(this.props, {depth: 4})}
    +
    + ); } } From c6926e35bf67395cfd8fbfb3a4b70b061c2a2134 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 11:01:21 -0500 Subject: [PATCH 2177/4053] Register ReviewsItem opener in RootController --- lib/controllers/root-controller.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index b911f11b3e..e83f80ad6b 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -22,6 +22,7 @@ import CommitDetailItem from '../items/commit-detail-item'; import CommitPreviewItem from '../items/commit-preview-item'; import GitTabItem from '../items/git-tab-item'; import GitHubTabItem from '../items/github-tab-item'; +import ReviewsItem from '../items/reviews-item'; import StatusBarTileController from './status-bar-tile-controller'; import RepositoryConflictController from './repository-conflict-controller'; import GitCacheView from '../views/git-cache-view'; @@ -425,6 +426,22 @@ export default class RootController extends React.Component { /> )} + + {({itemHolder, params}) => ( + + )} + {({itemHolder}) => } From c68452a04027d821b3ee6818d94d1dcaddbbeb7d Mon Sep 17 00:00:00 2001 From: annthurium Date: Fri, 1 Mar 2019 08:38:20 -0800 Subject: [PATCH 2178/4053] add `ReviewsFooterView` component --- lib/views/pr-detail-view.js | 8 ++----- lib/views/reviews-footer-view.js | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 lib/views/reviews-footer-view.js diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 92d660b024..794842b55a 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -15,6 +15,7 @@ import EmojiReactionsView from '../views/emoji-reactions-view'; import IssueishBadge from '../views/issueish-badge'; import PrCommitsView from '../views/pr-commits-view'; import PrStatusesView from '../views/pr-statuses-view'; +import ReviewsFooterView from '../views/reviews-footer-view'; import {PAGE_SIZE} from '../helpers'; class CheckoutState { @@ -275,12 +276,7 @@ export class BarePullRequestDetailView extends React.Component { {this.renderPullRequestBody(pullRequest)} - - +
    ); diff --git a/lib/views/reviews-footer-view.js b/lib/views/reviews-footer-view.js new file mode 100644 index 0000000000..1e444f2943 --- /dev/null +++ b/lib/views/reviews-footer-view.js @@ -0,0 +1,39 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + + +export default class ReviewsFooterView extends React.Component { + static propTypes = { + commentsResolved: PropTypes.number.isRequired, + totalComments: PropTypes.number.isRequired, + }; + + render() { + return ( +
    + + Reviews + + + + Resolved{' '} + + {this.props.commentsResolved} + + {' '}of{' '} + + {this.props.totalComments} + {' '}comments + + + {' '}comments{' '} + + + + +
    + ); + } +} From 0efc6a5c4ad565cac49d800a1bd681135d9ed36f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 14:49:52 -0500 Subject: [PATCH 2179/4053] Serialization and deserialization for ReviewsItems --- lib/github-package.js | 10 ++++++++++ lib/items/reviews-item.js | 2 +- package.json | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/github-package.js b/lib/github-package.js index 092c8eb6fa..ae96b0fa0c 100644 --- a/lib/github-package.js +++ b/lib/github-package.js @@ -394,6 +394,16 @@ export default class GithubPackage { return item; } + createReviewsStub({uri}) { + const item = StubItem.create('github-reviews', { + title: 'Reviews', + }, uri); + if (this.controller) { + this.rerender(); + } + return item; + } + destroyGitTabItem() { if (this.gitTabStubItem) { this.gitTabStubItem.destroy(); diff --git a/lib/items/reviews-item.js b/lib/items/reviews-item.js index f8fbb0df01..95369545b7 100644 --- a/lib/items/reviews-item.js +++ b/lib/items/reviews-item.js @@ -77,7 +77,7 @@ export default class ReviewsItem extends React.Component { serialize() { return { deserializer: 'ReviewsStub', - uri: this.constructor.buildURI( + uri: ReviewsItem.buildURI( this.props.host, this.props.owner, this.props.repo, diff --git a/package.json b/package.json index 056387db25..f6293da2b1 100644 --- a/package.json +++ b/package.json @@ -204,7 +204,8 @@ "GithubDockItem": "createDockItemStub", "FilePatchControllerStub": "createFilePatchControllerStub", "CommitPreviewStub": "createCommitPreviewStub", - "CommitDetailStub": "createCommitDetailStub" + "CommitDetailStub": "createCommitDetailStub", + "ReviewsStub": "createReviewsStub" }, "greenkeeper": { "ignore": [ From 3a4aee94edf793392cc74025a837ab5100fea8f5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 14:50:20 -0500 Subject: [PATCH 2180/4053] Plumbing to open the ReviewsItem from the ReviewsFooterView --- lib/controllers/issueish-detail-controller.js | 22 +++++++++++++++++++ lib/views/pr-detail-view.js | 7 +++++- lib/views/reviews-footer-view.js | 5 ++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/controllers/issueish-detail-controller.js b/lib/controllers/issueish-detail-controller.js index c704a8ebed..5beb3642a5 100644 --- a/lib/controllers/issueish-detail-controller.js +++ b/lib/controllers/issueish-detail-controller.js @@ -10,6 +10,7 @@ import EnableableOperation from '../models/enableable-operation'; import PullRequestDetailView, {checkoutStates} from '../views/pr-detail-view'; import IssueDetailView from '../views/issue-detail-view'; import CommitDetailItem from '../items/commit-detail-item'; +import ReviewsItem from '../items/reviews-item'; import {incrementCounter, addEvent} from '../reporter-proxy'; export class BareIssueishDetailController extends React.Component { @@ -132,6 +133,7 @@ export class BareIssueishDetailController extends React.Component { repository={repository} pullRequest={repository.pullRequest} checkoutOp={this.checkoutOp} + openReviews={this.openReviews} switchToIssueish={this.props.switchToIssueish} endpoint={this.props.endpoint} @@ -290,6 +292,26 @@ export class BareIssueishDetailController extends React.Component { await this.props.workspace.open(uri, {pending: true}); addEvent('open-commit-in-pane', {package: 'github', from: this.constructor.name}); } + + openReviews = async () => { + if (!this.props.workdirPath) { + return; + } + + if (this.state.typename !== 'PullRequest') { + return; + } + + const uri = ReviewsItem.buildURI( + this.props.endpoint.getHost(), + this.props.repository.owner.login, + this.props.repository.name, + this.props.issueishNumber, + this.props.workdirPath, + ); + await this.props.workspace.open(uri); + addEvent('open-reviews-tab', {package: 'github', from: this.constructor.name}); + } } export default createFragmentContainer(BareIssueishDetailController, { diff --git a/lib/views/pr-detail-view.js b/lib/views/pr-detail-view.js index 794842b55a..1f8cc76955 100644 --- a/lib/views/pr-detail-view.js +++ b/lib/views/pr-detail-view.js @@ -41,6 +41,7 @@ export class BarePullRequestDetailView extends React.Component { relay: PropTypes.shape({ refetch: PropTypes.func.isRequired, }), + openReviews: PropTypes.func.isRequired, switchToIssueish: PropTypes.func.isRequired, checkoutOp: EnableableOperationPropType.isRequired, repository: PropTypes.shape({ @@ -276,7 +277,11 @@ export class BarePullRequestDetailView extends React.Component { {this.renderPullRequestBody(pullRequest)} - +
    ); diff --git a/lib/views/reviews-footer-view.js b/lib/views/reviews-footer-view.js index 1e444f2943..8ad8a671ed 100644 --- a/lib/views/reviews-footer-view.js +++ b/lib/views/reviews-footer-view.js @@ -6,6 +6,9 @@ export default class ReviewsFooterView extends React.Component { static propTypes = { commentsResolved: PropTypes.number.isRequired, totalComments: PropTypes.number.isRequired, + + // Controller actions + openReviews: PropTypes.func.isRequired, }; render() { @@ -31,7 +34,7 @@ export default class ReviewsFooterView extends React.Component { + onClick={this.props.openReviews}>Open reviews
    ); From 78a6c64566036ce8f02b2bf3b306f640591a6988 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 14:53:27 -0500 Subject: [PATCH 2181/4053] Generate Relay files for the stub query in ReviewsContainer --- .../reviewsContainerQuery.graphql.js | 114 ++++++++++++++++++ lib/containers/reviews-container.js | 10 +- 2 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 lib/containers/__generated__/reviewsContainerQuery.graphql.js diff --git a/lib/containers/__generated__/reviewsContainerQuery.graphql.js b/lib/containers/__generated__/reviewsContainerQuery.graphql.js new file mode 100644 index 0000000000..df5eb0e96a --- /dev/null +++ b/lib/containers/__generated__/reviewsContainerQuery.graphql.js @@ -0,0 +1,114 @@ +/** + * @flow + * @relayHash 68140a8082e21c7d56516fb6ca6c8db7 + */ + +/* eslint-disable */ + +'use strict'; + +/*:: +import type { ConcreteRequest } from 'relay-runtime'; +export type reviewsContainerQueryVariables = {| + repoOwner: string, + repoName: string, +|}; +export type reviewsContainerQueryResponse = {| + +repository: ?{| + +id: string + |} +|}; +export type reviewsContainerQuery = {| + variables: reviewsContainerQueryVariables, + response: reviewsContainerQueryResponse, +|}; +*/ + + +/* +query reviewsContainerQuery( + $repoOwner: String! + $repoName: String! +) { + repository(owner: $repoOwner, name: $repoName) { + id + } +} +*/ + +const node/*: ConcreteRequest*/ = (function(){ +var v0 = [ + { + "kind": "LocalArgument", + "name": "repoOwner", + "type": "String!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "repoName", + "type": "String!", + "defaultValue": null + } +], +v1 = [ + { + "kind": "LinkedField", + "alias": null, + "name": "repository", + "storageKey": null, + "args": [ + { + "kind": "Variable", + "name": "name", + "variableName": "repoName", + "type": "String!" + }, + { + "kind": "Variable", + "name": "owner", + "variableName": "repoOwner", + "type": "String!" + } + ], + "concreteType": "Repository", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null + } + ] + } +]; +return { + "kind": "Request", + "fragment": { + "kind": "Fragment", + "name": "reviewsContainerQuery", + "type": "Query", + "metadata": null, + "argumentDefinitions": (v0/*: any*/), + "selections": (v1/*: any*/) + }, + "operation": { + "kind": "Operation", + "name": "reviewsContainerQuery", + "argumentDefinitions": (v0/*: any*/), + "selections": (v1/*: any*/) + }, + "params": { + "operationKind": "query", + "name": "reviewsContainerQuery", + "id": null, + "text": "query reviewsContainerQuery(\n $repoOwner: String!\n $repoName: String!\n) {\n repository(owner: $repoOwner, name: $repoName) {\n id\n }\n}\n", + "metadata": {} + } +}; +})(); +// prettier-ignore +(node/*: any*/).hash = '2a432f9e2a062215c96118ec0b5070cd'; +module.exports = node; diff --git a/lib/containers/reviews-container.js b/lib/containers/reviews-container.js index 7de75d094e..e0e1b4310c 100644 --- a/lib/containers/reviews-container.js +++ b/lib/containers/reviews-container.js @@ -74,11 +74,11 @@ export default class ReviewsContainer extends React.Component { ( $repoOwner: String! $repoName: String! - $prNumber: Int! - $reviewCount: Int! - $reviewCursor: String - $commentCount: Int! - $commentCursor: String + # $prNumber: Int! + # $reviewCount: Int! + # $reviewCursor: String + # $commentCount: Int! + # $commentCursor: String ) { repository(owner: $repoOwner, name: $repoName) { id From 41b88dcdcafad28c623c7f873de896c10836376e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 15:01:44 -0500 Subject: [PATCH 2182/4053] fetchToken() returns the token alone, not wrapped in an object --- lib/containers/reviews-container.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/containers/reviews-container.js b/lib/containers/reviews-container.js index e0e1b4310c..81cd707a27 100644 --- a/lib/containers/reviews-container.js +++ b/lib/containers/reviews-container.js @@ -37,16 +37,16 @@ export default class ReviewsContainer extends React.Component { ); } - renderWithToken(tokenData) { - if (!tokenData) { + renderWithToken(token) { + if (!token) { return ; } - if (tokenData.token === UNAUTHENTICATED) { + if (token === UNAUTHENTICATED) { return ; } - if (tokenData.token === INSUFFICIENT) { + if (token === INSUFFICIENT) { return (

    @@ -58,7 +58,7 @@ export default class ReviewsContainer extends React.Component { return ( - {repoData => this.renderWithRepositoryData(repoData, tokenData.token)} + {repoData => this.renderWithRepositoryData(repoData, token)} ); } @@ -143,5 +143,5 @@ export default class ReviewsContainer extends React.Component { handleLogin = token => this.props.loginModel.setToken(this.props.endpoint.getLoginAccount(), token); - handleLogout = this.props.loginModel.removeToken(this.props.endpoint.getLoginAccount()); + handleLogout = () => this.props.loginModel.removeToken(this.props.endpoint.getLoginAccount()); } From 062e20e84235946dd201d79bb89e10aa8ca9e694 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 15:38:36 -0500 Subject: [PATCH 2183/4053] Stub tests for ReviewsItem --- test/items/reviews-item.test.js | 110 ++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 test/items/reviews-item.test.js diff --git a/test/items/reviews-item.test.js b/test/items/reviews-item.test.js new file mode 100644 index 0000000000..f5fb363f31 --- /dev/null +++ b/test/items/reviews-item.test.js @@ -0,0 +1,110 @@ +import React from 'react'; +import {mount} from 'enzyme'; + +import ReviewsItem from '../../lib/items/reviews-item'; +import {cloneRepository} from '../helpers'; +import PaneItem from '../../lib/atom/pane-item'; +import {InMemoryStrategy} from '../../lib/shared/keytar-strategy'; +import GithubLoginModel from '../../lib/models/github-login-model'; +import WorkdirContextPool from '../../lib/models/workdir-context-pool'; + +describe('ReviewsItem', function() { + let atomEnv, repository, pool; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + const workdir = await cloneRepository(); + + pool = new WorkdirContextPool({ + workspace: atomEnv.workspace, + }); + + repository = pool.add(workdir).getRepository(); + }); + + afterEach(function() { + atomEnv.destroy(); + pool.clear(); + }); + + function buildPaneApp(override = {}) { + const props = { + workdirContextPool: pool, + loginModel: new GithubLoginModel(InMemoryStrategy), + ...override, + }; + + return ( + + {({itemHolder, params}) => ( + + )} + + ); + } + + async function open(wrapper, options = {}) { + const opts = { + host: 'github.com', + owner: 'atom', + repo: 'github', + number: 1848, + workdir: repository.getWorkingDirectoryPath(), + ...options, + }; + + const uri = ReviewsItem.buildURI(opts.host, opts.owner, opts.repo, opts.number, opts.workdir); + const item = await atomEnv.workspace.open(uri); + wrapper.update(); + return item; + } + + it('constructs and opens the correct URI', async function() { + const wrapper = mount(buildPaneApp()); + assert.isFalse(wrapper.exists('ReviewsItem')); + await open(wrapper); + assert.isTrue(wrapper.exists('ReviewsItem')); + }); + + it('locates the repository from the context pool', async function() { + const wrapper = mount(buildPaneApp()); + await open(wrapper); + + assert.strictEqual(wrapper.find('ReviewsContainer').prop('repository'), repository); + }); + + it('returns a title containing the pull request number', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(wrapper, {number: 1234}); + + assert.strictEqual(item.getTitle(), 'Reviews #1234'); + }); + + it('may be destroyed once', async function() { + const wrapper = mount(buildPaneApp()); + + const item = await open(wrapper); + const callback = sinon.spy(); + const sub = item.onDidDestroy(callback); + + assert.strictEqual(callback.callCount, 0); + item.destroy(); + assert.strictEqual(callback.callCount, 1); + + sub.dispose(); + }); + + it('serializes itself as a ReviewsItemStub', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(wrapper, {host: 'github.horse', owner: 'atom', repo: 'atom', number: 12, workdir: '/here'}); + assert.deepEqual(item.serialize(), { + deserializer: 'ReviewsStub', + uri: 'atom-github://reviews/github.horse/atom/atom/12?workdir=%2Fhere', + }); + }); +}); From a281f7061e379622707f7436becb955bc7f722bc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 15:45:24 -0500 Subject: [PATCH 2184/4053] Stub test for ReviewsContainer --- test/containers/reviews-container.test.js | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/containers/reviews-container.test.js diff --git a/test/containers/reviews-container.test.js b/test/containers/reviews-container.test.js new file mode 100644 index 0000000000..77075e6c85 --- /dev/null +++ b/test/containers/reviews-container.test.js @@ -0,0 +1,46 @@ +import React from 'react'; +import {mount} from 'enzyme'; + +import ReviewsContainer from '../../lib/containers/reviews-container'; +import Repository from '../../lib/models/repository'; +import {InMemoryStrategy} from '../../lib/shared/keytar-strategy'; +import GithubLoginModel from '../../lib/models/github-login-model'; +import {getEndpoint} from '../../lib/models/endpoint'; +import {cloneRepository} from '../helpers'; + +describe('ReviewsContainer', function() { + let atomEnv, repository, loginModel; + + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + const workdir = await cloneRepository(); + repository = new Repository(workdir); + await repository.getLoadPromise(); + loginModel = new GithubLoginModel(InMemoryStrategy); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(override = {}) { + const props = { + endpoint: getEndpoint('github.com'), + owner: 'atom', + repo: 'github', + number: 1234, + repository, + loginModel, + ...override, + }; + + return ; + } + + it('renders a loading spinner while the token is loading', function() { + sinon.stub(loginModel, 'getToken').returns(new Promise(() => {})); + + const wrapper = mount(buildApp()); + assert.isTrue(wrapper.exists('LoadingView')); + }); +}); From 99162ced2143aa68fb98a2dde405db81ad97d3cd Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 15:47:16 -0500 Subject: [PATCH 2185/4053] Stub test for ReviewsController --- test/controllers/reviews-controller.test.js | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 test/controllers/reviews-controller.test.js diff --git a/test/controllers/reviews-controller.test.js b/test/controllers/reviews-controller.test.js new file mode 100644 index 0000000000..273ef953c2 --- /dev/null +++ b/test/controllers/reviews-controller.test.js @@ -0,0 +1,31 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import ReviewsController from '../../lib/controllers/reviews-controller'; + +describe('ReviewsController', function() { + let atomEnv; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(override = {}) { + const props = { + ...override, + }; + + return ; + } + + it('renders a ReviewsView', function() { + const extra = Symbol('extra'); + const wrapper = shallow(buildApp({extra})); + assert.isTrue(wrapper.exists('ReviewsView')); + assert.strictEqual(wrapper.find('ReviewsView').prop('extra'), extra); + }); +}); From cc5a2479e1e639a3b0ac5117704488d014dd57ce Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 15:48:47 -0500 Subject: [PATCH 2186/4053] Stub test for ReviewsView --- test/views/reviews-view.test.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/views/reviews-view.test.js diff --git a/test/views/reviews-view.test.js b/test/views/reviews-view.test.js new file mode 100644 index 0000000000..736627fc22 --- /dev/null +++ b/test/views/reviews-view.test.js @@ -0,0 +1,29 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import ReviewsView from '../../lib/views/reviews-view'; + +describe('ReviewsView', function() { + let atomEnv; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(override = {}) { + const props = { + ...override, + }; + + return ; + } + + it('renders something', function() { + const wrapper = shallow(buildApp()); + assert.isTrue(wrapper.exists('div')); + }); +}); From f0ccde066ed183f772b4fef209bab22c922aaa62 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 1 Mar 2019 15:54:01 -0500 Subject: [PATCH 2187/4053] Stub test for ReviewsFooterView --- test/views/reviews-footer-view.test.js | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/views/reviews-footer-view.test.js diff --git a/test/views/reviews-footer-view.test.js b/test/views/reviews-footer-view.test.js new file mode 100644 index 0000000000..d4eed8b8d9 --- /dev/null +++ b/test/views/reviews-footer-view.test.js @@ -0,0 +1,44 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +import ReviewsFooterView from '../../lib/views/reviews-footer-view'; + +describe('ReviewsFooterView', function() { + let atomEnv; + + beforeEach(function() { + atomEnv = global.buildAtomEnvironment(); + }); + + afterEach(function() { + atomEnv.destroy(); + }); + + function buildApp(override = {}) { + const props = { + commentsResolved: 3, + totalComments: 4, + ...override, + }; + + return ; + } + + it('renders the resolved and total comment counts', function() { + const wrapper = shallow(buildApp({commentsResolved: 4, totalComments: 7})); + + assert.strictEqual(wrapper.find('.github-PrDetailViewReviews-countNr').text(), '4'); + assert.strictEqual(wrapper.find('.github-Reviews-countNr').text(), '7'); + assert.strictEqual(wrapper.find('.github-PrDetailViewReviews-progessBar').prop('value'), 4); + assert.strictEqual(wrapper.find('.github-PrDetailViewReviews-progessBar').prop('max'), 7); + }); + + it('triggers openReviews on button click', function() { + const openReviews = sinon.spy(); + const wrapper = shallow(buildApp({openReviews})); + + wrapper.find('.github-PrDetailViewReviews-openReviewsButton').simulate('click'); + + assert.isTrue(openReviews.called); + }); +}); From c50a80a5c9acac73c9d3775e57fb79e42578078c Mon Sep 17 00:00:00 2001 From: annthurium Date: Fri, 1 Mar 2019 13:47:58 -0800 Subject: [PATCH 2188/4053] style the :foot:er is my emoji too much? SORRY NOT SORRY --- lib/views/reviews-footer-view.js | 23 ++++++++----------- styles/reviews-footer-view.less | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 styles/reviews-footer-view.less diff --git a/lib/views/reviews-footer-view.js b/lib/views/reviews-footer-view.js index 8ad8a671ed..d693d8dcea 100644 --- a/lib/views/reviews-footer-view.js +++ b/lib/views/reviews-footer-view.js @@ -6,36 +6,33 @@ export default class ReviewsFooterView extends React.Component { static propTypes = { commentsResolved: PropTypes.number.isRequired, totalComments: PropTypes.number.isRequired, - - // Controller actions - openReviews: PropTypes.func.isRequired, }; render() { return ( -

    - +
    + Reviews - - + + Resolved{' '} - + {this.props.commentsResolved} {' '}of{' '} - + {this.props.totalComments} {' '}comments - {' '}comments{' '} - - + +
    ); } diff --git a/styles/reviews-footer-view.less b/styles/reviews-footer-view.less new file mode 100644 index 0000000000..6b24b82ad0 --- /dev/null +++ b/styles/reviews-footer-view.less @@ -0,0 +1,39 @@ +@import 'variables'; + +.github-ReviewsFooterView { + + margin-right: auto; + + // Footer ------------------------ + + &-footer { + display: flex; + align-items: center; + padding: @component-padding; + border-top: 1px solid @base-border-color; + background-color: @app-background-color; + } + + &-footerTitle { + font-size: 1.4em; + margin: 0 @component-padding*2 0 0; + } + + &-openReviewsButton, + &-reviewChangesButton { + margin-left: @component-padding; + } + + &-count { + margin-right: @component-padding; + color: @text-color-subtle; + } + + &-countNumber { + color: @text-color-highlight; + } + + &-progessBar { + margin-right: @component-padding; + } +} From e85700cbc170802f94d0a99503c0c1d473a5c164 Mon Sep 17 00:00:00 2001 From: annthurium Date: Fri, 1 Mar 2019 13:50:34 -0800 Subject: [PATCH 2189/4053] fix @smaswilson's changes that I accidentally nerfed Co-Authored-By: Ash Wilson --- lib/views/reviews-footer-view.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/views/reviews-footer-view.js b/lib/views/reviews-footer-view.js index d693d8dcea..f8abded9c1 100644 --- a/lib/views/reviews-footer-view.js +++ b/lib/views/reviews-footer-view.js @@ -6,6 +6,9 @@ export default class ReviewsFooterView extends React.Component { static propTypes = { commentsResolved: PropTypes.number.isRequired, totalComments: PropTypes.number.isRequired, + + // Controller actions + openReviews: PropTypes.func.isRequired, }; render() { @@ -31,7 +34,7 @@ export default class ReviewsFooterView extends React.Component {
    + onClick={this.props.openReviews}>Open reviews
    ); From 2e4bb46819f229ab1251208541c701c6f1dd3fc7 Mon Sep 17 00:00:00 2001 From: annthurium Date: Fri, 1 Mar 2019 14:11:11 -0800 Subject: [PATCH 2190/4053] fix ReviewsFooterView tests --- lib/views/reviews-footer-view.js | 6 +++--- styles/reviews-footer-view.less | 4 ++-- test/views/reviews-footer-view.test.js | 11 ++++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/views/reviews-footer-view.js b/lib/views/reviews-footer-view.js index f8abded9c1..82f41a8155 100644 --- a/lib/views/reviews-footer-view.js +++ b/lib/views/reviews-footer-view.js @@ -18,13 +18,13 @@ export default class ReviewsFooterView extends React.Component { Reviews - + Resolved{' '} - + {this.props.commentsResolved} {' '}of{' '} - + {this.props.totalComments} {' '}comments diff --git a/styles/reviews-footer-view.less b/styles/reviews-footer-view.less index 6b24b82ad0..4a3179b19b 100644 --- a/styles/reviews-footer-view.less +++ b/styles/reviews-footer-view.less @@ -24,12 +24,12 @@ margin-left: @component-padding; } - &-count { + &-commentCount { margin-right: @component-padding; color: @text-color-subtle; } - &-countNumber { + &-commentsResolved { color: @text-color-highlight; } diff --git a/test/views/reviews-footer-view.test.js b/test/views/reviews-footer-view.test.js index d4eed8b8d9..03339076f3 100644 --- a/test/views/reviews-footer-view.test.js +++ b/test/views/reviews-footer-view.test.js @@ -18,6 +18,7 @@ describe('ReviewsFooterView', function() { const props = { commentsResolved: 3, totalComments: 4, + openReviews: () => {}, ...override, }; @@ -27,17 +28,17 @@ describe('ReviewsFooterView', function() { it('renders the resolved and total comment counts', function() { const wrapper = shallow(buildApp({commentsResolved: 4, totalComments: 7})); - assert.strictEqual(wrapper.find('.github-PrDetailViewReviews-countNr').text(), '4'); - assert.strictEqual(wrapper.find('.github-Reviews-countNr').text(), '7'); - assert.strictEqual(wrapper.find('.github-PrDetailViewReviews-progessBar').prop('value'), 4); - assert.strictEqual(wrapper.find('.github-PrDetailViewReviews-progessBar').prop('max'), 7); + assert.strictEqual(wrapper.find('.github-ReviewsFooterView-commentsResolved').text(), '4'); + assert.strictEqual(wrapper.find('.github-ReviewsFooterView-totalComments').text(), '7'); + assert.strictEqual(wrapper.find('.github-ReviewsFooterView-progessBar').prop('value'), 4); + assert.strictEqual(wrapper.find('.github-ReviewsFooterView-progessBar').prop('max'), 7); }); it('triggers openReviews on button click', function() { const openReviews = sinon.spy(); const wrapper = shallow(buildApp({openReviews})); - wrapper.find('.github-PrDetailViewReviews-openReviewsButton').simulate('click'); + wrapper.find('.github-ReviewsFooterView-openReviewsButton').simulate('click'); assert.isTrue(openReviews.called); }); From 4b6787059fd4854e1ec3b8f50c1f626b95823cd9 Mon Sep 17 00:00:00 2001 From: annthurium Date: Fri, 1 Mar 2019 14:24:40 -0800 Subject: [PATCH 2191/4053] add test for footer in `PullRequestDetailView` --- test/fixtures/props/issueish-pane-props.js | 1 + test/views/pr-detail-view.test.js | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/test/fixtures/props/issueish-pane-props.js b/test/fixtures/props/issueish-pane-props.js index cabc7e4140..4fd32e976d 100644 --- a/test/fixtures/props/issueish-pane-props.js +++ b/test/fixtures/props/issueish-pane-props.js @@ -171,6 +171,7 @@ export function pullRequestDetailViewProps(opts, overrides = {}) { switchToIssueish: () => {}, destroy: () => {}, openCommit: () => {}, + openReviews: () => {}, // atom env props workspace: {}, diff --git a/test/views/pr-detail-view.test.js b/test/views/pr-detail-view.test.js index 62364fe02d..99f7ec1e8f 100644 --- a/test/views/pr-detail-view.test.js +++ b/test/views/pr-detail-view.test.js @@ -64,6 +64,14 @@ describe('PullRequestDetailView', function() { assert.strictEqual(wrapper.find('.github-IssueishDetailView-headRefName').text(), headRefName); }); + it('renders footer and passes openReviews prop through', function() { + const wrapper = shallow(buildApp()); + const footer = wrapper.find('ReviewsFooterView'); + assert.lengthOf(footer, 1); + + assert.strictEqual(footer.prop('openReviews'), wrapper.instance().props.openReviews); + }); + it('renders tabs', function() { const pullRequestCommitCount = 11; const pullRequestChangedFileCount = 22; From c57862952da435a95bdec0b1e854fb62b9674c0b Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 1 Mar 2019 23:32:32 +0000 Subject: [PATCH 2192/4053] chore(package): update sinon to version 7.2.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 056387db25..2b775c3bc5 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "nyc": "13.3.0", "relay-compiler": "3.0.0", "semver": "5.6.0", - "sinon": "7.2.5", + "sinon": "7.2.6", "test-until": "1.1.1" }, "consumedServices": { From a141469609c7ceaf1cf630775e8bf77ff880c351 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 1 Mar 2019 23:32:36 +0000 Subject: [PATCH 2193/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3a33c7b5a1..1ee071b868 100644 --- a/package-lock.json +++ b/package-lock.json @@ -864,21 +864,22 @@ "dev": true }, "@sinonjs/commons": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", - "integrity": "sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.1.tgz", + "integrity": "sha512-rgmZk5CrBGAMATk0HlHOFvo8V44/r+On6cKS80tqid0Eljd+fFBWBOXZp9H2/EB3faxdNdzXTx6QZIKLkbJ7mA==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/formatio": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz", - "integrity": "sha512-ZAR2bPHOl4Xg6eklUGpsdiIJ4+J1SNag1DHHrG/73Uz/nVwXqjgUtRPLoS+aVyieN9cSbc0E4LsU984tWcDyNg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.0.tgz", + "integrity": "sha512-hskkZG4qB0HgsxrPUlnk2EiIyBwntM+ETIxCha/gidl172MCfdosNezB5706ciS5P2VhueM7MoACWwMc4A4gMQ==", "dev": true, "requires": { - "@sinonjs/samsam": "^2 || ^3" + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" } }, "@sinonjs/samsam": { @@ -7453,13 +7454,13 @@ } }, "sinon": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.5.tgz", - "integrity": "sha512-1c2KK6g5NQr9XNYCEcUbeFtBpKZD1FXEw0VX7gNhWUBtkchguT2lNdS7XmS7y64OpQWfSNeeV/f8py3NNcQ63Q==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.6.tgz", + "integrity": "sha512-n5r5wZ/VyrP7oAJpGGGjSxvw2Yy8T+qdGjDBltK01KBqB33O60V/xcG0Hgqm6SbJEIyCGuICpaXg9uFzY5DOYw==", "dev": true, "requires": { "@sinonjs/commons": "^1.3.0", - "@sinonjs/formatio": "^3.1.0", + "@sinonjs/formatio": "^3.2.0", "@sinonjs/samsam": "^3.2.0", "diff": "^3.5.0", "lolex": "^3.1.0", From 46e80515ac5d4537fb6193bc7b80d775f5434a54 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 2 Mar 2019 06:46:22 +0000 Subject: [PATCH 2194/4053] chore(package): update eslint to version 5.15.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2b775c3bc5..abdf1fe47e 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "electron-mksnapshot": "~2.0", "enzyme": "3.8.0", "enzyme-adapter-react-16": "1.7.1", - "eslint": "5.14.1", + "eslint": "5.15.0", "eslint-config-fbjs-opensource": "1.0.0", "eslint-plugin-jsx-a11y": "6.2.1", "globby": "9.0.0", From 7adad21fd548eb317383827021545a6898ce5bf5 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 2 Mar 2019 06:46:26 +0000 Subject: [PATCH 2195/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1ee071b868..31fb13a08c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2511,9 +2511,9 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.14.1.tgz", - "integrity": "sha512-CyUMbmsjxedx8B0mr79mNOqetvkbij/zrXnFeK2zc3pGRn3/tibjiNAv/3UxFEyfMDjh+ZqTrJrEGBFiGfD5Og==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.15.0.tgz", + "integrity": "sha512-xwG7SS5JLeqkiR3iOmVgtF8Y6xPdtr6AAsN6ph7Q6R/fv+3UlKYoika8SmNzmb35qdRF+RfTY35kMEdtbi+9wg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -2522,7 +2522,7 @@ "cross-spawn": "^6.0.5", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^4.0.0", + "eslint-scope": "^4.0.2", "eslint-utils": "^1.3.1", "eslint-visitor-keys": "^1.0.0", "espree": "^5.0.1", @@ -2682,9 +2682,9 @@ } }, "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.2.tgz", + "integrity": "sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg==", "dev": true, "requires": { "esrecurse": "^4.1.0", From baaeb402d72c99ec63fcd3484e6791531d31f444 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 3 Mar 2019 13:22:09 +0000 Subject: [PATCH 2196/4053] chore(package): update globby to version 9.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index abdf1fe47e..3550e4faf9 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "eslint": "5.15.0", "eslint-config-fbjs-opensource": "1.0.0", "eslint-plugin-jsx-a11y": "6.2.1", - "globby": "9.0.0", + "globby": "9.1.0", "hock": "1.3.3", "lodash.isequalwith": "4.4.0", "mkdirp": "0.5.1", From 6ac3d653a1c84977d769caf4c6ef0e49f93d8c69 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 3 Mar 2019 13:22:12 +0000 Subject: [PATCH 2197/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 31fb13a08c..84dc7ce21d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -899,6 +899,29 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, "@types/node": { "version": "11.9.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.5.tgz", @@ -3374,11 +3397,12 @@ "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" }, "globby": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.0.0.tgz", - "integrity": "sha512-q0qiO/p1w/yJ0hk8V9x1UXlgsXUxlGd0AHUOXZVXBO6aznDtpx7M8D1kBrCAItoPm+4l8r6ATXV1JpjY2SBQOw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.1.0.tgz", + "integrity": "sha512-VtYjhHr7ncls724Of5W6Kaahz0ag7dB4G62/2HsN+xEKG6SrPzM1AJMerGxQTwJGnN9reeyxdvXbuZYpfssCvg==", "dev": true, "requires": { + "@types/glob": "^7.1.1", "array-union": "^1.0.2", "dir-glob": "^2.2.1", "fast-glob": "^2.2.6", From fc95d2ae5daa42bb8dabc3ff4577f7c1a1ab7f9d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 4 Mar 2019 12:59:45 +0000 Subject: [PATCH 2198/4053] chore(package): update sinon to version 7.2.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3550e4faf9..b320382bbc 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "nyc": "13.3.0", "relay-compiler": "3.0.0", "semver": "5.6.0", - "sinon": "7.2.6", + "sinon": "7.2.7", "test-until": "1.1.1" }, "consumedServices": { From 289ea022e66fbdd2bd06ffb2fe986ebd4465607b Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 4 Mar 2019 12:59:52 +0000 Subject: [PATCH 2199/4053] chore(package): update lockfile package-lock.json --- package-lock.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 84dc7ce21d..fb82b96122 100644 --- a/package-lock.json +++ b/package-lock.json @@ -873,9 +873,9 @@ } }, "@sinonjs/formatio": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.0.tgz", - "integrity": "sha512-hskkZG4qB0HgsxrPUlnk2EiIyBwntM+ETIxCha/gidl172MCfdosNezB5706ciS5P2VhueM7MoACWwMc4A4gMQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", "dev": true, "requires": { "@sinonjs/commons": "^1", @@ -7478,13 +7478,13 @@ } }, "sinon": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.6.tgz", - "integrity": "sha512-n5r5wZ/VyrP7oAJpGGGjSxvw2Yy8T+qdGjDBltK01KBqB33O60V/xcG0Hgqm6SbJEIyCGuICpaXg9uFzY5DOYw==", + "version": "7.2.7", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.7.tgz", + "integrity": "sha512-rlrre9F80pIQr3M36gOdoCEWzFAMDgHYD8+tocqOw+Zw9OZ8F84a80Ds69eZfcjnzDqqG88ulFld0oin/6rG/g==", "dev": true, "requires": { - "@sinonjs/commons": "^1.3.0", - "@sinonjs/formatio": "^3.2.0", + "@sinonjs/commons": "^1.3.1", + "@sinonjs/formatio": "^3.2.1", "@sinonjs/samsam": "^3.2.0", "diff": "^3.5.0", "lolex": "^3.1.0", From 1935f455904ed552a0d5365e3c7eca1120aa2168 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 4 Mar 2019 10:23:50 -0500 Subject: [PATCH 2200/4053] :art: delete a newline --- lib/containers/pr-review-comments-container.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/containers/pr-review-comments-container.js b/lib/containers/pr-review-comments-container.js index 16051026bd..03749fee74 100644 --- a/lib/containers/pr-review-comments-container.js +++ b/lib/containers/pr-review-comments-container.js @@ -5,7 +5,6 @@ import React from 'react'; import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../helpers'; export class BarePullRequestReviewCommentsContainer extends React.Component { - static propTypes = { collectComments: PropTypes.func.isRequired, review: PropTypes.shape({ From 9bec06acb195214e25115d5e13c1661a567293f5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 4 Mar 2019 10:26:53 -0500 Subject: [PATCH 2201/4053] PullRequestReviewsController calls a render prop with each comment thread This sets us up to render a PullRequestCommentThreadView from MultiFilePatchView and a different view component from the ReviewsItem. --- lib/controllers/pr-reviews-controller.js | 16 ++-- .../controllers/pr-reviews-controller.test.js | 93 ++++++++++++++++--- 2 files changed, 88 insertions(+), 21 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index fdaf09c434..94adeed5a5 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import {RelayConnectionPropType} from '../prop-types'; import PullRequestReviewCommentsContainer from '../containers/pr-review-comments-container'; -import PullRequestReviewCommentsView from '../views/pr-review-comments-view'; import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../helpers'; export default class PullRequestReviewsController extends React.Component { @@ -18,8 +17,7 @@ export default class PullRequestReviewsController extends React.Component { PropTypes.object, ), }), - getBufferRowForDiffPosition: PropTypes.func.isRequired, - isPatchVisible: PropTypes.func.isRequired, + children: PropTypes.func.isRequired, } constructor(props) { @@ -77,12 +75,18 @@ export default class PullRequestReviewsController extends React.Component { * Upon fetching new comments, the `collectComments` method is called with all comments fetched for that review. * Ultimately we want to group comments based on the root comment they are replies to. * `renderCommentFetchingContainers` only fetches data and doesn't render any user visible DOM elements. - * `PullRequestReviewCommentsView` renders the aggregated comment thread data. - * */ + * The child render prop is called with each comment thread. + */ return ( {this.renderCommentFetchingContainers()} - + {commentThreads.map(commentThread => { + return ( + + {this.props.children(commentThread)} + + ); + })} ); } diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index 60e2dbf2be..e90c1d6ca9 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -1,17 +1,23 @@ import React from 'react'; import {shallow} from 'enzyme'; import {reviewBuilder} from '../builder/pr'; +import {inspect} from 'util'; import PullRequestReviewsController from '../../lib/controllers/pr-reviews-controller'; +import PullRequestReviewCommentsContainer from '../../lib/containers/pr-review-comments-container'; import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../../lib/helpers'; +function SomeView(props) { + return
    {inspect(props, {depth: 3})}
    ; +} + describe('PullRequestReviewsController', function() { - function buildApp(opts, overrideProps = {}) { + function buildApp(opts, overrideProps = {}, childFn = SomeView) { const o = { - relayHasMore: () => { return false; }, + relayHasMore: () => false, relayLoadMore: () => {}, - relayIsLoading: () => { return false; }, + relayIsLoading: () => false, reviewSpecs: [], reviewStartCursor: 0, ...opts, @@ -40,14 +46,15 @@ describe('PullRequestReviewsController', function() { loadMore: o.relayLoadMore, isLoading: o.relayIsLoading, }, - - switchToIssueish: () => {}, - getBufferRowForDiffPosition: () => {}, - isPatchVisible: () => true, pullRequest: {reviews}, ...overrideProps, }; - return ; + + return ( + + {childFn} + + ); } it('returns null if props.pullRequest is falsy', function() { const wrapper = shallow(buildApp({}, {pullRequest: null})); @@ -72,17 +79,73 @@ describe('PullRequestReviewsController', function() { assert.strictEqual(containers.at(1).prop('review').id, review2.id); }); - it('renders a PullRequestReviewCommentsView and passes props through', function() { - const review1 = reviewBuilder().build(); - const review2 = reviewBuilder().build(); + it('calls the child render prop for each comment thread', function() { + const review1 = reviewBuilder() + .id(10) + .submittedAt('2019-01-01T10:00:00Z') + .addComment(c => c.id(1)) + .build(); + const review2 = reviewBuilder() + .id(20) + .submittedAt('2019-01-01T11:00:00Z') + .addComment(c => c.id(2)) + .addComment(c => c.id(3).replyTo(1)) + .addComment(c => c.id(4)) + .build(); + + function ChildComponent() { + return
    ; + } const reviewSpecs = [review1, review2]; const passThroughProp = 'I only exist for the children'; - const wrapper = shallow(buildApp({reviewSpecs}, {passThroughProp})); - const view = wrapper.find('PullRequestCommentsView'); - assert.strictEqual(view.length, 1); - assert.strictEqual(wrapper.instance().props.passThroughProp, view.prop('passThroughProp')); + const wrapper = shallow(buildApp({reviewSpecs}, {}, thread => { + return ; + })); + + assert.isFalse(wrapper.exists('ChildComponent')); + + wrapper.find(PullRequestReviewCommentsContainer).at(0).prop('collectComments')({ + reviewId: review1.id, + submittedAt: review1.submittedAt, + comments: review1.comments, + fetchingMoreComments: false, + }); + + assert.lengthOf(wrapper.find('ChildComponent'), 1); + assert.strictEqual(wrapper.find('ChildComponent').at(0).prop('passThroughProp'), passThroughProp); + assert.deepEqual(wrapper.find('ChildComponent').at(0).prop('thread'), { + rootCommentId: '1', + comments: [review1.comments.edges[0].node], + }); + + wrapper.find(PullRequestReviewCommentsContainer).at(1).prop('collectComments')({ + reviewId: review2.id, + submittedAt: review2.submittedAt, + comments: review2.comments, + fetchingMoreComments: false, + }); + + assert.lengthOf(wrapper.find('ChildComponent'), 3); + + assert.strictEqual(wrapper.find('ChildComponent').at(0).prop('passThroughProp'), passThroughProp); + assert.deepEqual(wrapper.find('ChildComponent').at(0).prop('thread'), { + rootCommentId: '4', + comments: [review2.comments.edges[2].node], + }); + + assert.strictEqual(wrapper.find('ChildComponent').at(1).prop('passThroughProp'), passThroughProp); + assert.deepEqual(wrapper.find('ChildComponent').at(1).prop('thread'), { + rootCommentId: '2', + comments: [review2.comments.edges[0].node], + }); + + assert.strictEqual(wrapper.find('ChildComponent').at(2).prop('passThroughProp'), passThroughProp); + assert.deepEqual(wrapper.find('ChildComponent').at(2).prop('thread'), { + rootCommentId: '1', + comments: [review1.comments.edges[0].node, review2.comments.edges[1].node], + }); }); describe('collectComments', function() { From 30bc7ced9d0ced734b14d21d417afde4b61d0dd1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 4 Mar 2019 10:28:34 -0500 Subject: [PATCH 2202/4053] PullRequestReviewCommentsView > PullRequestReviewCommentThreadView Rework PullRequestReviewCommentsView to render a single comment thread at a time. Rename it to PullRequestReviewCommentThreadView which is a mouthful, but a more accurate description of what it does. --- ...ew.js => pr-review-comment-thread-view.js} | 63 +++++++++---------- ... => pr-review-comment-thread-view.test.js} | 51 +++++++-------- 2 files changed, 54 insertions(+), 60 deletions(-) rename lib/views/{pr-review-comments-view.js => pr-review-comment-thread-view.js} (63%) rename test/views/{pr-comments-view.test.js => pr-review-comment-thread-view.test.js} (75%) diff --git a/lib/views/pr-review-comments-view.js b/lib/views/pr-review-comment-thread-view.js similarity index 63% rename from lib/views/pr-review-comments-view.js rename to lib/views/pr-review-comment-thread-view.js index 636b88d4b1..232136dca4 100644 --- a/lib/views/pr-review-comments-view.js +++ b/lib/views/pr-review-comment-thread-view.js @@ -10,55 +10,51 @@ import Octicon from '../atom/octicon'; import GithubDotcomMarkdown from './github-dotcom-markdown'; import Timeago from './timeago'; -export default class PullRequestCommentsView extends React.Component { +export default class PullRequestReviewCommentThreadView extends React.Component { static propTypes = { - commentThreads: PropTypes.arrayOf(PropTypes.shape({ + commentThread: PropTypes.shape({ rootCommentId: PropTypes.string.isRequired, comments: PropTypes.arrayOf(PropTypes.object).isRequired, - })), + }), getBufferRowForDiffPosition: PropTypes.func.isRequired, switchToIssueish: PropTypes.func.isRequired, isPatchVisible: PropTypes.func.isRequired, } render() { - return [...this.props.commentThreads].map(({rootCommentId, comments}) => { - const rootComment = comments[0]; - if (!rootComment.position) { - return null; - } + const {rootCommentId, comments} = this.props.commentThread; + const rootComment = comments[0]; + if (!rootComment.position) { + return null; + } - // if file patch is collapsed or too large, do not render the comments - if (!this.props.isPatchVisible(rootComment.path)) { - return null; - } + // if file patch is collapsed or too large, do not render the comments + if (!this.props.isPatchVisible(rootComment.path)) { + return null; + } - const nativePath = toNativePathSep(rootComment.path); - const row = this.props.getBufferRowForDiffPosition(nativePath, rootComment.position); - const point = new Point(row, 0); - const range = new Range(point, point); - return ( - - - {comments.map(comment => { - return ( - - ); - })} - - - ); - }); + const nativePath = toNativePathSep(rootComment.path); + const row = this.props.getBufferRowForDiffPosition(nativePath, rootComment.position); + const point = new Point(row, 0); + const range = new Range(point, point); + return ( + + + {comments.map(comment => ( + + ))} + + + ); } } export class PullRequestCommentView extends React.Component { static propTypes = { - switchToIssueish: PropTypes.func.isRequired, comment: PropTypes.shape({ author: PropTypes.shape({ avatarUrl: PropTypes.string, @@ -69,6 +65,7 @@ export class PullRequestCommentView extends React.Component { createdAt: PropTypes.string.isRequired, isMinimized: PropTypes.bool.isRequired, }).isRequired, + switchToIssueish: PropTypes.func.isRequired, } render() { diff --git a/test/views/pr-comments-view.test.js b/test/views/pr-review-comment-thread-view.test.js similarity index 75% rename from test/views/pr-comments-view.test.js rename to test/views/pr-review-comment-thread-view.test.js index ff563441fc..331c294143 100644 --- a/test/views/pr-comments-view.test.js +++ b/test/views/pr-review-comment-thread-view.test.js @@ -3,26 +3,21 @@ import {shallow} from 'enzyme'; import {multiFilePatchBuilder} from '../builder/patch'; import {pullRequestBuilder} from '../builder/pr'; -import PullRequestCommentsView, {PullRequestCommentView} from '../../lib/views/pr-review-comments-view'; - -describe('PullRequestCommentsView', function() { - function buildApp(multiFilePatch, pullRequest, overrideProps) { - const relay = { - hasMore: () => {}, - loadMore: () => {}, - isLoading: () => {}, - }; +import PullRequestReviewCommentThreadView, {PullRequestCommentView} from '../../lib/views/pr-review-comment-thread-view'; + +describe('PullRequestReviewCommentThreadView', function() { + function buildApp(multiFilePatch, commentThread, overrideProps = {}) { return shallow( - true} + getBufferRowForDiffPosition={multiFilePatch.getBufferRowForDiffPosition} + commentThread={commentThread} switchToIssueish={() => {}} {...overrideProps} />, ); } + it('adjusts the position for comments after hunk headers', function() { const {multiFilePatch} = multiFilePatchBuilder() .addFilePatch(fp => { @@ -48,11 +43,14 @@ describe('PullRequestCommentsView', function() { }) .build(); - const wrapper = buildApp(multiFilePatch, pr, {}); + const wrapper0 = buildApp(multiFilePatch, pr.commentThreads[0]); + assert.deepEqual(wrapper0.find('Marker').prop('bufferRange').serialize(), [[1, 0], [1, 0]]); - assert.deepEqual(wrapper.find('Marker').at(0).prop('bufferRange').serialize(), [[1, 0], [1, 0]]); - assert.deepEqual(wrapper.find('Marker').at(1).prop('bufferRange').serialize(), [[12, 0], [12, 0]]); - assert.deepEqual(wrapper.find('Marker').at(2).prop('bufferRange').serialize(), [[20, 0], [20, 0]]); + const wrapper1 = buildApp(multiFilePatch, pr.commentThreads[1]); + assert.deepEqual(wrapper1.find('Marker').prop('bufferRange').serialize(), [[12, 0], [12, 0]]); + + const wrapper2 = buildApp(multiFilePatch, pr.commentThreads[2]); + assert.deepEqual(wrapper2.find('Marker').prop('bufferRange').serialize(), [[20, 0], [20, 0]]); }); it('does not render comment if patch is too large or collapsed', function() { @@ -64,9 +62,8 @@ describe('PullRequestCommentsView', function() { }) .build(); - const wrapper = buildApp(multiFilePatch, pr, {isPatchVisible: () => { return false; }}); - const comments = wrapper.find('PullRequestCommentView'); - assert.lengthOf(comments, 0); + const wrapper = buildApp(multiFilePatch, pr.commentThreads[0], {isPatchVisible: () => false}); + assert.isFalse(wrapper.exists('PullRequestCommentView')); }); it('does not render comment if position is null', function() { @@ -88,11 +85,11 @@ describe('PullRequestCommentsView', function() { }) .build(); - const wrapper = buildApp(multiFilePatch, pr, {}); + const wrapper = buildApp(multiFilePatch, pr.commentThreads[0]); const comments = wrapper.find('PullRequestCommentView'); assert.lengthOf(comments, 1); - assert.strictEqual(comments.at(0).prop('comment').body, 'one'); + assert.strictEqual(comments.prop('comment').body, 'one'); }); }); @@ -132,7 +129,7 @@ describe('PullRequestCommentView', function() { assert.strictEqual(avatar.getElement('src').props.src, avatarUrl); assert.strictEqual(avatar.getElement('alt').props.alt, login); - assert.isTrue(wrapper.text().includes(`${login} commented`)); + assert.include(wrapper.text(), `${login} commented`); const a = wrapper.find('.github-PrComment-timeAgo'); assert.strictEqual(a.getElement('href').props.href, commentUrl); @@ -147,12 +144,12 @@ describe('PullRequestCommentView', function() { it('contains the text `someone commented` for null authors', function() { const wrapper = shallow(buildApp({author: null})); - assert.isTrue(wrapper.text().includes('someone commented')); + assert.include(wrapper.text(), 'someone commented'); }); it('hides minimized comment', function() { const wrapper = shallow(buildApp({isMinimized: true})); - assert.isTrue(wrapper.find('.github-PrComment-hidden').exists()); - assert.isFalse(wrapper.find('.github-PrComment-header').exists()); + assert.isTrue(wrapper.exists('.github-PrComment-hidden')); + assert.isFalse(wrapper.exists('.github-PrComment-header')); }); }); From 9090283a478d6d61c613a0585c238d10d872b86d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 4 Mar 2019 10:31:16 -0500 Subject: [PATCH 2203/4053] Pass a render prop to PullRequestReviewsContainer Preserve existing review comments behavior in MultiFilePatchView. --- lib/views/multi-file-patch-view.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 76fa7e199f..edc80adb59 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -16,7 +16,8 @@ import Commands, {Command} from '../atom/commands'; import FilePatchHeaderView from './file-patch-header-view'; import FilePatchMetaView from './file-patch-meta-view'; import HunkHeaderView from './hunk-header-view'; -import PullRequestsReviewsContainer from '../containers/pr-reviews-container'; +import PullRequestReviewsContainer from '../containers/pr-reviews-container'; +import PullRequestReviewCommentThreadView from './pr-review-comment-thread-view'; import RefHolder from '../models/ref-holder'; import ChangedFileItem from '../items/changed-file-item'; import CommitDetailItem from '../items/commit-detail-item'; @@ -385,13 +386,16 @@ export default class MultiFilePatchView extends React.Component { // "forceRerender" ensures that the PullRequestCommentsView re-renders each time that the MultiFilePatchView does. // It doesn't re-query for reviews, but it does re-check patch visibility. return ( - + + {commentThread => ( + + )} + ); } else { return null; From eb70cc00cee5274b61b1e3954c88b18c690c99d8 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 4 Mar 2019 15:52:05 +0100 Subject: [PATCH 2204/4053] port over view styles from prototype --- lib/views/reviews-view.js | 394 +++++++++++++++++++++++++++++++++++++- styles/review.less | 388 +++++++++++++++++++++++++++++++++++++ 2 files changed, 779 insertions(+), 3 deletions(-) create mode 100644 styles/review.less diff --git a/lib/views/reviews-view.js b/lib/views/reviews-view.js index ce8ec6b45d..309581d925 100644 --- a/lib/views/reviews-view.js +++ b/lib/views/reviews-view.js @@ -4,9 +4,397 @@ import {inspect} from 'util'; export default class ReviewsView extends React.Component { render() { return ( -
    -

    HELL YEAH PR REVIEW COMMENTS

    -
    {inspect(this.props, {depth: 4})}
    +
    + + {/* Review Summaries ------------------------------------------ */} + +
    + +

    Reviews

    +
    +
    + +
    +
    + + + vanessayuenn + approved these changes + 18 minutes ago +
    +
    + looks good! 🚢 +
    +
    + +
    +
    + + + simurai + requested changes + 2 hours ago +
    +
    + This is a great addition. Before merging we might should rethink the position of the icon. +
    +
    + +
    +
    + + + annthurium + commented + 4 hours ago +
    +
    + looking good so far -- just found a few nits to address. +
    +
    + +
    +
    + + + + {/* Review Comments ------------------------------------------ */} + +
    + +

    Review comments

    + + Resolved 1 of 7 + + +
    +
    + +
    + + + lib/controllers/ + commit-detail-controller.js + 19 + + 4h + + +
    +                
    { ' messageCollapsible: this.props.commit.isBodyLong(),' }
    +
    { ' messageOpen: !this.props.commit.isBodyLong(),' }
    +
    { ' };' }
    +
    { ' }' }
    +
    +
    +
    +
    + + annthurium + 4 hours ago +
    +
    mind adding the missing semicolon here?
    +
    + +
    + + + +
    +
    +
    + +
    +
    + +
    + + + lib/controllers/ + pr-reviews-controller.js + 22 + + 4h + + +
    +                
    { ' ),' }
    +
    { ' }),' }
    +
    { ' getBufferRowForDiffPosition: PropTypes.func.isRequired,' }
    +
    { ' isPatchTooLargeOrCollapsed: PropTypes.func.isRequired,' }
    +
    +
    +
    +
    + + annthurium + 4 hours ago +
    +
    this is a really long name -- can you come up with something more concise?
    +
    +
    +
    + + vanessayuenn + 18 minutes ago +
    +
    how about just isPatchCollapsed?
    +
    + +
    + + + +
    +
    +
    + +
    +
    + +
    + + + lib/models/patch/ + builder.js + 12 + + 1h + + +
    +                
    { 'export const DEFAULT_OPTIONS = {' }
    +
    { ' // Number of lines after which we consider the diff "large"' }
    +
    { ' // TODO: Set this based on performance measurements' }
    +
    { ' largeDiffThreshold: 100,' }
    +
    +
    +
    +
    + + annthurium + 1 hour ago +
    +
    @simurai: how many lines do you think constitutes a large diff? Not just from a performance perspective, but from a user experience perspective. Like how many lines is disruptive to a user when they're trying to read, because often large diffs are the result of auto generated code.
    +
    +
    +
    + + simurai + 1 hour ago +
    +
    Hmmm.. will large diffs be collapsed by default or there is a "load" button? Maybe if the diff is so large that it fills the whole scroll height. Then I can uncollapse only if I'm really interested in that file. 100 seems fine. 👍
    +
    +
    +
    + + annthurium + 1 hour ago +
    + +
    +
    +
    + + vanessayuenn + 25 minutes ago +
    +
    mm we were using 1000 lines as the threshold before, but that's for performance reasons. 100 does seem a bit small though considering it's counting both deleted and added lines. 🤔
    +
    + 300 seems a bit more reasonable, but still an arbitrary number.
    +
    + +
    + + + +
    +
    +
    + +
    +
    + +
    + + + lib/models/patch/ + multi-file-patch.js + 359 + + 4h + + +
    +                
    { ' }' }
    +
    { ' }' }
    +
    { '' }
    +
    { ' isPatchTooLargeOrCollapsed = filePatchPath => {' }
    +
    +
    +
    +
    + + annthurium + 4 hours ago +
    +
    same as above - this name is too long and the line length linters are gonna be unhappy.
    +
    + +
    + + + +
    +
    +
    + +
    +
    + +
    + + + lib/views/ + file-patch-header-view.js + 52 + + 2h + + +
    +                
    { '
    ' }
    +
    { ' ' }
    +
    { ' {this.renderTitle()}' }
    +
    { ' {this.renderCollapseButton()}' }
    +
    +
    +
    +
    + + simurai + 2 hours ago +
    +
    Should we move the chevron icon to the left? So that the position doesn't jump when there are more buttons. Alternative would be to move it all the way to the right.
    +
    + +
    + + + +
    +
    +
    + +
    +
    + +
    + + + test/views/ + pr-comments-view.test.js + 17 + + 4h + + +
    +                
    { ' };' }
    +
    { ' return shallow(' }
    +
    { ' +
    { ' isPatchTooLargeOrCollapsed={sinon.stub().returns(false)}' }
    +
    +
    +
    +
    + + annthurium + 4 hours ago +
    +
    again, this variable name is too long
    +
    + +
    + + + +
    +
    +
    + +
    +
    + +
    + + + lib/models/patch/ + builder.js + 280 + + 4h + + +
    +                
    { ' /*' }
    +
    { ' * Construct a String containing diagnostic information about the internal state of this FilePatch.' }
    +
    { ' */' }
    +
    { ' inspect(opts = {}) {' }
    +
    +
    +
    +
    + + annthurium + 4 hours ago +
    +
    nice! This is going to be so useful when we are trying to debug the marker layer on a text buffer.
    +
    + +
    + + + +
    +
    +
    + annthurium marked this as resolved + +
    +
    + +
    +
    +
    ); } diff --git a/styles/review.less b/styles/review.less new file mode 100644 index 0000000000..3730283ef5 --- /dev/null +++ b/styles/review.less @@ -0,0 +1,388 @@ +@import "variables"; + +@avatar-size: 16px; +@font-size-default: @font-size; +@font-size-body: @font-size + 1px; +@nav-size: 2.5em; +@background-color: mix(@base-background-color, @tool-panel-background-color, 66%); + + +// PR ------------------------------------ + +.github-Pr { + flex: 1; + overflow-x: auto; +} + + +// Reviews ---------------------------------------------- + +.github-Reviews { + flex: 1; + overflow-y: auto; + + &-section { + border-bottom: 1px solid @base-border-color; + } + + &-header { + display: flex; + align-items: center; + padding: @component-padding; + cursor: default; + } + + &-title { + flex: 1; + font-size: 1.15em; + margin: 0; + } + + &-count { + color: @text-color-subtle; + } + + &-countNr { + color: @text-color-highlight; + } + + &-progessBar { + width: 5em; + margin-left: @component-padding; + } + + &-container { + font-size: @font-size-default; + padding: @component-padding; + padding-top: 0; + + &.is-resolved { + padding-top: @component-padding; + border-top: 1px solid @base-border-color; + } + } +} + + +// Review Summaries ------------------------------------------ + +.github-ReviewSummary { + padding: @component-padding; + border: 1px solid @base-border-color; + border-radius: @component-border-radius; + background-color: @background-color; + + & + & { + margin-top: @component-padding; + } + + &-header { + display: flex; + } + + &-icon { + vertical-align: -3px; + &.icon-check { + color: @text-color-success; + } + &.icon-alert { + color: @text-color-warning; + } + &.icon-comment { + color: @text-color-subtle; + } + } + + &-avatar { + margin-right: .5em; + width: @avatar-size; + height: @avatar-size; + border-radius: @component-border-radius; + image-rendering: -webkit-optimize-contrast; + } + + &-username { + margin-right: .3em; + font-weight: 500; + } + + &-type { + color: @text-color-subtle; + } + + &-timeAgo { + margin-left: auto; + color: @text-color-subtle; + } + + &-comment { + position: relative; + margin-top: @component-padding/2; + margin-left: 7px; + padding-left: 12px; + font-size: @font-size-body; + border-left: 2px solid @base-border-color; + } +} + + +// Review Comments ---------------------------------------------- + +.github-Review { + position: relative; + border: 1px solid @base-border-color; + border-radius: @component-border-radius; + background-color: @background-color; + + & + & { + margin-top: @component-padding; + } + + // Header ------------------ + + &-reference { + display: flex; + align-items: center; + padding-left: @component-padding; + height: @nav-size; + cursor: default; + + &::-webkit-details-marker { + margin-right: @component-padding/1.5; + } + + .is-resolved & { + &::-webkit-details-marker { + display: none; + } + } + } + + &-resolvedIcon { + display: none; + + .is-resolved & { + display: inline-block; + } + + &:before { + font-size: 12px; + width: 15px; // magic to match the triangle + margin-right: 0; + } + } + + &-path { + color: @text-color-subtle; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + pointer-events: none; + } + + &-file { + color: @text-color; + margin-right: @component-padding/2; + font-weight: 500; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + pointer-events: none; + } + + &-lineNr { + color: @text-color-subtle; + margin-right: @component-padding/2; + } + + &-referenceAvatar { + margin-left: auto; + margin-right: @component-padding/2; + width: @avatar-size; + height: @avatar-size; + border-radius: @component-border-radius; + image-rendering: -webkit-optimize-contrast; + + .github-Review[open] & { + display: none; + } + } + + &-referenceTimeAgo { + color: @text-color-subtle; + margin-right: @component-padding/2; + .github-Review[open] & { + display: none; + } + } + + &-nav { + display: none; + margin-left: auto; + .github-Review[open] & { + display: initial; + } + } + &-navButton { + width: @nav-size; + height: @nav-size; + padding: 0; + border: none; + border-left: 1px solid @base-border-color; + background-color: transparent; + cursor: default; + + &:hover { + background-color: @background-color-highlight; + } + &:active { + background-color: @background-color-selected; + } + + &:before { + width: auto; + margin-right: 0; + } + } + + + // Diff ------------------ + + &-diff { + padding: 0; + color: @text-color; + font-family: var(--editor-font-family); + border-top: 1px solid @base-border-color; + border-bottom: 1px solid @base-border-color; + border-radius: 0; + background: @base-background-color; + } + + &-diffLine { + padding: 0 @component-padding/2; + line-height: 1.75; + + &:last-child { + font-weight: 500; + } + + &::before { + content: " "; + margin-right: .5em; + } + + &.is-added { + color: mix(@text-color-success, @text-color-highlight, 25%); + background-color: mix(@background-color-success, @base-background-color, 20%); + &::before { + content: "+"; + } + } + &.is-deleted { + color: mix(@text-color-error, @text-color-highlight, 25%); + background-color: mix(@background-color-error, @base-background-color, 20%); + &::before { + content: "-"; + } + } + } + + &-diffLineIcon { + margin-right: .5em; + } + + + // Comments ------------------ + + &-comments { + padding: @component-padding; + padding-left: @component-padding + @avatar-size + 5px; // space for avatar + } + + &-comment { + margin-bottom: @component-padding*1.5; + } + + &-header { + display: flex; + align-items: center; + margin-bottom: @component-padding/4; + } + + &-avatar { + position: absolute; + left: @component-padding; + margin-right: .5em; + width: @avatar-size; + height: @avatar-size; + border-radius: @component-border-radius; + image-rendering: -webkit-optimize-contrast; + } + + &-username { + margin-right: .5em; + font-weight: 500; + } + + &-timeAgo { + color: @text-color-subtle; + } + + &-text { + font-size: @font-size-body; + + a { + font-weight: 500; + } + } + + &-reply { + margin-top: @component-padding; + } + + &-replyInput { + width: 100%; + height: 2.125em; // one line + min-height: 2.125em; + resize: vertical; + + &:focus { + height: 3.75em; // two lines + } + + &:focus + .github-ReviewComment-replyButton { + display: block; + } + } + + &-replyButton { + display: none; + margin-top: @component-padding; + } + + + // Footer ------------------ + + &-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: @component-padding; + border-top: 1px solid @base-border-color; + } + + &-resolveText { + margin-right: @component-padding; + color: @text-color-subtle; + } + + &-resolveButton { + margin-left: auto; + &.btn.icon { + &:before { + font-size: 14px; + } + } + } + +} From 4ab61052e0a7bff6b7cfa4fe71c20c0369a93582 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Mon, 4 Mar 2019 16:36:59 +0100 Subject: [PATCH 2205/4053] make it more programmatic --- lib/views/reviews-view.js | 695 ++++++++++++++++++-------------------- 1 file changed, 337 insertions(+), 358 deletions(-) diff --git a/lib/views/reviews-view.js b/lib/views/reviews-view.js index 309581d925..22a6bc425e 100644 --- a/lib/views/reviews-view.js +++ b/lib/views/reviews-view.js @@ -2,21 +2,19 @@ import React from 'react'; import {inspect} from 'util'; export default class ReviewsView extends React.Component { - render() { - return ( -
    - - {/* Review Summaries ------------------------------------------ */} - -
    - -

    Reviews

    -
    -
    + renderReviewSummaries() { + return ( +
    + +

    Reviews

    +
    +
    + {[1,2].map(({something}) => (
    - + {/*icon-check: approved, icon-comment: commented, icon-alert: requested changes */} + vanessayuenn approved these changes @@ -26,375 +24,356 @@ export default class ReviewsView extends React.Component { looks good! 🚢
    + ))} + + + ); + } -
    -
    - - - simurai - requested changes - 2 hours ago -
    -
    - This is a great addition. Before merging we might should rethink the position of the icon. -
    -
    + renderReviewComments() { + return ( +
    + +

    Review comments

    + + Resolved 1 of 7 + + +
    +
    -
    -
    - - - annthurium - commented - 4 hours ago -
    -
    - looking good so far -- just found a few nits to address. +
    + + + lib/controllers/ + commit-detail-controller.js + 19 + + 4h + + +
    +              
    { ' messageCollapsible: this.props.commit.isBodyLong(),' }
    +
    { ' messageOpen: !this.props.commit.isBodyLong(),' }
    +
    { ' };' }
    +
    { ' }' }
    +
    +
    +
    +
    + + annthurium + 4 hours ago +
    +
    mind adding the missing semicolon here?
    -
    - -
    -
    - +
    + + + +
    + +
    + +
    + - {/* Review Comments ------------------------------------------ */} - -
    - -

    Review comments

    - - Resolved 1 of 7 - - -
    -
    - -
    - - - lib/controllers/ - commit-detail-controller.js - 19 - - 4h - - -
    -                
    { ' messageCollapsible: this.props.commit.isBodyLong(),' }
    -
    { ' messageOpen: !this.props.commit.isBodyLong(),' }
    -
    { ' };' }
    -
    { ' }' }
    -
    -
    -
    -
    - - annthurium - 4 hours ago -
    -
    mind adding the missing semicolon here?
    -
    - -
    +
    + + + lib/controllers/ + pr-reviews-controller.js + 22 + + 4h + + +
    +              
    { ' ),' }
    +
    { ' }),' }
    +
    { ' getBufferRowForDiffPosition: PropTypes.func.isRequired,' }
    +
    { ' isPatchTooLargeOrCollapsed: PropTypes.func.isRequired,' }
    +
    +
    +
    +
    - - -
    -
    -
    - -
    -
    + annthurium + 4 hours ago + +
    this is a really long name -- can you come up with something more concise?
    +
    +
    +
    + + vanessayuenn + 18 minutes ago +
    +
    how about just isPatchCollapsed?
    +
    -
    - - - lib/controllers/ - pr-reviews-controller.js - 22 - - 4h - - -
    -                
    { ' ),' }
    -
    { ' }),' }
    -
    { ' getBufferRowForDiffPosition: PropTypes.func.isRequired,' }
    -
    { ' isPatchTooLargeOrCollapsed: PropTypes.func.isRequired,' }
    -
    -
    -
    -
    - - annthurium - 4 hours ago -
    -
    this is a really long name -- can you come up with something more concise?
    -
    -
    -
    - - vanessayuenn - 18 minutes ago -
    -
    how about just isPatchCollapsed?
    -
    +
    + + + +
    +
    +
    + +
    +
    -
    +
    + + + lib/models/patch/ + builder.js + 12 + + 1h + + +
    +              
    { 'export const DEFAULT_OPTIONS = {' }
    +
    { ' // Number of lines after which we consider the diff "large"' }
    +
    { ' // TODO: Set this based on performance measurements' }
    +
    { ' largeDiffThreshold: 100,' }
    +
    +
    +
    +
    - - -
    -
    -
    - -
    -
    - -
    - - - lib/models/patch/ - builder.js - 12 - - 1h - - -
    -                
    { 'export const DEFAULT_OPTIONS = {' }
    -
    { ' // Number of lines after which we consider the diff "large"' }
    -
    { ' // TODO: Set this based on performance measurements' }
    -
    { ' largeDiffThreshold: 100,' }
    -
    -
    -
    -
    - - annthurium - 1 hour ago -
    -
    @simurai: how many lines do you think constitutes a large diff? Not just from a performance perspective, but from a user experience perspective. Like how many lines is disruptive to a user when they're trying to read, because often large diffs are the result of auto generated code.
    -
    -
    -
    - - simurai - 1 hour ago -
    -
    Hmmm.. will large diffs be collapsed by default or there is a "load" button? Maybe if the diff is so large that it fills the whole scroll height. Then I can uncollapse only if I'm really interested in that file. 100 seems fine. 👍
    -
    -
    -
    - - annthurium - 1 hour ago -
    - -
    -
    -
    - - vanessayuenn - 25 minutes ago -
    -
    mm we were using 1000 lines as the threshold before, but that's for performance reasons. 100 does seem a bit small though considering it's counting both deleted and added lines. 🤔
    -
    - 300 seems a bit more reasonable, but still an arbitrary number.
    -
    - -
    + annthurium + 1 hour ago + +
    @simurai: how many lines do you think constitutes a large diff? Not just from a performance perspective, but from a user experience perspective. Like how many lines is disruptive to a user when they're trying to read, because often large diffs are the result of auto generated code.
    +
    +
    +
    + + simurai + 1 hour ago +
    +
    Hmmm.. will large diffs be collapsed by default or there is a "load" button? Maybe if the diff is so large that it fills the whole scroll height. Then I can uncollapse only if I'm really interested in that file. 100 seems fine. 👍
    +
    +
    +
    - - -
    -
    -
    - -
    -
    + annthurium + 1 hour ago + + +
    +
    +
    + + vanessayuenn + 25 minutes ago +
    +
    mm we were using 1000 lines as the threshold before, but that's for performance reasons. 100 does seem a bit small though considering it's counting both deleted and added lines. 🤔
    +
    + 300 seems a bit more reasonable, but still an arbitrary number.
    +
    -
    - - - lib/models/patch/ - multi-file-patch.js - 359 - - 4h - - -
    -                
    { ' }' }
    -
    { ' }' }
    -
    { '' }
    -
    { ' isPatchTooLargeOrCollapsed = filePatchPath => {' }
    -
    -
    -
    -
    - - annthurium - 4 hours ago -
    -
    same as above - this name is too long and the line length linters are gonna be unhappy.
    -
    +
    + + + +
    +
    +
    + +
    +
    -
    +
    + + + lib/models/patch/ + multi-file-patch.js + 359 + + 4h + + +
    +              
    { ' }' }
    +
    { ' }' }
    +
    { '' }
    +
    { ' isPatchTooLargeOrCollapsed = filePatchPath => {' }
    +
    +
    +
    +
    - - -
    -
    -
    - -
    -
    + annthurium + 4 hours ago + +
    same as above - this name is too long and the line length linters are gonna be unhappy.
    +
    -
    - - - lib/views/ - file-patch-header-view.js - 52 - - 2h - - -
    -                
    { '
    ' }
    -
    { ' ' }
    -
    { ' {this.renderTitle()}' }
    -
    { ' {this.renderCollapseButton()}' }
    -
    -
    -
    -
    - - simurai - 2 hours ago -
    -
    Should we move the chevron icon to the left? So that the position doesn't jump when there are more buttons. Alternative would be to move it all the way to the right.
    -
    +
    + + + +
    +
    +
    + +
    +
    -
    - - - -
    -
    -
    - -
    -
    +
    + + + lib/views/ + file-patch-header-view.js + 52 + + 2h + + +
    +              
    { '
    ' }
    +
    { ' ' }
    +
    { ' {this.renderTitle()}' }
    +
    { ' {this.renderCollapseButton()}' }
    +
    +
    +
    +
    + + simurai + 2 hours ago +
    +
    Should we move the chevron icon to the left? So that the position doesn't jump when there are more buttons. Alternative would be to move it all the way to the right.
    +
    -
    - - - test/views/ - pr-comments-view.test.js - 17 - - 4h - - -
    -                
    { ' };' }
    -
    { ' return shallow(' }
    -
    { ' -
    { ' isPatchTooLargeOrCollapsed={sinon.stub().returns(false)}' }
    -
    -
    -
    -
    - - annthurium - 4 hours ago -
    -
    again, this variable name is too long
    -
    +
    + + + +
    +
    +
    + +
    +
    -
    +
    + + + test/views/ + pr-comments-view.test.js + 17 + + 4h + + +
    +              
    { ' };' }
    +
    { ' return shallow(' }
    +
    { ' +
    { ' isPatchTooLargeOrCollapsed={sinon.stub().returns(false)}' }
    +
    +
    +
    +
    - - -
    -
    -
    - -
    -
    + annthurium + 4 hours ago + +
    again, this variable name is too long
    +
    -
    - - - lib/models/patch/ - builder.js - 280 - - 4h - - -
    -                
    { ' /*' }
    -
    { ' * Construct a String containing diagnostic information about the internal state of this FilePatch.' }
    -
    { ' */' }
    -
    { ' inspect(opts = {}) {' }
    -
    -
    -
    -
    - - annthurium - 4 hours ago -
    -
    nice! This is going to be so useful when we are trying to debug the marker layer on a text buffer.
    -
    +
    + + + +
    +
    +
    + +
    +
    -
    +
    + + + lib/models/patch/ + builder.js + 280 + + 4h + + +
    +              
    { ' /*' }
    +
    { ' * Construct a String containing diagnostic information about the internal state of this FilePatch.' }
    +
    { ' */' }
    +
    { ' inspect(opts = {}) {' }
    +
    +
    +
    +
    - - -
    -
    -
    - annthurium marked this as resolved - -
    -
    + annthurium + 4 hours ago + +
    nice! This is going to be so useful when we are trying to debug the marker layer on a text buffer.
    +
    + +
    + + + +
    +
    +
    + annthurium marked this as resolved + +
    +
    -
    -
    + + + ) + } + render() { + return ( +
    + {this.renderReviewSummaries()} + {this.renderReviewComments()}
    ); } From 0491894267add6fa74d633074f71378bf8c379a8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 4 Mar 2019 10:45:37 -0500 Subject: [PATCH 2206/4053] Render the prReviewsContainer fragment in ReviewsContainer --- .../reviewsContainerQuery.graphql.js | 579 ++++++++++++++++-- lib/containers/reviews-container.js | 18 +- 2 files changed, 557 insertions(+), 40 deletions(-) diff --git a/lib/containers/__generated__/reviewsContainerQuery.graphql.js b/lib/containers/__generated__/reviewsContainerQuery.graphql.js index df5eb0e96a..1b17c91da2 100644 --- a/lib/containers/__generated__/reviewsContainerQuery.graphql.js +++ b/lib/containers/__generated__/reviewsContainerQuery.graphql.js @@ -1,6 +1,6 @@ /** * @flow - * @relayHash 68140a8082e21c7d56516fb6ca6c8db7 + * @relayHash 2ff5fd99b183d2c9a7e114fa51f38d04 */ /* eslint-disable */ @@ -9,13 +9,22 @@ /*:: import type { ConcreteRequest } from 'relay-runtime'; +type prReviewsContainer_pullRequest$ref = any; export type reviewsContainerQueryVariables = {| repoOwner: string, repoName: string, + prNumber: number, + reviewCount: number, + reviewCursor?: ?string, + commentCount: number, + commentCursor?: ?string, |}; export type reviewsContainerQueryResponse = {| +repository: ?{| - +id: string + +pullRequest: ?{| + +$fragmentRefs: prReviewsContainer_pullRequest$ref + |}, + +id: string, |} |}; export type reviewsContainerQuery = {| @@ -29,11 +38,94 @@ export type reviewsContainerQuery = {| query reviewsContainerQuery( $repoOwner: String! $repoName: String! + $prNumber: Int! + $reviewCount: Int! + $reviewCursor: String + $commentCount: Int! + $commentCursor: String ) { repository(owner: $repoOwner, name: $repoName) { + pullRequest(number: $prNumber) { + ...prReviewsContainer_pullRequest_y4qc0 + id + } id } } + +fragment prReviewsContainer_pullRequest_y4qc0 on PullRequest { + url + reviews(first: $reviewCount, after: $reviewCursor) { + pageInfo { + hasNextPage + endCursor + } + edges { + cursor + node { + id + body + commitId: commit { + oid + id + } + state + submittedAt + login: author { + __typename + login + ... on Node { + id + } + } + author { + __typename + avatarUrl + ... on Node { + id + } + } + ...prReviewCommentsContainer_review_1VbUmL + __typename + } + } + } +} + +fragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview { + id + submittedAt + comments(first: $commentCount, after: $commentCursor) { + pageInfo { + hasNextPage + endCursor + } + edges { + cursor + node { + id + author { + __typename + avatarUrl + login + ... on Node { + id + } + } + bodyHTML + isMinimized + path + position + replyTo { + id + } + createdAt + url + __typename + } + } + } +} */ const node/*: ConcreteRequest*/ = (function(){ @@ -49,39 +141,153 @@ var v0 = [ "name": "repoName", "type": "String!", "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "prNumber", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "reviewCursor", + "type": "String", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCount", + "type": "Int!", + "defaultValue": null + }, + { + "kind": "LocalArgument", + "name": "commentCursor", + "type": "String", + "defaultValue": null } ], v1 = [ { - "kind": "LinkedField", - "alias": null, - "name": "repository", - "storageKey": null, - "args": [ - { - "kind": "Variable", - "name": "name", - "variableName": "repoName", - "type": "String!" - }, - { - "kind": "Variable", - "name": "owner", - "variableName": "repoOwner", - "type": "String!" - } - ], - "concreteType": "Repository", - "plural": false, - "selections": [ - { - "kind": "ScalarField", - "alias": null, - "name": "id", - "args": null, - "storageKey": null - } - ] + "kind": "Variable", + "name": "name", + "variableName": "repoName", + "type": "String!" + }, + { + "kind": "Variable", + "name": "owner", + "variableName": "repoOwner", + "type": "String!" + } +], +v2 = [ + { + "kind": "Variable", + "name": "number", + "variableName": "prNumber", + "type": "Int!" + } +], +v3 = { + "kind": "ScalarField", + "alias": null, + "name": "id", + "args": null, + "storageKey": null +}, +v4 = { + "kind": "ScalarField", + "alias": null, + "name": "url", + "args": null, + "storageKey": null +}, +v5 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "reviewCursor", + "type": "String" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "reviewCount", + "type": "Int" + } +], +v6 = { + "kind": "LinkedField", + "alias": null, + "name": "pageInfo", + "storageKey": null, + "args": null, + "concreteType": "PageInfo", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "hasNextPage", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "endCursor", + "args": null, + "storageKey": null + } + ] +}, +v7 = { + "kind": "ScalarField", + "alias": null, + "name": "cursor", + "args": null, + "storageKey": null +}, +v8 = { + "kind": "ScalarField", + "alias": null, + "name": "__typename", + "args": null, + "storageKey": null +}, +v9 = { + "kind": "ScalarField", + "alias": null, + "name": "login", + "args": null, + "storageKey": null +}, +v10 = { + "kind": "ScalarField", + "alias": null, + "name": "avatarUrl", + "args": null, + "storageKey": null +}, +v11 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "commentCursor", + "type": "String" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "commentCount", + "type": "Int" } ]; return { @@ -92,23 +298,326 @@ return { "type": "Query", "metadata": null, "argumentDefinitions": (v0/*: any*/), - "selections": (v1/*: any*/) + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "repository", + "storageKey": null, + "args": (v1/*: any*/), + "concreteType": "Repository", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "pullRequest", + "storageKey": null, + "args": (v2/*: any*/), + "concreteType": "PullRequest", + "plural": false, + "selections": [ + { + "kind": "FragmentSpread", + "name": "prReviewsContainer_pullRequest", + "args": [ + { + "kind": "Variable", + "name": "commentCount", + "variableName": "commentCount", + "type": null + }, + { + "kind": "Variable", + "name": "commentCursor", + "variableName": "commentCursor", + "type": null + }, + { + "kind": "Variable", + "name": "reviewCount", + "variableName": "reviewCount", + "type": null + }, + { + "kind": "Variable", + "name": "reviewCursor", + "variableName": "reviewCursor", + "type": null + } + ] + } + ] + }, + (v3/*: any*/) + ] + } + ] }, "operation": { "kind": "Operation", "name": "reviewsContainerQuery", "argumentDefinitions": (v0/*: any*/), - "selections": (v1/*: any*/) + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "repository", + "storageKey": null, + "args": (v1/*: any*/), + "concreteType": "Repository", + "plural": false, + "selections": [ + { + "kind": "LinkedField", + "alias": null, + "name": "pullRequest", + "storageKey": null, + "args": (v2/*: any*/), + "concreteType": "PullRequest", + "plural": false, + "selections": [ + (v4/*: any*/), + { + "kind": "LinkedField", + "alias": null, + "name": "reviews", + "storageKey": null, + "args": (v5/*: any*/), + "concreteType": "PullRequestReviewConnection", + "plural": false, + "selections": [ + (v6/*: any*/), + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewEdge", + "plural": true, + "selections": [ + (v7/*: any*/), + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReview", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "kind": "ScalarField", + "alias": null, + "name": "body", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "commitId", + "name": "commit", + "storageKey": null, + "args": null, + "concreteType": "Commit", + "plural": false, + "selections": [ + { + "kind": "ScalarField", + "alias": null, + "name": "oid", + "args": null, + "storageKey": null + }, + (v3/*: any*/) + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "state", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "submittedAt", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": "login", + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + (v8/*: any*/), + (v9/*: any*/), + (v3/*: any*/) + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + (v8/*: any*/), + (v10/*: any*/), + (v3/*: any*/) + ] + }, + { + "kind": "LinkedField", + "alias": null, + "name": "comments", + "storageKey": null, + "args": (v11/*: any*/), + "concreteType": "PullRequestReviewCommentConnection", + "plural": false, + "selections": [ + (v6/*: any*/), + { + "kind": "LinkedField", + "alias": null, + "name": "edges", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewCommentEdge", + "plural": true, + "selections": [ + (v7/*: any*/), + { + "kind": "LinkedField", + "alias": null, + "name": "node", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "kind": "LinkedField", + "alias": null, + "name": "author", + "storageKey": null, + "args": null, + "concreteType": null, + "plural": false, + "selections": [ + (v8/*: any*/), + (v10/*: any*/), + (v9/*: any*/), + (v3/*: any*/) + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "bodyHTML", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "isMinimized", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "path", + "args": null, + "storageKey": null + }, + { + "kind": "ScalarField", + "alias": null, + "name": "position", + "args": null, + "storageKey": null + }, + { + "kind": "LinkedField", + "alias": null, + "name": "replyTo", + "storageKey": null, + "args": null, + "concreteType": "PullRequestReviewComment", + "plural": false, + "selections": [ + (v3/*: any*/) + ] + }, + { + "kind": "ScalarField", + "alias": null, + "name": "createdAt", + "args": null, + "storageKey": null + }, + (v4/*: any*/), + (v8/*: any*/) + ] + } + ] + } + ] + }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "comments", + "args": (v11/*: any*/), + "handle": "connection", + "key": "PrReviewCommentsContainer_comments", + "filters": null + }, + (v8/*: any*/) + ] + } + ] + } + ] + }, + { + "kind": "LinkedHandle", + "alias": null, + "name": "reviews", + "args": (v5/*: any*/), + "handle": "connection", + "key": "PrReviewsContainer_reviews", + "filters": null + }, + (v3/*: any*/) + ] + }, + (v3/*: any*/) + ] + } + ] }, "params": { "operationKind": "query", "name": "reviewsContainerQuery", "id": null, - "text": "query reviewsContainerQuery(\n $repoOwner: String!\n $repoName: String!\n) {\n repository(owner: $repoOwner, name: $repoName) {\n id\n }\n}\n", + "text": "query reviewsContainerQuery(\n $repoOwner: String!\n $repoName: String!\n $prNumber: Int!\n $reviewCount: Int!\n $reviewCursor: String\n $commentCount: Int!\n $commentCursor: String\n) {\n repository(owner: $repoOwner, name: $repoName) {\n pullRequest(number: $prNumber) {\n ...prReviewsContainer_pullRequest_y4qc0\n id\n }\n id\n }\n}\n\nfragment prReviewsContainer_pullRequest_y4qc0 on PullRequest {\n url\n reviews(first: $reviewCount, after: $reviewCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n body\n commitId: commit {\n oid\n id\n }\n state\n submittedAt\n login: author {\n __typename\n login\n ... on Node {\n id\n }\n }\n author {\n __typename\n avatarUrl\n ... on Node {\n id\n }\n }\n ...prReviewCommentsContainer_review_1VbUmL\n __typename\n }\n }\n }\n}\n\nfragment prReviewCommentsContainer_review_1VbUmL on PullRequestReview {\n id\n submittedAt\n comments(first: $commentCount, after: $commentCursor) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n cursor\n node {\n id\n author {\n __typename\n avatarUrl\n login\n ... on Node {\n id\n }\n }\n bodyHTML\n isMinimized\n path\n position\n replyTo {\n id\n }\n createdAt\n url\n __typename\n }\n }\n }\n}\n", "metadata": {} } }; })(); // prettier-ignore -(node/*: any*/).hash = '2a432f9e2a062215c96118ec0b5070cd'; +(node/*: any*/).hash = '51bf09a7273a2f166d4ff1c89b433cf6'; module.exports = node; diff --git a/lib/containers/reviews-container.js b/lib/containers/reviews-container.js index 81cd707a27..79a78d857e 100644 --- a/lib/containers/reviews-container.js +++ b/lib/containers/reviews-container.js @@ -74,13 +74,21 @@ export default class ReviewsContainer extends React.Component { ( $repoOwner: String! $repoName: String! - # $prNumber: Int! - # $reviewCount: Int! - # $reviewCursor: String - # $commentCount: Int! - # $commentCursor: String + $prNumber: Int! + $reviewCount: Int! + $reviewCursor: String + $commentCount: Int! + $commentCursor: String ) { repository(owner: $repoOwner, name: $repoName) { + pullRequest(number: $prNumber) { + ...prReviewsContainer_pullRequest @arguments( + reviewCount: $reviewCount + reviewCursor: $reviewCursor + commentCount: $commentCount + commentCursor: $commentCursor + ) + } id } } From c9d6b998c06a2fbbb8718aaa900585d2bf2a3a3e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 4 Mar 2019 10:53:44 -0500 Subject: [PATCH 2207/4053] :shirt: lint reviews-view --- lib/views/reviews-view.js | 93 +++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/lib/views/reviews-view.js b/lib/views/reviews-view.js index 22a6bc425e..a804f27c8d 100644 --- a/lib/views/reviews-view.js +++ b/lib/views/reviews-view.js @@ -1,5 +1,4 @@ import React from 'react'; -import {inspect} from 'util'; export default class ReviewsView extends React.Component { @@ -10,12 +9,12 @@ export default class ReviewsView extends React.Component {

    Reviews

    - {[1,2].map(({something}) => ( + {[1, 2].map(({something}) => (
    - {/*icon-check: approved, icon-comment: commented, icon-alert: requested changes */} + {/* icon-check: approved, icon-comment: commented, icon-alert: requested changes */} - + vanessayuenn approved these changes 18 minutes ago @@ -37,7 +36,7 @@ export default class ReviewsView extends React.Component {

    Review comments

    Resolved 1 of 7 - +
    @@ -48,12 +47,12 @@ export default class ReviewsView extends React.Component { lib/controllers/ commit-detail-controller.js 19 - + 4h
    @@ -65,7 +64,7 @@ export default class ReviewsView extends React.Component {
                 
    - + annthurium 4 hours ago
    @@ -73,8 +72,8 @@ export default class ReviewsView extends React.Component {
    - - + +
    @@ -89,12 +88,12 @@ export default class ReviewsView extends React.Component { lib/controllers/ pr-reviews-controller.js 22 - + 4h
    @@ -106,7 +105,7 @@ export default class ReviewsView extends React.Component {
                 
    - + annthurium 4 hours ago
    @@ -114,7 +113,7 @@ export default class ReviewsView extends React.Component {
    - + vanessayuenn 18 minutes ago
    @@ -122,8 +121,8 @@ export default class ReviewsView extends React.Component {
    - - + +
    @@ -138,12 +137,12 @@ export default class ReviewsView extends React.Component { lib/models/patch/ builder.js 12 - + 1h
    @@ -155,7 +154,7 @@ export default class ReviewsView extends React.Component {
                 
    - + annthurium 1 hour ago
    @@ -163,7 +162,7 @@ export default class ReviewsView extends React.Component {
    - + simurai 1 hour ago
    @@ -171,7 +170,7 @@ export default class ReviewsView extends React.Component {
    - + annthurium 1 hour ago
    @@ -179,18 +178,18 @@ export default class ReviewsView extends React.Component {
    - + vanessayuenn 25 minutes ago
    -
    mm we were using 1000 lines as the threshold before, but that's for performance reasons. 100 does seem a bit small though considering it's counting both deleted and added lines. 🤔
    -
    +
    mm we were using 1000 lines as the threshold before, but that's for performance reasons. 100 does seem a bit small though considering it's counting both deleted and added lines. 🤔
    +
    300 seems a bit more reasonable, but still an arbitrary number.
    - - + +
    @@ -205,12 +204,12 @@ export default class ReviewsView extends React.Component { lib/models/patch/ multi-file-patch.js 359 - + 4h
    @@ -222,7 +221,7 @@ export default class ReviewsView extends React.Component {
                 
    - + annthurium 4 hours ago
    @@ -230,8 +229,8 @@ export default class ReviewsView extends React.Component {
    - - + +
    @@ -246,12 +245,12 @@ export default class ReviewsView extends React.Component { lib/views/ file-patch-header-view.js 52 - + 2h
    @@ -263,7 +262,7 @@ export default class ReviewsView extends React.Component {
                 
    - + simurai 2 hours ago
    @@ -271,8 +270,8 @@ export default class ReviewsView extends React.Component {
    - - + +
    @@ -287,12 +286,12 @@ export default class ReviewsView extends React.Component { test/views/ pr-comments-view.test.js 17 - + 4h
    @@ -304,7 +303,7 @@ export default class ReviewsView extends React.Component {
                 
    - + annthurium 4 hours ago
    @@ -312,8 +311,8 @@ export default class ReviewsView extends React.Component {
    - - + +
    @@ -328,12 +327,12 @@ export default class ReviewsView extends React.Component { lib/models/patch/ builder.js 280 - + 4h
    @@ -345,7 +344,7 @@ export default class ReviewsView extends React.Component {
                 
    - + annthurium 4 hours ago
    @@ -353,8 +352,8 @@ export default class ReviewsView extends React.Component {
    - - + +
    @@ -366,7 +365,7 @@ export default class ReviewsView extends React.Component {
    - ) + ); } render() { From da9c23948b05fd3227ef1440655cb8a68ad0eff0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 4 Mar 2019 10:54:12 -0500 Subject: [PATCH 2208/4053] Fetch reviews and comments with a PullRequestReviewsContainer --- lib/views/reviews-view.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/views/reviews-view.js b/lib/views/reviews-view.js index a804f27c8d..57365fd15c 100644 --- a/lib/views/reviews-view.js +++ b/lib/views/reviews-view.js @@ -1,4 +1,5 @@ import React from 'react'; +import PullRequestReviewsContainer from '../containers/pr-reviews-container'; export default class ReviewsView extends React.Component { @@ -372,7 +373,9 @@ export default class ReviewsView extends React.Component { return (
    {this.renderReviewSummaries()} - {this.renderReviewComments()} + + {_commentThread => this.renderReviewComments()} +
    ); } From 3d6eaff4bed8aaf0ab8b3fcb83cc51fc7b90f74b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 4 Mar 2019 11:24:49 -0500 Subject: [PATCH 2209/4053] Call a single render prop with review and comment thread collections --- lib/controllers/pr-reviews-controller.js | 10 +-- lib/views/multi-file-patch-view.js | 7 +- .../controllers/pr-reviews-controller.test.js | 68 ++++++++----------- 3 files changed, 35 insertions(+), 50 deletions(-) diff --git a/lib/controllers/pr-reviews-controller.js b/lib/controllers/pr-reviews-controller.js index 94adeed5a5..79811e3f01 100644 --- a/lib/controllers/pr-reviews-controller.js +++ b/lib/controllers/pr-reviews-controller.js @@ -61,6 +61,8 @@ export default class PullRequestReviewsController extends React.Component { return null; } + const reviews = this.props.pullRequest.reviews.edges.map(edge => edge.node); + const commentThreads = Object.keys(this.state).reverse().map(rootCommentId => { return { rootCommentId, @@ -80,13 +82,7 @@ export default class PullRequestReviewsController extends React.Component { return ( {this.renderCommentFetchingContainers()} - {commentThreads.map(commentThread => { - return ( - - {this.props.children(commentThread)} - - ); - })} + {this.props.children({reviews, commentThreads})} ); } diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index edc80adb59..761861af54 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -386,15 +386,16 @@ export default class MultiFilePatchView extends React.Component { // "forceRerender" ensures that the PullRequestCommentsView re-renders each time that the MultiFilePatchView does. // It doesn't re-query for reviews, but it does re-check patch visibility. return ( - - {commentThread => ( + + {({commentThreads}) => commentThreads.map(commentThread => ( - )} + ))} ); } else { diff --git a/test/controllers/pr-reviews-controller.test.js b/test/controllers/pr-reviews-controller.test.js index e90c1d6ca9..7bbf9e779c 100644 --- a/test/controllers/pr-reviews-controller.test.js +++ b/test/controllers/pr-reviews-controller.test.js @@ -1,19 +1,14 @@ import React from 'react'; import {shallow} from 'enzyme'; import {reviewBuilder} from '../builder/pr'; -import {inspect} from 'util'; import PullRequestReviewsController from '../../lib/controllers/pr-reviews-controller'; import PullRequestReviewCommentsContainer from '../../lib/containers/pr-review-comments-container'; import {PAGE_SIZE, PAGINATION_WAIT_TIME_MS} from '../../lib/helpers'; -function SomeView(props) { - return
    {inspect(props, {depth: 3})}
    ; -} - describe('PullRequestReviewsController', function() { - function buildApp(opts, overrideProps = {}, childFn = SomeView) { + function buildApp(opts, overrideProps = {}) { const o = { relayHasMore: () => false, relayLoadMore: () => {}, @@ -47,15 +42,13 @@ describe('PullRequestReviewsController', function() { isLoading: o.relayIsLoading, }, pullRequest: {reviews}, + children: () => {}, ...overrideProps, }; - return ( - - {childFn} - - ); + return ; } + it('returns null if props.pullRequest is falsy', function() { const wrapper = shallow(buildApp({}, {pullRequest: null})); assert.isNull(wrapper.getElement()); @@ -79,7 +72,7 @@ describe('PullRequestReviewsController', function() { assert.strictEqual(containers.at(1).prop('review').id, review2.id); }); - it('calls the child render prop for each comment thread', function() { + it('calls the render prop with all reviews and all comment threads', function() { const review1 = reviewBuilder() .id(10) .submittedAt('2019-01-01T10:00:00Z') @@ -100,11 +93,19 @@ describe('PullRequestReviewsController', function() { const reviewSpecs = [review1, review2]; const passThroughProp = 'I only exist for the children'; - const wrapper = shallow(buildApp({reviewSpecs}, {}, thread => { - return ; + const wrapper = shallow(buildApp({reviewSpecs}, { + children: ({reviews, commentThreads}) => ( + + ), })); - assert.isFalse(wrapper.exists('ChildComponent')); + assert.strictEqual(wrapper.find('ChildComponent').prop('passThroughProp'), passThroughProp); + assert.deepEqual(wrapper.find('ChildComponent').prop('reviews').map(review => review.id), [10, 20]); + assert.lengthOf(wrapper.find('ChildComponent').prop('threads'), 0); wrapper.find(PullRequestReviewCommentsContainer).at(0).prop('collectComments')({ reviewId: review1.id, @@ -113,12 +114,11 @@ describe('PullRequestReviewsController', function() { fetchingMoreComments: false, }); - assert.lengthOf(wrapper.find('ChildComponent'), 1); - assert.strictEqual(wrapper.find('ChildComponent').at(0).prop('passThroughProp'), passThroughProp); - assert.deepEqual(wrapper.find('ChildComponent').at(0).prop('thread'), { - rootCommentId: '1', - comments: [review1.comments.edges[0].node], - }); + assert.strictEqual(wrapper.find('ChildComponent').prop('passThroughProp'), passThroughProp); + assert.deepEqual(wrapper.find('ChildComponent').prop('reviews').map(review => review.id), [10, 20]); + assert.deepEqual(wrapper.find('ChildComponent').prop('threads'), [ + {rootCommentId: '1', comments: [review1.comments.edges[0].node]}, + ]); wrapper.find(PullRequestReviewCommentsContainer).at(1).prop('collectComments')({ reviewId: review2.id, @@ -127,25 +127,13 @@ describe('PullRequestReviewsController', function() { fetchingMoreComments: false, }); - assert.lengthOf(wrapper.find('ChildComponent'), 3); - - assert.strictEqual(wrapper.find('ChildComponent').at(0).prop('passThroughProp'), passThroughProp); - assert.deepEqual(wrapper.find('ChildComponent').at(0).prop('thread'), { - rootCommentId: '4', - comments: [review2.comments.edges[2].node], - }); - - assert.strictEqual(wrapper.find('ChildComponent').at(1).prop('passThroughProp'), passThroughProp); - assert.deepEqual(wrapper.find('ChildComponent').at(1).prop('thread'), { - rootCommentId: '2', - comments: [review2.comments.edges[0].node], - }); - - assert.strictEqual(wrapper.find('ChildComponent').at(2).prop('passThroughProp'), passThroughProp); - assert.deepEqual(wrapper.find('ChildComponent').at(2).prop('thread'), { - rootCommentId: '1', - comments: [review1.comments.edges[0].node, review2.comments.edges[1].node], - }); + assert.strictEqual(wrapper.find('ChildComponent').prop('passThroughProp'), passThroughProp); + assert.deepEqual(wrapper.find('ChildComponent').prop('reviews').map(review => review.id), [10, 20]); + assert.deepEqual(wrapper.find('ChildComponent').prop('threads'), [ + {rootCommentId: '4', comments: [review2.comments.edges[2].node]}, + {rootCommentId: '2', comments: [review2.comments.edges[0].node]}, + {rootCommentId: '1', comments: [review1.comments.edges[0].node, review2.comments.edges[1].node]}, + ]); }); describe('collectComments', function() { From 4c3211d15045e1a66a94701a4458624c04a54bef Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 4 Mar 2019 11:29:03 -0500 Subject: [PATCH 2210/4053] Oops, you _do_ need that pullRequest={} prop --- lib/views/multi-file-patch-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 761861af54..f18183dfb5 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -386,7 +386,7 @@ export default class MultiFilePatchView extends React.Component { // "forceRerender" ensures that the PullRequestCommentsView re-renders each time that the MultiFilePatchView does. // It doesn't re-query for reviews, but it does re-check patch visibility. return ( - + {({commentThreads}) => commentThreads.map(commentThread => ( Date: Mon, 4 Mar 2019 17:32:17 +0100 Subject: [PATCH 2211/4053] split up to components, pull in markdown view and timeago --- lib/views/reviews-view.js | 390 +++++++------------------------------- 1 file changed, 67 insertions(+), 323 deletions(-) diff --git a/lib/views/reviews-view.js b/lib/views/reviews-view.js index 57365fd15c..19e04cf20b 100644 --- a/lib/views/reviews-view.js +++ b/lib/views/reviews-view.js @@ -1,5 +1,7 @@ import React from 'react'; import PullRequestReviewsContainer from '../containers/pr-reviews-container'; +import GithubDotcomMarkdown from './github-dotcom-markdown'; +import Timeago from './timeago'; export default class ReviewsView extends React.Component { @@ -41,329 +43,7 @@ export default class ReviewsView extends React.Component {
    - -
    - - - lib/controllers/ - commit-detail-controller.js - 19 - - 4h - - -
    -              
    { ' messageCollapsible: this.props.commit.isBodyLong(),' }
    -
    { ' messageOpen: !this.props.commit.isBodyLong(),' }
    -
    { ' };' }
    -
    { ' }' }
    -
    -
    -
    -
    - - annthurium - 4 hours ago -
    -
    mind adding the missing semicolon here?
    -
    - -
    - - - -
    -
    -
    - -
    -
    - -
    - - - lib/controllers/ - pr-reviews-controller.js - 22 - - 4h - - -
    -              
    { ' ),' }
    -
    { ' }),' }
    -
    { ' getBufferRowForDiffPosition: PropTypes.func.isRequired,' }
    -
    { ' isPatchTooLargeOrCollapsed: PropTypes.func.isRequired,' }
    -
    -
    -
    -
    - - annthurium - 4 hours ago -
    -
    this is a really long name -- can you come up with something more concise?
    -
    -
    -
    - - vanessayuenn - 18 minutes ago -
    -
    how about just isPatchCollapsed?
    -
    - -
    - - - -
    -
    -
    - -
    -
    - -
    - - - lib/models/patch/ - builder.js - 12 - - 1h - - -
    -              
    { 'export const DEFAULT_OPTIONS = {' }
    -
    { ' // Number of lines after which we consider the diff "large"' }
    -
    { ' // TODO: Set this based on performance measurements' }
    -
    { ' largeDiffThreshold: 100,' }
    -
    -
    -
    -
    - - annthurium - 1 hour ago -
    -
    @simurai: how many lines do you think constitutes a large diff? Not just from a performance perspective, but from a user experience perspective. Like how many lines is disruptive to a user when they're trying to read, because often large diffs are the result of auto generated code.
    -
    -
    -
    - - simurai - 1 hour ago -
    -
    Hmmm.. will large diffs be collapsed by default or there is a "load" button? Maybe if the diff is so large that it fills the whole scroll height. Then I can uncollapse only if I'm really interested in that file. 100 seems fine. 👍
    -
    -
    -
    - - annthurium - 1 hour ago -
    - -
    -
    -
    - - vanessayuenn - 25 minutes ago -
    -
    mm we were using 1000 lines as the threshold before, but that's for performance reasons. 100 does seem a bit small though considering it's counting both deleted and added lines. 🤔
    -
    - 300 seems a bit more reasonable, but still an arbitrary number.
    -
    - -
    - - - -
    -
    -
    - -
    -
    - -
    - - - lib/models/patch/ - multi-file-patch.js - 359 - - 4h - - -
    -              
    { ' }' }
    -
    { ' }' }
    -
    { '' }
    -
    { ' isPatchTooLargeOrCollapsed = filePatchPath => {' }
    -
    -
    -
    -
    - - annthurium - 4 hours ago -
    -
    same as above - this name is too long and the line length linters are gonna be unhappy.
    -
    - -
    - - - -
    -
    -
    - -
    -
    - -
    - - - lib/views/ - file-patch-header-view.js - 52 - - 2h - - -
    -              
    { '
    ' }
    -
    { ' ' }
    -
    { ' {this.renderTitle()}' }
    -
    { ' {this.renderCollapseButton()}' }
    -
    -
    -
    -
    - - simurai - 2 hours ago -
    -
    Should we move the chevron icon to the left? So that the position doesn't jump when there are more buttons. Alternative would be to move it all the way to the right.
    -
    - -
    - - - -
    -
    -
    - -
    -
    - -
    - - - test/views/ - pr-comments-view.test.js - 17 - - 4h - - -
    -              
    { ' };' }
    -
    { ' return shallow(' }
    -
    { ' -
    { ' isPatchTooLargeOrCollapsed={sinon.stub().returns(false)}' }
    -
    -
    -
    -
    - - annthurium - 4 hours ago -
    -
    again, this variable name is too long
    -
    - -
    - - - -
    -
    -
    - -
    -
    - -
    - - - lib/models/patch/ - builder.js - 280 - - 4h - - -
    -              
    { ' /*' }
    -
    { ' * Construct a String containing diagnostic information about the internal state of this FilePatch.' }
    -
    { ' */' }
    -
    { ' inspect(opts = {}) {' }
    -
    -
    -
    -
    - - annthurium - 4 hours ago -
    -
    nice! This is going to be so useful when we are trying to debug the marker layer on a text buffer.
    -
    - -
    - - - -
    -
    -
    - annthurium marked this as resolved - -
    -
    - +
    ); @@ -373,6 +53,7 @@ export default class ReviewsView extends React.Component { return (
    {this.renderReviewSummaries()} + {this.renderReviewComments()} {_commentThread => this.renderReviewComments()} @@ -380,3 +61,66 @@ export default class ReviewsView extends React.Component { ); } } + +class ReviewCommentThreadView extends React.Component { + render() { + const {comments} = this.props.commentThread; + const rootComment = comments[0]; + + return ( +
    + + + lib/controllers/ + commit-detail-controller.js + 19 + {rootComment.author.login} + {rootComment.createdAt} + + +
    +          
    { ' messageCollapsible: this.props.commit.isBodyLong(),' }
    +
    { ' messageOpen: !this.props.commit.isBodyLong(),' }
    +
    { ' };' }
    +
    { ' }' }
    +
    + +
    + {comments.map(comment => ( +
    +
    + {comment.author.login} + {comment.author.login} + + + +
    +
    + +
    +
    + ))} + +
    +